본문으로 건너뛰기

구조 개요

Functorium Architecture

Functorium은 Domain, Application, Adapter의 3계층 아키텍처를 기반으로 합니다. 이 구조는 비즈니스 로직을 인프라 기술로부터 격리하여, 도메인 모델의 순수성과 테스트 용이성을 극대화하는 것을 목표로 합니다. 모든 의존성은 바깥에서 안쪽으로 흐르며, 도메인 계층은 어떤 외부 기술에도 의존하지 않습니다.

Domain Layer — 순수 비즈니스 로직

섹션 제목: “Domain Layer — 순수 비즈니스 로직”

도메인 계층은 시스템의 핵심입니다. 외부 의존성 없이 순수 함수 기반으로 비즈니스 규칙을 표현하며, 프레임워크나 데이터베이스 같은 인프라 기술에 대한 참조가 전혀 없습니다.

Value Object는 항상 유효한 상태를 보장합니다. 생성 시점에 유효성 검증이 완료되므로, 이후 코드에서는 별도의 검증 없이 안전하게 사용할 수 있습니다. Entity는 Ulid 기반 ID를 사용하여 고유성을 보장하고, AggregateRoot는 도메인 이벤트를 수집하여 트랜잭션 경계를 명확히 정의합니다. Specification 패턴은 비즈니스 규칙을 재사용 가능한 단위로 캡슐화합니다.

순수 함수로 구성된 도메인 로직은 Mock 없이 단위 테스트가 가능하고, LINQ를 통해 자연스럽게 조합됩니다. 이러한 순수성이 전체 아키텍처의 테스트 용이성과 변경 유연성의 기반이 됩니다.

  • 디렉터리Domains/
    • 디렉터리Entities/ Entity, AggregateRoot, IEntityId
    • 디렉터리ValueObjects/ AbstractValueObject, SimpleValueObject, ComparableSimpleValueObject, UnionValueObject, Validations
    • 디렉터리Errors/ DomainError, DomainErrorType (27 sealed records)
    • 디렉터리Events/ IDomainEvent, DomainEvent
    • 디렉터리Repositories/ IRepository (Command 포트 정의)
    • 디렉터리Specifications/ Specification, ExpressionSpecification
    • 디렉터리Services/ IDomainService
    • 디렉터리Observabilities/ IObservablePort

Application Layer — 유스케이스 조립

섹션 제목: “Application Layer — 유스케이스 조립”

애플리케이션 계층은 도메인 객체를 조립하여 유스케이스를 구현하고, 사이드 이펙트의 경계를 명시적으로 관리합니다. 도메인 계층에만 의존하며, 인프라 접근이 필요한 경우 포트(인터페이스)를 정의하여 Adapter 계층이 구현하도록 합니다.

ICommandRequest와 IQueryRequest는 CQRS 패턴의 진입점입니다. 모든 유스케이스의 결과는 FinResponse<T>로 통일되어, 성공과 실패를 명시적으로 표현합니다. FinT<IO, T> LINQ 확장은 사이드 이펙트가 포함된 연산을 순수 함수처럼 조합할 수 있게 하여, 복잡한 유스케이스도 선언적으로 작성할 수 있습니다.

Command 경로는 IRepository<T, TId>(Domain 계층 정의)를 통해 Aggregate Root를 영속화하고, Query 경로는 IQueryPort(Application 계층 정의)를 통해 Dapper 기반 DTO 직접 프로젝션을 수행합니다. 포트가 정의되는 계층이 다르므로, 읽기와 쓰기의 관심사가 구조적으로 분리됩니다.

이 계층은 “무엇을 해야 하는가”를 정의하지만, “어떻게 수행하는가”는 관여하지 않습니다. 데이터베이스 접근, 외부 API 호출 같은 구체적인 구현은 모두 Adapter 계층에 위임됩니다.

  • 디렉터리Applications/
    • 디렉터리Cqrs/ ICommandRequest, IQueryRequest, FinResponse
    • 디렉터리Events/ IDomainEventHandler, IDomainEventPublisher
    • 디렉터리Errors/ ApplicationError, EventError
    • 디렉터리Validations/ FluentValidation 확장
    • 디렉터리Linq/ FinT LINQ 확장
    • 디렉터리Queries/ IQueryPort, PagedResult, SortExpression, CursorPagination
    • 디렉터리Persistence/ IUnitOfWork

Adapter 계층은 Application 계층이 정의한 포트를 구현하며, 데이터 저장소 연동, 외부 시스템 통합, 관측성(Observability) 구현을 담당합니다. Domain과 Application 계층에 의존하지만, 반대 방향의 의존은 존재하지 않습니다.

Pipeline 행위(Behavior)는 모든 유스케이스에 자동으로 적용되어 로깅, 메트릭, 트레이싱을 계측합니다. 유스케이스 코드에 관측성 코드가 침투하지 않으므로, 비즈니스 로직의 가독성이 유지됩니다. DapperQueryAdapterBase는 읽기 전용 쿼리를 위한 경량 데이터 접근 기반을 제공하고, Source Generator는 관측성 포트 구현 같은 반복적인 코드를 컴파일 시점에 자동 생성합니다.

인프라 기술이 교체되더라도 이 계층만 수정하면 됩니다. Domain과 Application 계층은 영향을 받지 않습니다.

  • 디렉터리Adapters/
    • 디렉터리Observabilities/
      • 디렉터리Builders/ OpenTelemetry 구성
      • 디렉터리Pipelines/ Usecase Pipeline (Logging, Metrics, Tracing, …)
      • 디렉터리Events/ Observable 도메인 이벤트 발행
      • 디렉터리Loggers/ 구조화된 로거 확장
      • 디렉터리Naming/ Observability 네이밍 규칙
    • 디렉터리Events/ DomainEventCollector, DomainEventPublisher
    • 디렉터리Repositories/ DapperQueryAdapterBase
    • 디렉터리Errors/ AdapterError
    • 디렉터리Options/ 옵션 설정
    • 디렉터리SourceGenerators/
      • [GenerateObservablePort] Observable wrapper
      • [GenerateEntityId] Ulid ID + ValueConverter
      • CtxEnricherGenerator ctx.* 필드 자동 생성
      • DomainEventCtxEnricherGenerator 이벤트 컨텍스트
      • [UnionType] Discriminated Union 패턴 매칭

3계층 구조에서 의존성이 항상 안쪽을 향하는 원칙은 다음과 같은 실질적인 이점을 제공합니다.

테스트 용이성을 확보합니다. Domain과 Application 계층은 인프라 없이 독립적으로 테스트할 수 있습니다. 데이터베이스나 외부 서비스 없이도 핵심 비즈니스 로직을 빠르게 검증할 수 있습니다.

기술 유연성을 보장합니다. ORM을 EF Core에서 다른 기술로 교체하거나, 메시지 브로커를 변경하더라도 Domain과 Application 계층의 코드는 수정할 필요가 없습니다. 변경은 Adapter 계층에 국한됩니다.

병렬 개발이 가능합니다. 계층 간 인터페이스가 명확하게 정의되어 있으므로, 각 계층을 담당하는 팀이 독립적으로 작업할 수 있습니다.

품질은 테스트 단계에서 확보하는 것이 아니라, 아키텍처 구조 자체에 내재화됩니다.

  • 핵심 도메인 로직은 높은 수준의 단위 테스트 커버리지를 유지합니다. 외부 의존성이 없으므로 Mock 없이 빠르게 검증할 수 있습니다.
  • 사이드 이펙트 영역은 FinT<IO, T>로 명시적으로 분리되어, 검증 가능한 구조를 갖습니다.
  • 배포 이전 단계에서 Observability 검증이 완료됩니다. Pipeline이 모든 유스케이스에 자동으로 계측을 적용하기 때문입니다.
  • 아키텍처 규칙은 ClassValidator/InterfaceValidator를 통해 단위 테스트로 자동 검증됩니다. 리뷰어 없이도 설계 원칙이 유지됩니다.

이러한 구조적 접근을 통해 개발과 운영이 동일한 도메인 모델 기반으로 협업하고, 변경 영향 범위가 예측 가능해지며, 반복적인 장애 대응과 유지보수 비용이 구조적으로 감소합니다.


Functorium의 각 계층을 구성하는 핵심 기능은 주요 핵심 기능에서, 실제 프로젝트 적용은 시작하기에서 확인할 수 있습니다.