웹서비스 내부 구조 아키텍처 가이드 Part 1: Layered, Ports & Adapters, CQRS
이 글은 Claude Opus 4.6 을 이용해 초안이 작성되었으며, 이후 퇴고를 거쳤습니다.
이 글은 웹서비스를 전제로 합니다. 구체적으로는 HTML/CSS/JS 프런트엔드, REST/gRPC 기반 백엔드, RDB/Redis/NoSQL 저장소, Kafka/RabbitMQ 같은 메시지 브로커가 있는 환경을 상정합니다.
비교 대상은 “배포 아키텍처"가 아닙니다. 마이크로서비스냐 모놀리스냐의 문제가 아니라, 애플리케이션 내부 코드를 어떻게 나누고, 의존성을 어느 방향으로 흐르게 하며, 변경을 어떤 단위로 흡수할 것인가 에 관한 내부 구조 아키텍처입니다. Microsoft도 논리적 계층(layer)과 물리적 계층(tier)을 구분하며, 논리적 계층 분리가 반드시 별도 서버 분리를 의미하지는 않는다고 설명합니다.
이 시리즈에서 다루는 아키텍처는 다음 여섯 가지입니다.
- Layered Architecture
- Ports & Adapters 계열 (Hexagonal / Onion / Clean Architecture)
- CQRS (Command Query Responsibility Segregation)
- Event-Driven Architecture
- Vertical Slice Architecture
- Modular Monolith
Part 1에서는 1~3번을, Part 2에서는 4~6번과 비교·선택 기준을 다룹니다.
1. Layered Architecture#
개요#
Layered Architecture는 가장 오래되고 가장 널리 쓰이는 내부 구조 패턴입니다. 코드를 책임별 계층(layer)으로 나누고, 각 계층이 바로 아래 계층에만 의존하도록 만드는 것이 기본 원칙입니다. Microsoft Azure 문서는 이를 “코드를 논리적 계층으로 조직하는 가장 전통적인 방법"으로 설명합니다.
가장 단순한 형태는 3계층입니다.
Presentation → Business Logic → Data Access
DDD(Domain-Driven Design)를 적용하면 보통 4계층으로 확장됩니다. Evans는 도메인 코드를 UI, Application, Infrastructure로부터 분리해 모델을 선명하게 유지해야 한다고 강조했습니다.
Presentation (UI)
- Controller, gRPC handler, ViewModel, 인증 진입점
Application
- 유스케이스 조율, 트랜잭션 경계, 권한 검사, DTO 변환
Domain
- Entity, Aggregate, Value Object, Domain Service, Domain Event
Infrastructure
- Repository 구현, ORM/SQL, Redis 어댑터, Kafka producer/consumer, 외부 API 클라이언트
graph TD
P["<b>Presentation</b><br/>Controller, gRPC Handler, ViewModel"]
App["<b>Application</b><br/>Use Case 조율, 트랜잭션, DTO"]
D["<b>Domain</b><br/>Entity, Aggregate, Value Object"]
Infra["<b>Infrastructure</b><br/>Repository 구현, ORM, Redis, Kafka"]
P --> App
App --> D
App -.->|"interface 호출"| Infra
Infra -.->|"구현 제공"| D
style P fill:#87CEEB,color:#000000
style App fill:#90EE90,color:#000000
style D fill:#FFD700,color:#000000
style Infra fill:#FFB6C1,color:#000000
점선 화살표는 DIP(의존성 역전)가 적용된 부분입니다. Application이 Infrastructure의 구체 구현이 아니라 Domain에 정의된 인터페이스를 호출하고, Infrastructure가 그 인터페이스를 구현합니다.
의존성 방향과 closed/open layer#
전통적 Layered Architecture에서 의존성은 위에서 아래로 흐릅니다. Closed layer 방식에서는 각 계층이 바로 아래 계층만 호출할 수 있고, open layer 방식에서는 여러 계층을 건너뛸 수 있습니다. Closed layer는 결합을 줄이는 대신 단순한 위임(pass-through) 메서드가 늘어나고, open layer는 유연하지만 계층 간 결합이 늘어납니다.
DDD식 4계층에서는 한 가지 중요한 변형이 있습니다. Domain 계층이 Infrastructure에 직접 의존하지 않도록, Domain에 인터페이스(Repository interface 등)를 정의하고 Infrastructure가 이를 구현하게 만드는 것입니다. 이렇게 하면 의존성 역전 원칙(DIP)이 적용되어, 가장 중요한 비즈니스 로직이 DB나 메시지 큐 같은 기술 세부사항에 끌려다니지 않게 됩니다.
장점#
이해 비용이 낮습니다. 대부분의 개발자가 이미 UI/BLL/DAL 같은 모델에 익숙합니다. 초기 온보딩과 코드 탐색이 쉽고, 팀 합의도 빠릅니다. 관리자 페이지, 내부 업무 시스템, 전통적 CRUD 웹앱에서는 이 익숙함 자체가 큰 생산성이 됩니다.
책임 경계가 명시적입니다. “이 코드는 어느 계층에 있어야 하는가?“라는 질문에 대체로 명확한 답이 있습니다. 코드 리뷰에서도 계층 위반을 발견하기 쉽습니다.
기존 시스템과의 호환성이 좋습니다. 레거시 온프레미스 시스템을 클라우드로 이전하거나, 기존 enterprise 프레임워크를 활용할 때 자연스러운 출발점이 됩니다.
단점과 흔한 실패 모드#
컴파일타임 의존성이 아래로 흐르기 쉽습니다. DIP를 적용하지 않으면 Business Logic이 Data Access의 구체 구현에 의존하게 되고, 테스트가 어려워집니다. Microsoft는 이 점을 전통적 layered 구조의 대표적 약점으로 지적합니다.
도메인 빈혈(Anemic Domain Model)이 발생하기 쉽습니다. DDD식 4계층이라는 이름을 붙였지만, 실제로는 Entity가 getter/setter 묶음이고 모든 규칙이 Application Service에 몰리는 경우가 매우 흔합니다. Fowler는 이를 Anemic Domain Model 안티패턴이라 부르며, “도메인 객체에 행동이 거의 없는 것"이 문제의 본질이라고 설명합니다.
Repository를 테이블 단위 DAO로 오해하는 경우가 많습니다. DDD에서 Repository는 Aggregate Root 단위로 두어야 합니다. 테이블마다 Repository를 만들면 Aggregate의 일관성 경계가 흐려집니다.
계층이 많아질수록 pass-through 코드가 늘어납니다. Controller → Service → Repository 사이에 실질적인 로직 없이 위임만 하는 메서드가 쌓이면, 계층 분리의 비용이 이점을 초과하기 시작합니다.
적합한 상황#
- 업무 흐름은 많지만 도메인 난도가 중간 이하인 시스템
- 팀이 전통적 enterprise layering에 익숙한 조직
- 기존 레거시 시스템을 점진적으로 이전하는 경우
- 규정 준수상 계층 책임을 문서화해야 하는 환경
부적합한 상황#
- 유스케이스마다 데이터 접근 방식이 크게 다른 시스템
- 읽기/쓰기 비대칭이 극단적인 서비스
- 프레임워크/DB 독립성이 매우 중요한 경우
실무 팁#
완벽한 4계층 교리를 강요하기보다, 다음 네 가지만 지켜도 건강한 Layered Architecture가 됩니다.
- 비즈니스 규칙은 Domain 안에 둔다
- Application 계층은 얇게 유지한다
- Repository는 Aggregate 단위로 둔다
- 읽기 경로를 과도하게 교리화하지 않는다 (읽기 최적화가 필요하면 별도 경로를 허용한다)
Layered Architecture의 문제는 대개 계층 자체보다 “계층을 기계적으로 적용하는 것"에서 발생합니다.
2. Ports & Adapters 계열#
Hexagonal, Onion, Clean Architecture — 하나의 원칙, 세 가지 표현#
Microsoft는 DIP(의존성 역전 원칙)와 DDD를 따라가면 역사적으로 Hexagonal → Ports and Adapters → Onion → Clean Architecture로 불려온 매우 유사한 구조에 도달한다고 설명합니다. 이 셋은 적대적 대안이 아니라, 같은 핵심 원칙을 서로 다른 은유로 표현한 것에 가깝습니다.
공통 원칙은 세 가지입니다.
- 도메인(비즈니스 규칙)이 중심에 위치한다 — UI, DB, 프레임워크 같은 바깥 요소가 안쪽에 의존하지, 안쪽이 바깥에 의존하지 않는다.
- 인프라는 외부에 위치한다 — DB는 시스템의 중심이 아니라 교체 가능한 외부 서비스이다.
- 경계는 인터페이스(포트/계약)로 정의한다 — 안쪽은 추상에 의존하고, 바깥쪽이 구체 구현을 제공한다.
이 원칙만 이해하면 세 가지 변종의 차이는 강조점의 차이일 뿐입니다.
graph LR
subgraph "Driving Adapters (입력)"
REST["REST Controller"]
GRPC["gRPC Handler"]
MSG_IN["Message Consumer"]
end
subgraph "Application Core"
IP["Input Ports"]
UC["Use Cases<br/>Domain Model"]
OP["Output Ports"]
IP --> UC --> OP
end
subgraph "Driven Adapters (출력)"
DB["DB Repository"]
CACHE["Redis Cache"]
MQ["Kafka Publisher"]
end
REST --> IP
GRPC --> IP
MSG_IN --> IP
OP --> DB
OP --> CACHE
OP --> MQ
style UC fill:#FFD700,color:#000000
style IP fill:#90EE90,color:#000000
style OP fill:#90EE90,color:#000000
style REST fill:#87CEEB,color:#000000
style GRPC fill:#87CEEB,color:#000000
style MSG_IN fill:#87CEEB,color:#000000
style DB fill:#FFB6C1,color:#000000
style CACHE fill:#FFB6C1,color:#000000
style MQ fill:#FFB6C1,color:#000000
중앙의 Application Core(도메인 + 유스케이스)가 Port를 통해 바깥과 통신하고, 각 Adapter가 Port의 구체 구현을 제공합니다. 왼쪽 Driving Adapter는 외부에서 애플리케이션을 호출하는 쪽이고, 오른쪽 Driven Adapter는 애플리케이션이 외부를 호출하는 쪽입니다.
Hexagonal Architecture (Ports and Adapters)#
Alistair Cockburn이 제안한 이 구조의 핵심은 inside-outside 비대칭 입니다. 문제의 본질은 “왼쪽은 UI, 오른쪽은 DB"가 아니라, 안쪽의 애플리케이션 핵심과 바깥의 외부 세계가 뒤엉키는 것 입니다.
- Port: 애플리케이션이 외부와 나누는 “목적 있는 대화"를 나타내는 인터페이스입니다.
OrderCreationPort,PaymentGatewayPort같은 비즈니스 목적 중심으로 정의합니다. - Adapter: 각 Port에 맞춰 구체적인 외부 기술을 연결하는 구현체입니다. 한 Port에 REST adapter, gRPC adapter, 테스트 stub 등 여러 어댑터가 붙을 수 있습니다.
입력 어댑터(driving): REST Controller, gRPC handler, 메시지 소비자, 배치 작업, CLI 출력 어댑터(driven): RDB Repository 구현, Redis 캐시, Kafka publisher, 외부 결제 API 클라이언트
Hexagonal이 특히 빛나는 상황은 입출력 채널이 다양한 시스템 입니다. 같은 주문 생성 유스케이스를 HTTP API, 내부 gRPC, 배치 작업, 메시지 소비자에서 동시에 호출해야 한다면, 이들을 “같은 입력 포트에 붙는 서로 다른 어댑터"로 모델링할 수 있습니다.
주의점: 많은 팀이 이를 “Controller 뒤에 Service, Repository 인터페이스 두기” 정도로 이해합니다. 하지만 Cockburn의 핵심은 계층 수가 아니라 Port의 목적성입니다. SqlOrderPort, KafkaOrderPort 같은 기술 기준 이름이 나오기 시작하면 본래 의도에서 벗어난 것입니다.
Onion Architecture#
Jeffrey Palermo가 제안한 이 구조는 동심원(concentric circles) 은유를 사용합니다.
- 가장 안쪽: Domain Model (Entity, Value Object, Domain Service)
- 중간 링: Application Service, Repository interface 같은 계약
- 가장 바깥: UI, Infrastructure, Tests
핵심 메시지는 “모든 결합은 중심을 향한다"입니다. Palermo는 DB를 시스템의 중심이 아니라 외부 저장 서비스로 보아야 한다고 강조합니다. 이 관점은 “테이블 구조가 곧 도메인 모델"이라는 사고를 깨뜨리는 데 효과적입니다.
Onion의 정신 모델은 매우 강력합니다. “우리 시스템의 진짜 핵심이 무엇인가?“를 팀이 계속 의식하게 만들기 때문입니다. 금융, 주문/정산, 예약, 정책 엔진처럼 규칙과 상태 전이가 복잡한 시스템에서 특히 효과적입니다.
다만 Palermo 자신도 이 구조가 작은 웹사이트보다는 오래 살아남아야 하는 복잡한 비즈니스 애플리케이션 에 적합하다고 말합니다.
Clean Architecture#
Robert C. Martin(Uncle Bob)이 제안한 이 구조는 Dependency Rule 을 가장 전면에 내세웁니다.
- Entities: 가장 일반적이고 안정적인 비즈니스 규칙
- Use Cases: 애플리케이션 특화 비즈니스 규칙
- Interface Adapters: Controller, Presenter, Gateway, Repository 구현
- Frameworks & Drivers: 웹 프레임워크, DB, 메시지 브로커
규칙은 단순합니다. 소스코드 의존성은 오직 안쪽으로만 향해야 하며, 안쪽 원은 바깥 원의 어떤 이름도 알면 안 됩니다. 경계를 넘는 데이터는 가능한 단순한 구조여야 하며, ORM 엔티티나 프레임워크 객체를 안쪽으로 들이밀면 안 됩니다.
Clean Architecture의 최대 강점은 위반을 발견하기 쉽다 는 것입니다. Use Case 안에서 EF Core 엔티티를 직접 다루거나, Controller에서 DbContext를 직접 호출하면 어디서 규칙을 어겼는지 바로 보입니다. 팀 교육과 코드 리뷰에서 이 명확함은 큰 이점입니다.
세 변종의 차이 요약#
| 관점 | Hexagonal | Onion | Clean |
|---|---|---|---|
| 핵심 은유 | 육각형, 포트와 어댑터 | 동심원, 양파 껍질 | 동심원, 의존성 규칙 |
| 강조점 | 안팎의 비대칭, 입출력 대칭성 | 도메인 중심성, 결합 방향 | 의존성 규칙, 경계 통과 규칙 |
| 잘 설명하는 것 | “바깥과 어떻게 연결하는가” | “코어가 어디인가” | “어디까지가 정책이고 어디부터가 세부사항인가” |
| 실무 적합도 | 입출력 채널이 다양한 시스템 | 도메인 규칙이 핵심 자산인 시스템 | 의존성 거버넌스가 중요한 팀 |
계열 전체의 장점#
프레임워크/DB 독립성이 높습니다. 핵심 비즈니스 로직이 특정 프레임워크나 DB에 묶이지 않으므로, 기술 교체 비용이 줄어듭니다.
테스트가 쉬워집니다. 도메인과 유스케이스를 인프라 없이 단위 테스트할 수 있습니다. Cockburn은 GUI가 완성되기 전에도 자동 테스트를 만들 수 있어야 한다고 강조합니다.
장기 유지보수에 유리합니다. 외곽 기술(DB, 메시지 브로커, 외부 API)이 바뀌어도 중심부는 안정적으로 유지됩니다.
계열 전체의 단점#
추상화 비용이 있습니다. 포트, 어댑터, 인터페이스, DTO, 매핑 코드가 늘어나는 방향으로 가기 쉽습니다. 단순 CRUD 서비스에서는 이 비용이 실제 도메인 복잡도를 초과할 수 있습니다.
교리화 위험이 있습니다. “무조건 Repository 인터페이스”, “무조건 Use Case 클래스”, “무조건 Presenter” 식으로 적용하면, 구조 보존 자체가 목적이 되는 역전이 일어납니다. 이 계열의 목적은 도메인을 보호하는 것 이지, 계층 수를 늘리는 것 이 아닙니다.
읽기 경로가 무거워질 수 있습니다. 모든 조회를 Domain → Repository → Use Case 경로로 밀어 넣으면 불필요하게 무거워집니다. 읽기 모델이 복잡한 서비스에서는 조회 전용 경로를 별도로 두는 것이 더 실용적입니다. (이 문제는 바로 다음에 다룰 CQRS와 직접 연결됩니다.)
적합한 상황#
- 핵심 비즈니스 규칙이 복잡하고 오래 유지해야 하는 시스템
- 입출력 채널이 다양한 서비스 (HTTP, gRPC, 배치, 메시지 소비자)
- 인프라 교체 가능성이 높은 환경
- 테스트 격리가 중요한 프로젝트
부적합한 상황#
- 화면 몇 개짜리 단순 CRUD 서비스
- 대부분이 읽기 위주인 시스템
- 팀이 인터페이스 개수만 늘리고 포트의 목적을 정의하지 않는 경우
3. CQRS (Command Query Responsibility Segregation)#
개요#
CQRS는 읽기(Query)와 쓰기(Command)의 모델을 분리 하는 패턴입니다. Greg Young이 체계화하고 Martin Fowler가 정리한 이 패턴은, Bertrand Meyer의 CQS(Command Query Separation) 원칙을 아키텍처 수준으로 확장한 것입니다.
전통적인 CRUD 시스템에서는 같은 모델이 읽기와 쓰기를 모두 담당합니다. OrderService가 주문을 생성하고, 주문 목록을 조회하고, 주문 상세를 반환합니다. 단순한 시스템에서는 이것으로 충분합니다.
하지만 시스템이 커지면 읽기와 쓰기의 요구사항이 극단적으로 달라지는 경우가 많습니다.
- 쓰기: 도메인 규칙 검증, 상태 전이, 불변조건 보장, 트랜잭션 일관성이 중요합니다.
- 읽기: 여러 Aggregate를 조인한 비정규화 뷰, 페이징, 정렬, 필터링, 캐싱이 중요합니다.
이 둘을 하나의 모델로 다루면, 도메인 모델이 읽기 최적화를 위해 오염되거나, 읽기 성능이 도메인 규칙 때문에 제한됩니다.
핵심 구조#
graph TD
Client["Client"]
Client -->|"Command"| CH["Command Handler"]
Client -->|"Query"| QH["Query Handler"]
subgraph "쓰기 경로 (Command)"
CH --> DM["Domain Model<br/>Aggregate, 규칙 검증"]
DM --> REPO["Repository"]
REPO --> WDB[("Write DB")]
end
subgraph "읽기 경로 (Query)"
QH --> RM["Read Model<br/>비정규화 뷰, DTO"]
RM --> RDB[("Read DB / Cache")]
end
WDB -.->|"동기화<br/>(같은 DB 또는 이벤트)"| RDB
style Client fill:#FFFFFF,color:#000000
style CH fill:#FFB6C1,color:#000000
style DM fill:#FFD700,color:#000000
style REPO fill:#FFB6C1,color:#000000
style WDB fill:#FFB6C1,color:#000000
style QH fill:#87CEEB,color:#000000
style RM fill:#87CEEB,color:#000000
style RDB fill:#87CEEB,color:#000000
쓰기 경로(붉은색)는 도메인 규칙을 엄격하게 적용하고, 읽기 경로(푸른색)는 조회에 최적화된 별도 모델을 사용합니다. 두 경로 사이의 점선은 동기화를 나타냅니다.
쓰기 경로는 Aggregate와 Domain Event를 통해 비즈니스 규칙을 엄격하게 적용합니다. 읽기 경로는 도메인 모델을 거치지 않고, 조회에 최적화된 별도의 Read Model을 직접 사용합니다.
중요한 점은 CQRS가 반드시 별도 DB를 요구하지 않는다 는 것입니다. 같은 DB를 쓰면서 모델만 분리하는 것도 유효한 CQRS입니다. Fowler도 이 점을 강조합니다. 쓰기용 정규화 테이블과 읽기용 뷰/비정규화 테이블이 같은 DB에 있어도, 모델이 분리되어 있다면 CQRS의 이점을 얻을 수 있습니다.
장점#
읽기와 쓰기를 독립적으로 최적화할 수 있습니다. 쓰기 경로는 도메인 규칙에 집중하고, 읽기 경로는 성능에 집중할 수 있습니다. 읽기에 Redis 캐시를 붙이거나, Elasticsearch를 Read Model로 쓰거나, 비정규화 뷰를 만들어도 쓰기 모델에 영향을 주지 않습니다.
도메인 모델이 읽기 요구사항에 오염되지 않습니다. 화면에 보여줄 데이터 형태 때문에 Aggregate에 불필요한 필드를 추가하거나 관계를 풀어야 하는 압력이 사라집니다.
독립적 확장(scaling)이 가능합니다. 대부분의 웹서비스는 읽기가 쓰기보다 훨씬 많습니다. 읽기 경로만 별도로 스케일 아웃하거나, 읽기 전용 레플리카를 활용하기 쉬워집니다.
Ports & Adapters 계열과 자연스럽게 결합됩니다. 쓰기 경로에서는 Hexagonal/Clean의 도메인 중심 구조를 적용하고, 읽기 경로에서는 가볍게 DB를 직접 조회하는 방식이 가능합니다. 앞 섹션에서 언급한 “읽기 경로가 무거워지는 문제"를 CQRS가 직접 해결합니다.
단점#
복잡성이 증가합니다. 모델이 두 개가 되므로 코드량이 늘어나고, 두 모델 간 동기화를 관리해야 합니다. Fowler는 CQRS가 시스템의 일부분에만 적용되어야 하며, 전체 시스템에 적용하면 상당한 복잡성이 추가된다고 경고합니다.
별도 DB를 쓰는 경우 eventual consistency를 다뤄야 합니다. 쓰기 DB와 읽기 DB가 분리되면, 쓰기 직후 읽기에서 최신 데이터가 바로 보이지 않을 수 있습니다. 이 지연을 사용자 경험 측면에서 어떻게 처리할지 설계해야 합니다.
단순 CRUD에는 과도합니다. 읽기와 쓰기의 모양이 거의 같은 시스템이라면, 모델을 분리하는 비용이 이점을 초과합니다. Microsoft도 CQRS를 “도메인이 복잡한 경우에만 적용하라"고 권합니다.
Event Sourcing과의 관계#
CQRS는 종종 Event Sourcing과 함께 언급되지만, 둘은 별개의 패턴입니다.
- CQRS: 읽기/쓰기 모델을 분리한다.
- Event Sourcing: 상태를 직접 저장하는 대신, 상태 변화를 이벤트의 시퀀스로 저장한다.
Event Sourcing을 쓰면 이벤트 스트림에서 Read Model을 프로젝션(projection)하는 것이 자연스러우므로 CQRS와 궁합이 좋습니다. 하지만 CQRS 없이 Event Sourcing을 쓸 수도 있고, Event Sourcing 없이 CQRS를 쓸 수도 있습니다. 실무에서는 Event Sourcing 없이 CQRS만 적용하는 경우가 훨씬 흔합니다.
적합한 상황#
- 읽기/쓰기 비율이 크게 비대칭인 시스템 (예: 읽기 90% 이상)
- 읽기와 쓰기의 데이터 모양이 크게 다른 서비스
- 쓰기는 도메인 규칙이 복잡하지만, 읽기는 다양한 뷰가 필요한 경우
- 읽기 경로의 독립적 확장이 필요한 서비스
부적합한 상황#
- 읽기와 쓰기의 모양이 거의 같은 단순 CRUD
- 도메인 규칙이 단순한 시스템
- 팀이 eventual consistency를 다룰 준비가 되지 않은 경우 (별도 DB 사용 시)
실무 팁#
CQRS를 도입할 때 가장 실용적인 접근법은 점진적 적용 입니다. 처음부터 별도 DB를 나누지 말고, 같은 DB 안에서 쓰기 모델과 읽기 모델(뷰, 비정규화 테이블)만 분리하는 것부터 시작합니다. 이것만으로도 도메인 모델 오염 방지와 읽기 최적화의 이점을 얻을 수 있습니다. 별도 DB 분리는 성능이나 확장 요구가 실제로 드러난 후에 해도 늦지 않습니다.
Part 2에서는 Event-Driven Architecture, Vertical Slice Architecture, Modular Monolith를 다루고, 여섯 가지 아키텍처의 비교와 선택 기준을 정리합니다.
References#
- Microsoft, “Common web application architectures,” Microsoft Learn. https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/common-web-application-architectures
- Microsoft, “N-tier architecture style,” Azure Architecture Center. https://learn.microsoft.com/en-us/azure/architecture/guide/architecture-styles/n-tier
- Microsoft, “DDD-oriented microservice,” Microsoft Learn. https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/ddd-oriented-microservice
- Microsoft, “Infrastructure persistence layer design,” Microsoft Learn. https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design
- Eric Evans, “Domain-Driven Design Reference,” Domain Language, 2015. https://www.domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf
- Martin Fowler, “AnemicDomainModel,” martinfowler.com. https://martinfowler.com/bliki/AnemicDomainModel.html
- Martin Fowler, “CQRS,” martinfowler.com. https://martinfowler.com/bliki/CQRS.html
- Alistair Cockburn, “Hexagonal Architecture,” alistair.cockburn.us. https://alistair.cockburn.us/hexagonal-architecture
- Jeffrey Palermo, “The Onion Architecture: part 1,” Programming with Palermo, 2008. https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/
- Robert C. Martin, “The Clean Architecture,” Clean Coder Blog, 2012. https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
- Greg Young, “CQRS Documents,” 2010. https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf