본문으로 건너뛰기

주요 핵심 기능

타입 안전한 함수형 도메인 모델링

섹션 제목: “타입 안전한 함수형 도메인 모델링”

도메인 객체가 유효하지 않은 상태로 생성될 수 있다면, 비즈니스 규칙 위반은 런타임 예외로만 드러납니다. 검증 로직이 호출부마다 흩어지고, 어디서 유효성을 확인했는지 추적하기 어려워집니다. 시간이 지나면 검증 누락이 버그로 이어지고, 방어적 코드가 곳곳에 중복됩니다. Functorium은 도메인 객체의 유효성을 생성 시점에 강제하는 함수형 팩토리 패턴으로 이 문제를 해결합니다.

Value Object 계층은 6개 기본 타입과 Union 확장을 제공합니다. Class 기반(AbstractValueObjectValueObject / SimpleValueObject<T> / ComparableValueObject / ComparableSimpleValueObject<T>)은 값 기반 동등성과 Always-valid 팩토리를, Record 기반(UnionValueObject<TSelf>)은 상태 머신과 Discriminated Union을 지원합니다. 이 계층 구조가 생성자를 직접 노출하지 않고 팩토리 메서드를 통해서만 인스턴스를 만들 수 있으므로, 유효성 검증을 우회하는 경로가 원천적으로 차단됩니다. Entity와 AggregateRoot는 Ulid 기반 타입 안전한 ID(IEntityId<T>)로 식별되며, AggregateRoot에서 도메인 이벤트를 수집하고 발행합니다.

이 계층 구조 위에서 FinResponse<T> Discriminated Union이 모든 유스케이스의 결과를 명시적으로 표현합니다. sealed record 기반의 Success/Failure 구분은 예외에 의존하지 않는 흐름 제어를 가능하게 합니다. 호출자는 반환 타입만 보고도 성공과 실패 경로를 모두 처리해야 한다는 사실을 인지할 수 있으며, LINQ 합성을 통해 여러 연산을 자연스럽게 조립할 수 있습니다.

사이드 이펙트 경계는 FinT<IO, T> Monad Transformer가 타입 수준에서 추적합니다. Repository 반환 타입에 FinT<IO, T>를 사용하면 순수 함수와 비순수 함수의 경계가 시그니처에 명시되므로, 호출자는 사이드 이펙트의 존재 여부를 컴파일 타임에 인지할 수 있습니다. 이를 통해 도메인 로직의 순수한 부분과 인프라에 의존하는 부분이 타입으로 구분됩니다.

IO 모나드는 Timeout, Retry(지수 백오프), Fork(병렬 실행), Bracket(리소스 생명주기 관리) 등 고급 기능을 제공합니다. 외부 서비스 호출의 장애 내성(fault tolerance)을 타입 안전하게 구성할 수 있어, 수동 try-catch 없이도 안정적인 인프라 통합이 가능합니다.

에러 처리 역시 구조화되어 있습니다. DomainErrors.Email.Empty와 같은 형태의 에러 코드가 8가지 명명 규칙(R1-R8)에 따라 자동 생성되며, 계층별로 DomainError, ApplicationError, AdapterError가 명확히 분리됩니다. Specification Pattern은 &/|/! 연산자 합성과 Expression Tree 기반 SQL 변환을 지원하고, PropertyMap 브릿지를 통해 도메인 속성과 영속성 컬럼 간의 매핑을 처리합니다. 함수형 검증에서는 순차 검증(Bind)과 병렬 검증(Apply)을 조합하여, 첫 번째 실패에서 멈추거나 모든 오류를 한꺼번에 수집하는 전략을 선택적으로 적용할 수 있습니다.

이 설계의 실제 효과는 컴파일 타임에 더 많은 오류를 잡아낸다는 점입니다. 유효하지 않은 객체가 존재할 수 없으므로 방어적 검증 코드가 줄어들고, LINQ 합성으로 도메인 흐름을 선언적으로 조립할 수 있습니다. 결과적으로 도메인 로직의 정확성과 합성 가능성을 동시에 확보할 수 있습니다.

Domain 계층과 Adapter 계층 사이에는 반복적인 브릿지 코드가 필요합니다. 포트 구현체마다 Observable wrapper를 작성하고, 도메인 ID 타입마다 EF Core 변환기를 만드는 작업은 단순하지만 실수가 잦고 유지보수 부담이 큽니다. 새로운 포트가 추가될 때마다 동일한 패턴의 코드를 반복 작성해야 하며, 원본 인터페이스가 변경되면 wrapper도 함께 수정해야 합니다. Functorium의 5개 Source Generator는 이 브릿지 코드를 컴파일 시점에 자동 생성하여 보일러플레이트를 제거합니다.

[GenerateObservablePort] 어트리뷰트를 Adapter 포트 구현체에 선언하면, OpenTelemetry 기반의 Tracing, Logging, Metrics를 포함하는 Observable wrapper가 자동 생성됩니다. 원본 구현체를 수정하지 않고도 모든 포트 호출에 관측성이 부여되므로, 관측성 코드와 비즈니스 로직이 분리된 상태를 유지할 수 있습니다. 생성된 wrapper는 메서드 진입/종료 시점의 트레이싱, 실행 시간 메트릭, 구조화된 로깅을 자동으로 처리합니다.

[GenerateEntityId] 어트리뷰트는 Ulid 기반 EntityId 타입과 함께 EF Core의 ValueConverter/ValueComparer를 자동 생성합니다. 도메인 계층의 강타입 ID가 영속성 계층에서 변환 코드 없이 그대로 사용되므로, Domain과 Adapter 사이의 영속성 브릿지가 어트리뷰트 선언만으로 완성됩니다. 타입 안전한 ID를 유지하면서도 데이터베이스 저장과 조회에 필요한 변환 로직을 직접 구현할 필요가 없습니다.

CtxEnricherGenerator는 Command/Query Request 속성을 ctx.{snake_case} 형식의 구조화된 필드로 자동 변환하여, Logging과 Tracing에 비즈니스 컨텍스트를 전파합니다. DomainEventCtxEnricherGenerator는 도메인 이벤트 핸들러에 동일한 컨텍스트 enrichment를 적용합니다. [UnionType] 어트리뷰트는 Discriminated Union에 exhaustive Match<T>Switch 메서드를 생성하여, 패턴 매칭 누락을 컴파일 타임에 방지합니다.

결과적으로 개발자는 계층 간 연결에 필요한 반복 코드를 직접 작성하지 않으면서도, 타입 안전성과 관측성을 확보할 수 있습니다. 인터페이스가 변경되면 Source Generator가 wrapper를 자동으로 재생성하므로, 수동 동기화로 인한 불일치 문제도 발생하지 않습니다.

아키텍처 규칙이 문서에만 존재하면, 시간이 지날수록 코드와의 괴리가 발생합니다. 새로운 팀원이 합류하거나 일정이 촉박해지면 규칙은 쉽게 무시되고, 기술 부채가 누적됩니다. 코드 리뷰에서 아키텍처 위반을 잡아내려 해도, 리뷰어의 주의력과 경험에 의존하게 됩니다. Functorium은 아키텍처 규칙을 타입 시스템과 자동화된 테스트로 코드에 내재화하여 이 문제를 해결합니다.

모든 유스케이스는 횡단 관심사 Pipeline을 거칩니다. Command는 Metrics → Tracing → Logging → Validation → Exception → Transaction → Custom, Query는 Metrics → Tracing → Logging → Validation → Caching → Exception → Custom 순서로 자동 적용됩니다. Caching은 Query 전용, Transaction은 Command 전용이며, Custom은 프로젝트별 확장 지점입니다. 유스케이스 개발자는 비즈니스 로직에만 집중하면 되고, 관측성과 트랜잭션 관리는 Pipeline이 일관되게 보장합니다. Pipeline 제약은 IFinResponse 인터페이스 계층과 CRTP 팩토리가 타입 수준에서 강제하며, 리플렉션 없이 Pipeline별 최소 제약 조건을 보장합니다.

읽기와 쓰기 경로는 CQRS 패턴으로 분리됩니다. Command 경로는 ICommandRequest<T>IRepository, EF Core를 사용하여 도메인 모델의 상태 변경을 처리하고, Query 경로는 IQueryRequest<T>IQueryPort, Dapper를 사용하여 읽기에 최적화된 경로로 데이터를 조회합니다. Cursor 페이지네이션을 기본 지원하여 대량 데이터 조회에서도 일관된 성능을 유지합니다.

마지막으로, 아키텍처 규칙 테스트가 설계 의도를 코드 수준에서 검증합니다. ClassValidator, InterfaceValidator, MethodValidator가 불변성, 가시성, 상속 규칙을 단위 테스트로 강제하므로, CI 파이프라인에서 규칙 위반이 자동으로 감지됩니다. 예를 들어, Value Object가 sealed가 아니거나, Entity 생성자가 public으로 노출되면 테스트가 실패합니다. 리뷰어가 매번 확인하지 않아도 설계 원칙이 지속적으로 보존됩니다.

영역핵심 타입/도구역할
도메인 모델링SimpleValueObject<T>, UnionValueObject<TSelf>, Fin<T>타입 안전한 불변식 보장
IO 고급 기능Timeout, Retry, Fork, Bracket장애 내성 + 리소스 관리
에러 시스템DomainError, ApplicationError, AdapterError구조화된 에러 코드 자동 생성
CQRSICommandRequest<T>, IQueryRequest<T>, FinResponse<T>읽기/쓰기 경로 분리
코드 생성5개 Source Generator컴파일 시점 브릿지 코드 생성
품질 자동화ClassValidator, InterfaceValidator아키텍처 규칙 단위 테스트
PipelineCommand 7단계, Query 8단계횡단 관심사 자동 적용