9.7 KiB
title, source_type, url, archive_url, status, confidence, tags, related_branches, related_projects, created, last_reviewed
| title | source_type | url | archive_url | status | confidence | tags | related_branches | related_projects | created | last_reviewed | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| company-tech-blog / CQRS-lite in Clean Architecture — Read Path Fast-Path (wakita181009, DEV.to 2026-02) | company-tech-blog | https://dev.to/wakita181009/adding-cqrs-to-clean-architecture-the-read-path-got-a-fast-path-and-the-domain-got-smaller-4mcp | raw | medium |
|
|
|
2026-06-04 | 2026-06-04 |
CQRS-lite in Clean Architecture — Read Path Fast-Path (wakita181009, DEV.to 2026-02)
Layer:
raw/company-tech-blogs/— DEV.to 의 wakita181009 작성 "CQRS with Clean Architecture in Kotlin: Separating Read and Write Paths for Better Performance" (2026-02-22). Clean Architecture 내에서 CQRS-lite (same database, no separate store) 를 적용해 read path 가 domain aggregate reconstruction 을 우회하고 application-layer DTO 를 직접 반환하는 구조를 구체적으로 설명. 이 글의 저자는 별도로 ArchUnit 규칙 적용 시리즈도 작성 (feature-application-query-bypass-contract 의 선행 연구 방향과 일치).출처 신뢰도: DEV.to 개인 블로그 (
engineering-blog등급). 대기업 공식 블로그가 아님. 그러나 저자는 Clean Architecture + CQRS-lite + ArchUnit 시리즈를 일관성 있게 작성하며 Kotlin + jOOQ 환경의 구체 구현 제공. company-tech-blog 로 분류하나 best practice 로 일반화 금지.
Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| raw/branch-notes/feature-application-query-bypass-contract | D1 (aggregate-through read path) 의 overhead 문제 + Alt 2 (CQRS-lite — same database, dedicated read port) 의 "logical split only" 패턴 근거. "read port 가 domain type 을 가지지 않는다" 는 hexagonal purity 유지 방법의 실제 사례 |
출처 / Source
- 원본 URL: https://dev.to/wakita181009/adding-cqrs-to-clean-architecture-the-read-path-got-a-fast-path-and-the-domain-got-smaller-4mcp
- 아카이브 URL:
- 저자 / 조직: wakita181009 — DEV.to 개인 블로그 (Engineering blog, 개인 저자)
- 발행일: 2026-02-22
- 마지막 확인일: 2026-06-04
- 검증 한계: DEV.to 개인 블로그. Kotlin + jOOQ 환경이므로 Java + Spring Data JPA 에 직접 전이되지 않음. 개념적 패턴은 전이 가능.
왜 저장했는지 / Why archived
Clean Architecture + CQRS-lite (same database) 의 구체적 구현 패턴을 보여주는 블로그. "read path 가 domain aggregate 를 거치는 것은 pure overhead" 라는 명확한 문제 진술 + "query repository 는 application-layer port, domain type 없음" 의 hexagonal 정합 패턴을 직접 코드로 보여줌. Alt 2 채택의 실제 구현 패턴 근거.
핵심 인용 / Key quotes (verbatim)
[§Problem statement — read overhead] "Steps 4 and 5 are pure overhead. The client asked for a list of repos. The read path doesn't modify anything, doesn't enforce business invariants, doesn't trigger side effects. It just needs data — but it's constructing fully validated domain objects, only to immediately unwrap them into flat DTOs."
[§CQRS-lite solution — logical split] "Commands (writes) go through the full domain model: validation, invariants, business rules. Queries (reads) bypass the domain and return DTOs directly from the database... Both repositories read from and write to the same github_repo table. The split is logical, not physical."
[§Read port architecture] "The query repository is an application-layer port... it takes primitives (Long, Int) and returns application-layer DTOs. It has no domain types in its signature... The domain doesn't know it exists."
[§Read path architecture — fast path] "Adding a search query that joins across tables, a dashboard aggregation that returns computed fields, or a denormalized read model for high-traffic endpoints — none of these will touch the command side or the domain."
[§Write/read type isolation] "The write path and read path have separate DTOs, separate errors, separate repository interfaces. The write path and read path don't share application-layer types. They share domain value objects for input validation — and nothing else."
Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| WAKITA-CQRS-C1 | read path 가 full domain aggregate 를 load 하는 것은 "pure overhead" — read 는 invariant 보호나 side effect 가 없으므로 data 만 필요 | "The read path doesn't modify anything, doesn't enforce business invariants, doesn't trigger side effects. It just needs data — but it's constructing fully validated domain objects, only to immediately unwrap them into flat DTOs." | engineering-blog |
read operation 이 domain logic 을 전혀 필요로 하지 않는 단순 조회 use case | complex read operation (write side 와 같은 aggregate 검증이 필요한 경우) 에는 적용 불가 |
| WAKITA-CQRS-C2 | CQRS-lite 는 물리적 store 분리 없이 논리적 분리만 으로 구현 가능 — read/write 가 같은 table 을 사용 | "Both repositories read from and write to the same github_repo table. The split is logical, not physical." | engineering-blog |
single database + CQRS (logical separation only) 패턴 — separate store 없이 bypass 가능 | 물리적 store 분리 없이도 CQRS 의 모든 이점을 얻는다는 보편적 주장 아님 — "스케일링 독립" 이점은 여전히 physical split 이 필요 |
| WAKITA-CQRS-C3 | query repository 는 application-layer port 로 domain type 을 signature 에 포함하지 않음 — domain layer 는 query port 의 존재를 모름 | "The query repository is an application-layer port... it takes primitives (Long, Int) and returns application-layer DTOs. It has no domain types in its signature... The domain doesn't know it exists." | engineering-blog |
hexagonal architecture 에서 read-only query port 를 application layer 에 배치하는 패턴 | Spring Data JPA 또는 Java 환경에서 동일 패턴이 직접 적용 가능하다는 보장 — 저자는 Kotlin + jOOQ 사용 |
| WAKITA-CQRS-C4 | CQRS-lite read path 는 "fast path" 로 join / aggregation / denormalized read model 을 command side 나 domain 에 영향 없이 추가 가능 | "Adding a search query that joins across tables, a dashboard aggregation that returns computed fields, or a denormalized read model for high-traffic endpoints — none of these will touch the command side or the domain." | engineering-blog |
CQRS-lite read path 의 확장성 이점 — read 요구사항 변화가 write side 에 영향 없음 | 이 확장성이 추가 운영 비용 없이 달성된다는 보장 없음 — 별도 query 관리 코드가 증가함 |
| WAKITA-CQRS-C5 | write path 와 read path 는 application-layer type (DTO, error, repository interface) 을 공유하지 않음 — domain value object 만 input validation 목적으로 공유 | "The write path and read path have separate DTOs, separate errors, separate repository interfaces. The write path and read path don't share application-layer types. They share domain value objects for input validation — and nothing else." | engineering-blog |
write/read application type 의 완전 분리 원칙 | "domain value object 공유" 가 항상 안전하다는 일반 규칙 아님 — 특정 구현에서 coupling 이 생길 수 있음 |
Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
WAKITA-CQRS-C1: aggregate load overhead 의 문제 진술 (단순 조회에서 invariant 보호가 불필요하므로 overhead)WAKITA-CQRS-C2: same database CQRS-lite 의 "logical split only" 패턴WAKITA-CQRS-C3: query repository 를 application-layer port 로 배치하고 domain type 을 배제하는 hexagonal 패턴
- 이 자료가 증명하지 않는 것:
- Spring Data JPA 환경에서의 구체 구현 (저자는 Kotlin + jOOQ)
- ArchUnit 으로 이 패턴을 정적 강제하는 방법 (저자는 별도 Detekt 시리즈에서 다룸)
- 이 패턴이 production 에서 실제 performance 개선을 가져왔다는 수치 증거
- ca-tmpl 의
QueryUseCase+TransactionPort.inRead계약과 직접 호환되는지
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- ca-tmpl 의
QueryUseCase인터페이스가 domain object 와 projection DTO 를 둘 다 반환할 수 있는지, 아니면 전용 read port 를 별도 도입해야 하는지 (D1 결정 핵심) - Spring Data JPA closed projection 이 Kotlin jOOQ DTO 와 동일한 "no domain type in signature" 를 달성하는지
- ca-tmpl 의
메모 / Notes
- 저자는 같은 시리즈에서 ArchUnit + Detekt 로 이 패턴을 정적 강제하는 방법을 다룸 ("An LLM Broke My Architecture in One Generation. I Made That a Build Error") — ca-tmpl ArchUnit fitness function 방향과 일치
- Kotlin + jOOQ 구현이므로 Java + Spring Data JPA 로의 직접 이식은 별도 검토 필요. 핵심 패턴 (application-layer port, no domain type in signature) 은 언어/ORM 중립
Related / 관련
- raw/official-docs/spring-data-jpa-projections-spring-official — Spring Data JPA 환경에서 이 패턴의 구체 mechanism (closed projection, DTO constructor)
- raw/official-docs/cqrs-pattern-azure-architecture-center — CQRS-lite (single store) 의 공식 "foundational level" 분류
- raw/branch-notes/feature-application-port-usecase-contract — QueryUseCase 선행 계약 (D9: READ_REPOSITORY capability)
- raw/branch-notes/feature-application-query-bypass-contract — 본 자료를 소비하는 branch