Files
llm-wiki/raw/company-tech-blogs/cqrs-lite-clean-architecture-read-path-bypass-wakita.md

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
cqrs
read-model
clean-architecture
hexagonal
query-bypass
projection
application-port
ca-skeleton
feature-application-query-bypass-contract
ca-skeleton
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

왜 저장했는지 / 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" 를 달성하는지

메모 / 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 중립