본문으로 건너뛰기

에러 시스템 사양

Functorium의 에러 시스템은 레이어별 sealed record 계층(DomainErrorKind, ApplicationErrorKind, AdapterErrorKind)과 레이어별 팩토리(DomainError, ApplicationError, AdapterError)로 구성됩니다. 팩토리의 공통 생성 로직은 내부 LayerErrorCore에 집중되고, Expected 에러의 공통 override는 ExpectedErrorBase에 통합됩니다. 이 사양서는 공개/내부 타입의 시그니처, 속성, 에러 코드 생성 규칙을 정의합니다.

타입네임스페이스설명
ErrorKindFunctorium.Abstractions.Errors모든 레이어 에러 타입의 추상 기반 record
IHasErrorCodeFunctorium.Abstractions.Errors에러 코드 접근 인터페이스
ErrorFactoryFunctorium.Abstractions.ErrorsExpected/Exceptional 에러 생성 팩토리
DomainErrorKindFunctorium.Domains.Errors도메인 에러 타입 sealed record 계층 (9개 카테고리)
DomainErrorFunctorium.Domains.Errors도메인 에러 생성 팩토리
ApplicationErrorKindFunctorium.Applications.Errors애플리케이션 에러 타입 sealed record 계층 (7개 카테고리)
ApplicationErrorFunctorium.Applications.Errors애플리케이션 에러 생성 팩토리
AdapterErrorKindFunctorium.Adapters.Errors어댑터 에러 타입 sealed record 계층 (9개 카테고리)
AdapterErrorFunctorium.Adapters.Errors어댑터 에러 생성 팩토리
타입네임스페이스설명
ExpectedErrorBaseFunctorium.Abstractions.ErrorsExpected 에러 4종의 공통 LanguageExt Error override 기반 클래스
ExpectedError (4종)Functorium.Abstractions.ErrorsExpected 에러 — 값 저장만 담당하며 나머지는 base에서 상속
ExceptionalErrorFunctorium.Abstractions.ErrorsException을 에러 코드와 함께 래핑
LayerErrorCoreFunctorium.Abstractions.Errors3개 레이어 팩토리의 공통 에러 코드 생성 로직
ErrorAssertionCoreFunctorium.Testing.Assertions.Errors3개 레이어 Assertion의 공통 검증 로직

모든 에러 코드는 다음 패턴을 따릅니다:

{LayerPrefix}.{ContextName}.{ErrorName}
레이어접두사예시
DomainDomainDomain.Email.Empty
ApplicationApplicationApplication.CreateProductCommand.AlreadyExists
AdapterAdapterAdapter.ProductRepository.NotFound

다음 다이어그램은 사용자 코드의 진입점(레이어 팩토리)부터 내부 에러 레코드 생성, 관측성 계층까지의 흐름을 한눈에 보여줍니다. 1.0.0-alpha.4 재설계 기준 — ErrorKind 추상 기반, ErrorFactory(internal), ExpectedError / ExceptionalError, 단축된 prefix ("Domain" / "Application" / "Adapter")가 반영되어 있습니다.

[Public API -- User Code Path]
DomainError.For<Email>(new DomainErrorKind.Empty(), value, msg)
| |
| (static factory) | (classification = Kind)
| +--> ErrorKind (abstract base)
| |
| v
| *ErrorKind (per-layer derivation)
| |
| v
| Empty/Null/Custom/... (nested)
v
IHasErrorCode { string ErrorCode } <-- implemented by all errors
| (internal delegation)
v
[Internal Implementation -- InternalsVisibleTo: Adapters, Testing]
LayerErrorCore.Create<Email>(ErrorCodePrefixes.Domain, kind, ...)
| |
| | (3 internal constants)
| +--> "Domain" / "Application" / "Adapter"
v
ErrorFactory.CreateExpected(prefix, typeName, kind.Name, msg)
| (exception path)
+--> ErrorFactory.CreateExceptional(exception, ...)
v
ExpectedError / ExceptionalError : LanguageExt.Error
* ErrorCode : string <-- "Domain.Email.Empty"
* NumericCode : int <-- -1000 (default)
v
Serilog Destructurer
--> ErrorLogFieldNames.{Kind, NumericCode, Message, Inner, ...}

참고: 이 사양서는 현재 1.0.0-alpha.4+ 설계 기준입니다. 이전 1.0.0-alpha 버전에서의 마이그레이션 매핑은 1.0.0-alpha.4 릴리스 노트를 참조하세요.


namespace Functorium.Abstractions.Errors;
public abstract record ErrorKind
{
public virtual string Name => GetType().Name;
}

ErrorKind 모든 레이어별 에러 타입(DomainErrorKind, ApplicationErrorKind, AdapterErrorKind)의 공통 기반입니다.

멤버종류설명
Namevirtual string에러 코드의 마지막 세그먼트. 기본값은 GetType().Name

레이어 접두사 상수: "Domain"·"Application"·"Adapter" prefix 상수는 내부 ErrorCodePrefixes 클래스(Functorium.Abstractions.Errors 네임스페이스, internal)로 분리되어 있으며, 외부 공개 API에 노출되지 않습니다. 소비자는 레이어 팩토리(DomainError.For<T>(...) 등)를 통해 간접적으로만 사용합니다.

namespace Functorium.Abstractions.Errors;
public interface IHasErrorCode
{
string ErrorCode { get; }
}

IHasErrorCode 리플렉션 없이 타입 안전하게 에러 코드에 접근하기 위한 인터페이스입니다. ExpectedErrorExceptionalError이 이 인터페이스를 구현합니다.


internal abstract record ExpectedErrorBase(
string ErrorCode,
string ErrorMessage,
int NumericCode = -1000,
Option<Error> Inner = default) : Error, IHasErrorCode

ExpectedErrorBase Expected 에러 4종(ExpectedError, <T>, <T1,T2>, <T1,T2,T3>)의 공통 기반 클래스입니다. LanguageExt Error의 13개 override를 한 곳에 정의하여 파생 타입의 중복을 제거합니다.

멤버종류설명
ErrorCodestring"{Prefix}.{Context}.{ErrorName}" 형식 에러 코드
Messageoverride string사람이 읽을 수 있는 에러 메시지
Codeoverride int정수 에러 코드 ID (기본값 -1000)
Inneroverride Option<Error>내부 에러 (기본값 None)
ToString()sealed overrideMessage 반환. sealed로 파생 record의 자동생성 방지
ToErrorException()overrideWrappedErrorExpectedException 반환
IsExpectedbool항상 true
IsExceptionalbool항상 false

sealed override ToString(): C# record는 파생 클래스에서 ToString()을 자동 재생성합니다. sealed로 이를 차단하여 모든 파생 타입이 일관되게 Message를 반환하도록 보장합니다.

internal record ExpectedError(
string ErrorCode,
string ErrorCurrentValue,
string ErrorMessage,
int NumericCode = -1000,
Option<Error> Inner = default)
: ExpectedErrorBase(ErrorCode, ErrorMessage, NumericCode, Inner)

ExpectedError 비즈니스 규칙 위반 등 예상된(Expected) 에러를 표현합니다. ExpectedErrorBase에서 ErrorCode, Message, Code, Inner, ToString(), IsExpected, IsExceptional 등 공통 멤버를 상속받고, 파생 타입은 에러 발생 시점의 값(ErrorCurrentValue)만 추가로 정의합니다.

오버로드추가 속성설명
ExpectedErrorErrorCurrentValue: string문자열 값
ExpectedError<T>ErrorCurrentValue: T타입 값 (1개)
ExpectedError<T1, T2>ErrorCurrentValue1: T1, ErrorCurrentValue2: T2타입 값 (2개)
ExpectedError<T1, T2, T3>ErrorCurrentValue1: T1, ErrorCurrentValue2: T2, ErrorCurrentValue3: T3타입 값 (3개)
internal record ExceptionalError : Error, IHasErrorCode
{
public ExceptionalError(string errorCode, Exception exception);
}

ExceptionalError 시스템 예외(Exception)를 에러 코드와 함께 래핑합니다. IsExpected = false, IsExceptional = true입니다.

속성타입설명
ErrorCodestring"{Prefix}.{Context}.{ErrorName}" 형식 에러 코드
Messagestringexception.Message에서 추출
Codeintexception.HResult에서 추출
InnerOption<Error>InnerException이 있으면 재귀적으로 래핑
IsExpectedbool항상 false
IsExceptionalbool항상 true
namespace Functorium.Abstractions.Errors;
internal static class ErrorFactory
{
// Expected 에러 생성
public static Error CreateExpected(string errorCode, string errorCurrentValue, string errorMessage);
public static Error CreateExpected<T>(string errorCode, T errorCurrentValue, string errorMessage) where T : notnull;
public static Error CreateExpected<T1, T2>(string errorCode, T1 errorCurrentValue1, T2 errorCurrentValue2, string errorMessage)
where T1 : notnull where T2 : notnull;
public static Error CreateExpected<T1, T2, T3>(string errorCode, T1 errorCurrentValue1, T2 errorCurrentValue2, T3 errorCurrentValue3, string errorMessage)
where T1 : notnull where T2 : notnull where T3 : notnull;
// Exceptional 에러 생성
public static Error CreateExceptional(string errorCode, Exception exception);
}

ErrorFactory ExpectedErrorExceptionalError 인스턴스를 생성하는 internal 정적 팩토리입니다. 외부 소비자가 직접 호출하지 않으며, 공개 레이어 팩토리가 다음 흐름으로 위임합니다:

DomainError.For<T>(DomainErrorKind, ...) ← 레이어 타입 안전성 (공개 API)
→ LayerErrorCore.Create<T>(ErrorCodePrefixes.Domain, kind, ...) ← 공통 에러 코드 조립 (internal)
→ ErrorFactory.CreateExpected(errorCode, ...) ← ExpectedError 인스턴스 생성 (internal)

LayerErrorCore가 에러 코드 문자열({Prefix}.{Context}.{Kind.Name})을 조립하고, ErrorFactory가 최종 Error 인스턴스를 생성합니다. 모든 메서드에 [AggressiveInlining]이 적용되어 JIT이 위임 호출을 제거하므로 성능 차이는 없습니다.

메서드반환설명
CreateExpected(...)ErrorExpected 에러 생성 (4개 오버로드)
CreateExceptional(...)ErrorException을 Exceptional 에러로 래핑

namespace Functorium.Domains.Errors;
public abstract partial record DomainErrorKind : ErrorKind;

DomainErrorKind 도메인 레이어 에러의 sealed record 계층 기반입니다. 9개 카테고리로 분류됩니다.

sealed record속성설명
NotFound(없음)값을 찾을 수 없음
AlreadyExists(없음)값이 이미 존재함
Duplicate(없음)중복된 값
Mismatch(없음)값이 일치하지 않음 (예: 비밀번호 확인)
public sealed record NotFound : DomainErrorKind;
public sealed record AlreadyExists : DomainErrorKind;
public sealed record Duplicate : DomainErrorKind;
public sealed record Mismatch : DomainErrorKind;
sealed record속성설명
Empty(없음)값이 비어있음 (null, empty string, empty collection 등)
Null(없음)값이 null임
public sealed record Empty : DomainErrorKind;
public sealed record Null : DomainErrorKind;
sealed record속성설명
InvalidFormatstring? Pattern값의 형식이 유효하지 않음. Pattern은 기대되는 형식 패턴
NotUpperCase(없음)값이 대문자가 아님
NotLowerCase(없음)값이 소문자가 아님
public sealed record InvalidFormat(string? Pattern = null) : DomainErrorKind;
public sealed record NotUpperCase : DomainErrorKind;
public sealed record NotLowerCase : DomainErrorKind;
sealed record속성설명
TooShortint MinLength값이 최소 길이보다 짧음. 기본값 0 (미지정)
TooLongint MaxLength값이 최대 길이를 초과함. 기본값 int.MaxValue (미지정)
WrongLengthint Expected값의 길이가 기대와 불일치. 기본값 0 (미지정)
public sealed record TooShort(int MinLength = 0) : DomainErrorKind;
public sealed record TooLong(int MaxLength = int.MaxValue) : DomainErrorKind;
public sealed record WrongLength(int Expected = 0) : DomainErrorKind;
sealed record속성설명
Zero(없음)값이 0임
Negative(없음)값이 음수임
NotPositive(없음)값이 양수가 아님 (0 또는 음수)
OutOfRangestring? Min, string? Max값이 허용 범위를 벗어남
BelowMinimumstring? Minimum값이 최소값보다 작음
AboveMaximumstring? Maximum값이 최대값을 초과함
public sealed record Zero : DomainErrorKind;
public sealed record Negative : DomainErrorKind;
public sealed record NotPositive : DomainErrorKind;
public sealed record OutOfRange(string? Min = null, string? Max = null) : DomainErrorKind;
public sealed record BelowMinimum(string? Minimum = null) : DomainErrorKind;
public sealed record AboveMaximum(string? Maximum = null) : DomainErrorKind;
sealed record속성설명
DefaultDate(없음)날짜가 기본값(DateTime.MinValue)임
NotInPast(없음)날짜가 과거여야 하는데 미래임
NotInFuture(없음)날짜가 미래여야 하는데 과거임
TooLatestring? Boundary날짜가 기준 날짜보다 이후임 (이전이어야 함)
TooEarlystring? Boundary날짜가 기준 날짜보다 이전임 (이후여야 함)
public sealed record DefaultDate : DomainErrorKind;
public sealed record NotInPast : DomainErrorKind;
public sealed record NotInFuture : DomainErrorKind;
public sealed record TooLate(string? Boundary = null) : DomainErrorKind;
public sealed record TooEarly(string? Boundary = null) : DomainErrorKind;
sealed record속성설명
RangeInvertedstring? Min, string? Max범위가 역전됨 (최소값이 최대값보다 큼)
RangeEmptystring? Value범위가 비어있음 (최소값과 최대값이 같음)
public sealed record RangeInverted(string? Min = null, string? Max = null) : DomainErrorKind;
public sealed record RangeEmpty(string? Value = null) : DomainErrorKind;
sealed record속성설명
InvalidTransitionstring? FromState, string? ToState무효한 상태 전이 (예: Paid -> Active)
public sealed record InvalidTransition(string? FromState = null, string? ToState = null) : DomainErrorKind;

FromStateToState로 전이 전후 상태를 기록합니다. 에러 메시지와 별개로 구조화된 데이터로 전이 정보를 보존하여, 로깅/모니터링에서 활용할 수 있습니다.

public abstract record Custom : DomainErrorKind;

Custom 표준 에러 타입으로 표현할 수 없는 도메인 특화 에러의 기반 클래스입니다. 파생 sealed record로 정의하여 사용합니다.

// 엔티티 내부에 nested record로 정의
public sealed record InsufficientStock : DomainErrorKind.Custom;
DomainError.For<Inventory>(new InsufficientStock(), currentStock, "재고가 부족합니다");
// 에러 코드: Domain.Inventory.InsufficientStock

namespace Functorium.Applications.Errors;
public abstract record ApplicationErrorKind : ErrorKind;

ApplicationErrorKind 애플리케이션 레이어 에러의 sealed record 계층 기반입니다.

sealed record속성설명
Empty(없음)값이 비어있음
Null(없음)값이 null임
NotFound(없음)값을 찾을 수 없음
AlreadyExists(없음)값이 이미 존재함
Duplicate(없음)중복된 값
InvalidState(없음)유효하지 않은 상태
Unauthorized(없음)인증되지 않음
Forbidden(없음)접근 금지
public sealed record Empty : ApplicationErrorKind;
public sealed record Null : ApplicationErrorKind;
public sealed record NotFound : ApplicationErrorKind;
public sealed record AlreadyExists : ApplicationErrorKind;
public sealed record Duplicate : ApplicationErrorKind;
public sealed record InvalidState : ApplicationErrorKind;
public sealed record Unauthorized : ApplicationErrorKind;
public sealed record Forbidden : ApplicationErrorKind;
sealed record속성설명
ValidationFailedstring? PropertyName검증 실패. PropertyName은 실패한 속성 이름
public sealed record ValidationFailed(string? PropertyName = null) : ApplicationErrorKind;
sealed record속성설명
BusinessRuleViolatedstring? RuleName비즈니스 규칙 위반. RuleName은 위반된 규칙 이름
ConcurrencyConflict(없음)동시성 충돌
ResourceLockedstring? ResourceName리소스 잠금. ResourceName은 잠긴 리소스 이름
OperationCancelled(없음)작업 취소됨
InsufficientPermissionstring? Permission권한 부족. Permission은 필요한 권한
public sealed record BusinessRuleViolated(string? RuleName = null) : ApplicationErrorKind;
public sealed record ConcurrencyConflict : ApplicationErrorKind;
public sealed record ResourceLocked(string? ResourceName = null) : ApplicationErrorKind;
public sealed record OperationCancelled : ApplicationErrorKind;
public sealed record InsufficientPermission(string? Permission = null) : ApplicationErrorKind;
public abstract record Custom : ApplicationErrorKind;
// 사용 예시
public sealed record CannotProcess : ApplicationErrorKind.Custom;

namespace Functorium.Adapters.Errors;
public abstract record AdapterErrorKind : ErrorKind;

AdapterErrorKind 어댑터 레이어 에러의 sealed record 계층 기반입니다.

sealed record속성설명
Empty(없음)값이 비어있음
Null(없음)값이 null임
NotFound(없음)값을 찾을 수 없음
PartialNotFound(없음)요청한 ID 중 일부를 찾을 수 없음
AlreadyExists(없음)값이 이미 존재함
Duplicate(없음)중복된 값
InvalidState(없음)유효하지 않은 상태
NotConfigured(없음)필수 설정이 누락됨
NotSupported(없음)지원되지 않는 연산
Unauthorized(없음)인증되지 않음
Forbidden(없음)접근 금지
public sealed record Empty : AdapterErrorKind;
public sealed record Null : AdapterErrorKind;
public sealed record NotFound : AdapterErrorKind;
public sealed record PartialNotFound : AdapterErrorKind;
public sealed record AlreadyExists : AdapterErrorKind;
public sealed record Duplicate : AdapterErrorKind;
public sealed record InvalidState : AdapterErrorKind;
public sealed record NotConfigured : AdapterErrorKind;
public sealed record NotSupported : AdapterErrorKind;
public sealed record Unauthorized : AdapterErrorKind;
public sealed record Forbidden : AdapterErrorKind;
sealed record속성설명
PipelineValidationstring? PropertyName파이프라인 검증 실패. PropertyName은 실패한 속성 이름
PipelineException(없음)파이프라인 예외 발생
public sealed record PipelineValidation(string? PropertyName = null) : AdapterErrorKind;
public sealed record PipelineException : AdapterErrorKind;
sealed record속성설명
ExternalServiceUnavailablestring? ServiceName외부 서비스 사용 불가. ServiceName은 서비스 이름
ConnectionFailedstring? Target연결 실패. Target은 연결 대상
TimeoutTimeSpan? Duration타임아웃. Duration은 타임아웃 시간
public sealed record ExternalServiceUnavailable(string? ServiceName = null) : AdapterErrorKind;
public sealed record ConnectionFailed(string? Target = null) : AdapterErrorKind;
public sealed record Timeout(TimeSpan? Duration = null) : AdapterErrorKind;
sealed record속성설명
Serializationstring? Format직렬화 실패. Format은 직렬화 형식
Deserializationstring? Format역직렬화 실패. Format은 역직렬화 형식
DataCorruption(없음)데이터 손상
public sealed record Serialization(string? Format = null) : AdapterErrorKind;
public sealed record Deserialization(string? Format = null) : AdapterErrorKind;
public sealed record DataCorruption : AdapterErrorKind;
sealed record속성설명
ConcurrencyConflict(없음)Update/UpdateRange 시 로드 이후 Aggregate가 다른 주체에 의해 변경됐을 때 반환되는 낙관적 동시성 충돌. NotFound와 구분됩니다. 재시도 가능성 판단은 호출자 책임입니다.
public sealed record ConcurrencyConflict : AdapterErrorKind;

ApplicationErrorKind.ConcurrencyConflict와의 관계: Application 계층 버전은 유스케이스 로직이 판단하는 비즈니스 수준 충돌을, Adapter 계층 버전은 영속성 계층이 직접 감지한 충돌(EF Core RowVersion·DbUpdateConcurrencyException 또는 affected == 0 + ID 존재)을 의미합니다.

public abstract record Custom : AdapterErrorKind;
// 사용 예시
public sealed record RateLimited : AdapterErrorKind.Custom;

namespace Functorium.Abstractions.Errors;
internal static class LayerErrorCore
{
internal static Error Create<TContext>(string prefix, ErrorKind errorType, string currentValue, string message);
internal static Error Create<TContext, TValue>(string prefix, ErrorKind errorType, TValue currentValue, string message)
where TValue : notnull;
internal static Error Create<TContext, T1, T2>(...) where T1 : notnull where T2 : notnull;
internal static Error Create<TContext, T1, T2, T3>(...) where T1 : notnull where T2 : notnull where T3 : notnull;
internal static Error Create(string prefix, Type contextType, ErrorKind errorType, string currentValue, string message);
internal static Error ForContext(string prefix, string contextName, ErrorKind errorType, string currentValue, string message);
internal static Error ForContext<TValue>(string prefix, string contextName, ErrorKind errorType, TValue currentValue, string message)
where TValue : notnull;
internal static Error FromException<TContext>(string prefix, ErrorKind errorType, Exception exception);
}

LayerErrorCore 3개 레이어 팩토리(DomainError, ApplicationError, AdapterError)의 공통 구현입니다. 에러 코드 문자열 {prefix}.{typeof(TContext).Name}.{kind.Name}을 조립하고 ErrorFactory에 위임합니다.

설계 원리: 공개 팩토리는 레이어별 타입 파라미터(DomainErrorKind, ApplicationErrorKind 등)를 유지하여 컴파일 타임 안전성을 보장합니다. LayerErrorCore는 기반 타입 ErrorKind으로 수신하여 구현 중복을 제거합니다. 모든 메서드에 [AggressiveInlining]이 적용되어 JIT이 위임 호출을 인라인 처리합니다.

// 컴파일 타임 안전성 보장 예시
DomainError.For<Email>(new DomainErrorKind.Empty(), ...) // ✅ 컴파일 OK
DomainError.For<Email>(new AdapterErrorKind.Timeout(), ...) // ❌ CS1503
namespace Functorium.Testing.Assertions.Errors;
internal static class ErrorAssertionCore
{
// Error — ErrorCode 검증, 값 검증 (1~3개), Exceptional 검증
internal static void ShouldBeError<TContext>(Error error, string prefix, string errorName);
internal static void ShouldBeError<TContext, TValue>(Error error, string prefix, string errorName, TValue expectedValue);
internal static void ShouldBeExceptionalError<TContext>(Error error, string prefix, string errorName);
// Fin<T> — 실패 상태 + ErrorCode 검증
internal static void ShouldBeFinError<TContext, T>(Fin<T> fin, string prefix, string errorName);
// Validation<Error, T> — 에러 포함/유일/복수 검증
internal static void ShouldHaveError<TContext, T>(Validation<Error, T> validation, string prefix, string errorName);
internal static void ShouldHaveOnlyError<TContext, T>(Validation<Error, T> validation, string prefix, string errorName);
internal static void ShouldHaveErrors<TContext, T>(Validation<Error, T> validation, string prefix, params string[] errorNames);
}

ErrorAssertionCore 3개 레이어 Assertion(DomainErrorAssertions, ApplicationErrorAssertions, AdapterErrorAssertions)의 공통 검증 로직입니다. 에러 코드 조립({prefix}.{typeof(TContext).Name}.{errorName})과 ExpectedError<T> 타입 캐스팅, 값 비교를 제공합니다. 레이어별 Assertion은 prefix와 에러 타입만 바인딩하는 thin wrapper입니다.


namespace Functorium.Domains.Errors;
public static class DomainError
{
public static Error For<TDomain>(DomainErrorKind errorType, string currentValue, string message);
public static Error For<TDomain, TValue>(DomainErrorKind errorType, TValue currentValue, string message)
where TValue : notnull;
public static Error For<TDomain, T1, T2>(DomainErrorKind errorType, T1 value1, T2 value2, string message)
where T1 : notnull where T2 : notnull;
public static Error For<TDomain, T1, T2, T3>(DomainErrorKind errorType, T1 value1, T2 value2, T3 value3, string message)
where T1 : notnull where T2 : notnull where T3 : notnull;
}

에러 코드 형식: Domain.{typeof(TDomain).Name}.{kind.Name}

오버로드값 파라미터설명
For<TDomain>(...)string currentValue기본 문자열 값
For<TDomain, TValue>(...)TValue currentValue제네릭 단일 값
For<TDomain, T1, T2>(...)T1 value1, T2 value2제네릭 2개 값
For<TDomain, T1, T2, T3>(...)T1 value1, T2 value2, T3 value3제네릭 3개 값

사용 예시:

using static Functorium.Domains.Errors.DomainErrorKind;
// 기본 사용
DomainError.For<Email>(new Empty(), "", "이메일은 비어있을 수 없습니다");
// 에러 코드: Domain.Email.Empty
// 속성이 있는 에러 타입
DomainError.For<Password>(new TooShort(MinLength: 8), value, "비밀번호가 너무 짧습니다");
// 에러 코드: Domain.Password.TooShort
// 상태 전이 에러
DomainError.For<Order>(new InvalidTransition(FromState: "Paid", ToState: "Active"), orderId, "유효하지 않은 상태 전이");
// 에러 코드: Domain.Order.InvalidTransition
// 커스텀 에러
DomainError.For<Currency>(new Unsupported(), value, "지원되지 않는 통화입니다");
// 에러 코드: Domain.Currency.Unsupported
namespace Functorium.Applications.Errors;
public static class ApplicationError
{
public static Error For<TUsecase>(ApplicationErrorKind errorType, string currentValue, string message);
public static Error For<TUsecase, TValue>(ApplicationErrorKind errorType, TValue currentValue, string message)
where TValue : notnull;
public static Error For<TUsecase, T1, T2>(ApplicationErrorKind errorType, T1 value1, T2 value2, string message)
where T1 : notnull where T2 : notnull;
public static Error For<TUsecase, T1, T2, T3>(ApplicationErrorKind errorType, T1 value1, T2 value2, T3 value3, string message)
where T1 : notnull where T2 : notnull where T3 : notnull;
}

에러 코드 형식: Application.{typeof(TUsecase).Name}.{kind.Name}

오버로드값 파라미터설명
For<TUsecase>(...)string currentValue기본 문자열 값
For<TUsecase, TValue>(...)TValue currentValue제네릭 단일 값
For<TUsecase, T1, T2>(...)T1 value1, T2 value2제네릭 2개 값
For<TUsecase, T1, T2, T3>(...)T1 value1, T2 value2, T3 value3제네릭 3개 값

사용 예시:

Application.CreateProductCommand.AlreadyExists
using static Functorium.Applications.Errors.ApplicationErrorKind;
ApplicationError.For<CreateProductCommand>(new AlreadyExists(), productId, "이미 존재합니다");
ApplicationError.For<UpdateOrderCommand>(new ValidationFailed("Quantity"), value, "수량은 양수여야 합니다");
// 에러 코드: Application.UpdateOrderCommand.ValidationFailed
namespace Functorium.Adapters.Errors;
public static class AdapterError
{
public static Error For<TAdapter>(AdapterErrorKind errorType, string currentValue, string message);
public static Error For(Type adapterType, AdapterErrorKind errorType, string currentValue, string message);
public static Error For<TAdapter, TValue>(AdapterErrorKind errorType, TValue currentValue, string message)
where TValue : notnull;
public static Error For<TAdapter, T1, T2>(AdapterErrorKind errorType, T1 value1, T2 value2, string message)
where T1 : notnull where T2 : notnull;
public static Error For<TAdapter, T1, T2, T3>(AdapterErrorKind errorType, T1 value1, T2 value2, T3 value3, string message)
where T1 : notnull where T2 : notnull where T3 : notnull;
public static Error FromException<TAdapter>(AdapterErrorKind errorType, Exception exception);
}

에러 코드 형식: Adapter.{typeof(TAdapter).Name}.{kind.Name}

오버로드값 파라미터설명
For<TAdapter>(...)string currentValue기본 문자열 값
For(Type, ...)string currentValue런타임 Type으로 어댑터 지정 (베이스 클래스에서 GetType() 사용 시)
For<TAdapter, TValue>(...)TValue currentValue제네릭 단일 값
For<TAdapter, T1, T2>(...)T1 value1, T2 value2제네릭 2개 값
For<TAdapter, T1, T2, T3>(...)T1 value1, T2 value2, T3 value3제네릭 3개 값
FromException<TAdapter>(...)Exception exceptionException을 Exceptional 에러로 래핑

사용 예시:

using static Functorium.Adapters.Errors.AdapterErrorKind;
// Expected 에러
AdapterError.For<ProductRepository>(new NotFound(), id, "제품을 찾을 수 없습니다");
// 에러 코드: Adapter.ProductRepository.NotFound
// Pipeline 에러
AdapterError.For<UsecaseValidationPipeline>(new PipelineValidation("PropertyName"), value, "검증에 실패했습니다");
// 에러 코드: Adapter.UsecaseValidationPipeline.PipelineValidation
// Exception 래핑
AdapterError.FromException<UsecaseExceptionPipeline>(new PipelineException(), exception);
// 에러 코드: Adapter.UsecaseExceptionPipeline.PipelineException (Exceptional)
// 런타임 Type 사용
AdapterError.For(GetType(), new ConnectionFailed("DB"), connectionString, "연결에 실패했습니다");
// 에러 코드: Adapter.{실제타입이름}.ConnectionFailed

문서설명
에러 시스템: 기초와 네이밍에러 처리 원칙, Fin 패턴, 네이밍 규칙 R1~R8
에러 시스템: Domain/Application/EventDomain, Application, Event 에러 상세 가이드
에러 시스템: Adapter와 테스트Adapter 에러와 테스트 패턴 가이드
검증 시스템 사양TypedValidation, ContextualValidation 사양