16 KiB
title, source_type, status, confidence, tags, related_projects, last_reviewed
| title | source_type | status | confidence | tags | related_projects | last_reviewed | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Clean Architecture 패키지 레이아웃 (feature-first vs layer-first vs hexagonal vs modulith vs onion) | llm-generated | draft | medium |
|
|
2026-05-22 |
Clean Architecture 패키지 레이아웃 (feature-first vs layer-first vs hexagonal vs modulith vs onion)
Layer:
wiki/concepts/— 일반 개념. 내 프로젝트 사실은project-template사용.
Summary
feature-first 패키지 레이아웃은 최상위를 도메인 feature(features/{name}/)로 자르고 그 내부에 presentation/application/domain/infrastructure를 두는 구조로, 각 feature가 자체 inbound/outbound adapter와 application core를 갖는다는 점에서 본질적으로 "feature 단위로 잘린 mini-Hexagonal"과 동형이다. layer-first는 최상위가 기술 계층이고 도메인이 그 안에 흩어지는 점에서 응집도 축이 정반대다.
Standard (공식 정의)
- Uncle Bob, Screaming Architecture (2011): 시스템의 최상위 디렉터리는 사용된 framework이 아니라 시스템이 "외치는" use case / business 영역이어야 한다고 주장. controller/service/repository로 자르는 layer-first는 framework가 외치는 구조라는 점을 비판한다. 출처: raw/official-docs/feature-first-uncle-bob-screaming-architecture-2011.
- Cockburn, Hexagonal (Ports and Adapters): 응용 코어(application + domain)를 inbound adapter(driving)와 outbound adapter(driven)로부터 port interface로 격리. driving/driven adapter 분리가 본질이며 패키지 형태 자체는 비강제. 출처: raw/official-docs/hexagonal-cockburn-wikipedia-summary.
- Thombergs, BuckPal reference: Cockburn Hexagonal을 자바/스프링 부트로 구현한 reference. 최상위가 feature이고 내부에
domain/application/adapter(in|out)3-tier로 잘려 feature-first + Hexagonal이 같은 구조에서 만난다는 점을 보여줌. 출처: raw/official-docs/hexagonal-thombergs-buckpal-github. - Palermo, Onion Architecture (2008): 의존성은 외부 layer(infrastructure/UI)에서 내부 layer(domain model)로만 향하며, 안쪽이 바깥쪽 interface를 알지 않는다는 의존성 역전 규칙. layer를 동심원으로 표현. 출처: raw/official-docs/onion-palermo-original-2008.
- Spring Modulith (공식 문서): Spring Boot 위에서 패키지 자체가 모듈 경계가 되며
@ApplicationModule/named-interface로 cross-module 접근을 강제. JPA event SPI 위에서 transactional event publication 등 운영 contract를 framework가 제공. 출처: raw/official-docs/modulith-spring-official-doc.
한계 / 주의점
각 레이아웃은 다른 트레이드오프를 가진다.
-
feature-first
- cross-feature shared kernel(공통 value object, 공통 정책)을 어디에 둘지가 모호.
common/을 두되 business concept가 새지 않도록 별도 규칙이 필요. - 도메인 인접성이 강한 feature 사이에서 model 중복 위험(같은 개념을 두 feature가 따로 정의).
- feature 사이 호출은 직접 import보다는 port 또는 명시적 application API를 통해 통제해야 함 (그렇지 않으면 사실상 layer-first로 회귀).
- cross-feature shared kernel(공통 value object, 공통 정책)을 어디에 둘지가 모호.
-
layer-first
- 도메인 수가 늘어나면 같은 도메인의 코드가
controller/,service/,repository/에 흩어져 응집도가 폭락. 한 도메인을 수정할 때 패키지 3~4곳을 동시에 건드림. Sahibinden 기술블로그는 이를 "패키지가 도메인을 외치지 않는다"로 비판함. 출처: raw/company-tech-blogs/feature-first-sahibinden-package-by-layer-vs-feature. - Baeldung식 Clean Architecture Spring Boot 가이드는 입문 학습 비용이 가장 낮지만 결과적으로 도메인 응집을 보장하지 않음. 출처: raw/official-docs/layer-first-baeldung-clean-architecture-spring-boot, raw/company-tech-blogs/layer-first-kamilmazurek-github-template.
- 도메인 수가 늘어나면 같은 도메인의 코드가
-
hexagonal pure (feature 슬라이스 없음)
- 최상위가
application/domain/adapter로만 잘리고 feature 슬라이스가 없으면 도메인이 늘어날수록application과domain패키지가 비대해짐. - inbound/outbound 분리는 명확하지만 도메인 간 boundary가 약함. 우아한형제들 기술블로그의 Hexagonal 적용도 결국 도메인별 module로 분리하는 방향으로 진화. 출처: raw/company-tech-blogs/hexagonal-woowahan-techblog-2023.
- 최상위가
-
Spring Modulith
- Spring Framework / Spring Boot 종속. framework-neutral 도메인을 외부 강제로 보호하기 어려움 (도메인까지 Spring scan에 들어옴).
- transactional event publication은 JPA event SPI에 의존하는 구현체가 다수라 persistence 선택에 영향. 카카오뱅크 수신상품 사례는 Modulith가 "느슨한 modular monolith"의 좋은 진화 경로임을 보여주지만 framework lock-in 비용을 수반. 출처: raw/company-tech-blogs/modulith-kakaobank-techblog-2025, raw/company-tech-blogs/modulith-arawn-github-modular-monoliths-spring.
- Spring Modulith 공식 문서는 module boundary 위반을 verification API로 잡지만 빌드 실패 강제 여부는 적용 프로젝트의 CI 설정에 의존. 출처: raw/official-docs/modulith-spring-official-doc.
-
onion
- 의존성 방향 규칙은 Hexagonal과 동등 (안쪽으로만 의존).
- 그러나 boundary verification 도구가 framework 자체로는 제공되지 않음. ArchUnit 같은 별도 정적 분석 없이는 layer 우회를 build-time에 잡기 어려움. Allegro 기술블로그도 onion의 이상은 인정하면서 실제 강제는 별도 도구가 필요하다고 명시. 출처: raw/company-tech-blogs/onion-allegro-tech-blog-2023.
5종 모두 "의존성은 안쪽으로만"이라는 동일한 핵심 원칙을 공유하며, 차이는 (a) 최상위 자름의 기준(feature vs layer) (b) framework가 boundary를 강제하는지 (c) inbound/outbound adapter 명시 여부에 있다.
경계를 강제하는 방법 (enforcement)
레이아웃을 고른 것만으로 경계가 지켜지지 않는다. 어느 레이아웃이든 boundary drift를 막으려면 별도의 강제 수단이 필요하며, 일반적으로 두 축으로 나뉜다.
- Build-graph 검사: multi-module 빌드에서 module 간 허용 dependency를 화이트리스트로 두고, 허용 외 module dependency 선언 시 빌드를 실패시킨다(예: Gradle custom verification task). module 경계 자체가 1차 방어선이 된다.
- Source/bytecode import 검사: ArchUnit 같은 정적 분석 도구로 package/class 레벨 import·call·annotation을 검사한다. "
..domain..은org.springframework..에 의존 금지", "특정 class(예:ApplicationContext) 의존 금지(banned-class)", "특정 annotation 사용 금지", "DTO는 web adapter 안에서만 접근" 같은 fitness function을 test로 강제한다.
정적 분석의 한계는 분명하다. import/call/annotation은 bytecode에 남지만, runtime container lookup(ApplicationContext.getBean(String) 같은 string-key 조회), Class.forName(String) reflection, classloader 우회는 bytecode가 문자열 내용을 노출하지 않으므로 catch할 수 없다. class-literal getBean(Class<T>)까지는 method-call target으로 잡히지만 string-key 변종은 false-negative가 되며, 이 영역은 code review·runtime 검증(Actuator /beans, Modulith verifier 등)으로만 보완 가능하다. 또 ArchUnit의 should() 조건이 매칭 대상이 0개인 빈 module에서 vacuous하게 통과하는 empty-anchor 함정이 있어, allowEmptyShould 정책과 "위반을 데이터로 보는(violations-as-data)" negative fixture로 rule이 실제로 catch하는지 별도 보증하는 패턴이 쓰인다. ArchUnit 분석 scope(classpath import vs package filter)와 empty-should 함정의 일반 지식은 wiki/concepts/archunit-scope-classpath-vs-package-filter 참조.
Claim-backed Knowledge
인용 가능한 출처가 직접 뒷받침하는 일반 지식만 둔다. "어느 레이아웃이 옳다"는 추론·취향은 §한계 / 주의점과 §Do Not Overclaim에서 다룬다.
| Knowledge Point | Supporting Claims | Confidence | Notes |
|---|---|---|---|
| 최상위 디렉터리는 framework가 아니라 use case / business 영역을 드러내야 한다(layer-first 비판) | raw/official-docs/feature-first-uncle-bob-screaming-architecture-2011 | medium | Uncle Bob Screaming Architecture (2011), engineering-blog — 공식 표준이 아닌 영향력 있는 블로그 주장 |
| Hexagonal의 본질은 응용 코어를 inbound(driving)/outbound(driven) adapter로부터 port interface로 격리하는 것이며 package 형태 자체는 비강제 | raw/official-docs/hexagonal-cockburn-wikipedia-summary | medium | Cockburn Ports & Adapters, engineering-blog |
| 의존성은 외부 layer(infra/UI)→내부 layer(domain model) 방향으로만 향하고 안쪽은 바깥쪽 interface를 알지 않는다 | raw/official-docs/onion-palermo-original-2008 | medium | Palermo Onion (2008), engineering-blog |
Spring Modulith는 package를 module 경계로 삼고 @ApplicationModule/named-interface로 접근을 강제하나, 위반의 build 실패 강제 여부는 적용 프로젝트 CI 설정에 의존(framework는 verification API만 제공) |
raw/official-docs/modulith-spring-official-doc | high | 공식 문서. build 실패는 자동이 아님 |
| onion/hexagonal 의존성 방향 규칙은 framework 자체로 build-time 강제되지 않으며, ArchUnit 등 별도 정적 분석 없이는 layer 우회를 빌드 시점에 잡기 어렵다 | raw/company-tech-blogs/onion-allegro-tech-blog-2023 | medium | company-tech-blog 관점 — 공식 best practice로 승격 금지 |
| 도메인 수가 늘면 layer-first에서 한 도메인 코드가 controller/·service/·repository/에 흩어져 응집도가 떨어진다 | raw/company-tech-blogs/feature-first-sahibinden-package-by-layer-vs-feature | medium | company-tech-blog 사례 |
Project Application
- wiki/projects/ca-tmpl/clean-architecture-package-layout — ca-tmpl package/module blueprint + enforcement-rules 적용 기록. Gradle multi-module boundary(8 module, production root
dev.caskeleton)와 ArchUnit/Gradle guardrail은locally-verified(2026-06-04 ground-truth 대조). enforcement dimension:domain_is_pure(Lombok ban 포함), application↔adapter 격리,ApplicationContextbanned-class rule(D11, string-key bypass는 한계),verifyCleanArchitectureDependenciesbuild-graph 검사, violations-as-data negative fixture를 기록.sample-portfoliofixture business flow와 Spring Modulith verifier는 범위 밖. - raw/branch-notes/feature-architecture-enforcement-rules — 경계 의존성 규칙과 forbidden annotation/import의 ArchUnit 강제 기준.
- raw/branch-notes/feature-skeleton-package-blueprint-contract — Gradle multi-module Clean Architecture / Hexagonal module blueprint SSOT.
- raw/branch-notes/feature-domain-feature-onboarding-contract — 새 도메인 추가 시 New Domain Module Slice + Read/Write Difference Table 기준.
- raw/project-notes/ca-skeleton-operational-contract §20 Skeleton Blueprint Contract — 위 3개 branch-note를 통합한 canonical SSOT.
내가 설명할 수 있어야 하는 것
- feature-first / layer-first / hexagonal / onion / modulith 5종의 공식 정의와 공통 핵심 원칙("의존성은 안쪽으로만")은 무엇인가?
- 각 레이아웃이 어떤 문제를 해결하고, 어떤 상황에서는 무너지는가(특히 layer-first의 응집도 붕괴 시점)?
- 레이아웃 선택만으로 경계가 지켜지지 않는 이유와, build-graph 검사 / 정적 분석(ArchUnit) 두 축의 enforcement가 각각 무엇을 막는가?
- 공식 문서가 말하지 않는 부분(예: Spring Modulith가 위반의 build 실패를 자동 강제하지 않음)은 무엇인가?
- 회사 기술 블로그 사례(우아한형제들·카카오뱅크·Allegro 등)를 일반 법칙처럼 말하면 안 되는 지점은?
- 내 프로젝트(ca-tmpl)에서는 어떤 branch decision과 ArchUnit/Gradle rule로 연결됐는가?
- 정적 분석으로 잡히지 않는 우회(runtime lookup, reflection)는 코드/운영에서 어떻게 검증·보완하는가?
Interview Questions
- feature-first 패키지 레이아웃과 layer-first(controller/service/repository) 레이아웃의 차이는 무엇인가? 어느 시점에 후자가 무너지는가?
- feature-first 레이아웃이 Hexagonal Architecture와 "동형"이라는 표현은 무슨 뜻인가? buckpal 예시로 설명하라.
- 도메인 수가 늘어났을 때 layer-first가 응집도 면에서 무너지는 이유는 무엇인가? 어떤 운영 신호로 그것을 감지하는가?
- Spring Modulith를 즉시 도입하지 않고 Gradle multi-module + ArchUnit/Gradle guardrail로 시작하는 트레이드오프는 무엇인가? 향후 Modulith로 이행할 수 있는 조건은?
- 패키지 규약을 문서로만 두지 않고 ArchUnit 같은 architecture test로 boundary를 강제하는 이유는 무엇인가? 정적 분석으로 잡히지 않는 우회(runtime lookup 등)는 어떻게 보완하는가?
Do Not Overclaim
- "feature-first가 항상 layer-first보다 우월하다"는 금지. 학습 비용은 layer-first가 가장 낮고, 도메인 수가 적은 초기 단계에서는 layer-first도 합리적인 선택이다.
- "ca-tmpl이 Hexagonal Architecture다"는 단정 금지. ca-tmpl은 Gradle module boundary로 application/domain과 adapter를 물리 분리한 Clean Architecture / Hexagonal-inspired template이다. 현재 구현 어휘는 inbound =
adapter-web, outbound =adapter-persistence/adapter-outbound이며, Cockburn 원전의 모든 어휘를 그대로 차용한 구현은 아님. - "Spring Modulith를 곧 도입할 것"이라는 단정 금지. Modulith는 framework가 boundary를 강제하는 자연스러운 진화 경로이지만, 도입은 framework lock-in과 JPA 의존 비용을 수반하며 ca-tmpl의 framework-neutral 도메인 원칙과 일부 충돌한다. 향후 검토 대안 중 하나일 뿐 도입 결정이 아니다.
- "ArchUnit이 모든 경계 위반을 잡아낸다"는 단정 금지. 정적 분석은 ApplicationContext lookup,
@Lazyreflection, runtime classloader 우회를 감지할 수 없으며 별도 코드 리뷰/SonarQube 보완이 필요하다.
Sources
- raw/official-docs/feature-first-uncle-bob-screaming-architecture-2011 — Uncle Bob Screaming Architecture (2011)
- raw/official-docs/hexagonal-cockburn-wikipedia-summary — Cockburn Hexagonal Architecture (Ports & Adapters)
- raw/official-docs/hexagonal-thombergs-buckpal-github — Thombergs BuckPal reference (feature 단위로 잘린 Hexagonal)
- raw/official-docs/layer-first-baeldung-clean-architecture-spring-boot — Baeldung Clean Architecture Spring Boot
- raw/official-docs/onion-palermo-original-2008 — Palermo Onion Architecture (2008)
- raw/official-docs/modulith-spring-official-doc — Spring Modulith 공식 문서
- raw/company-tech-blogs/feature-first-sahibinden-package-by-layer-vs-feature — Sahibinden: feature vs layer 응집도 비교
- raw/company-tech-blogs/layer-first-kamilmazurek-github-template — layer-first Spring Boot template
- raw/company-tech-blogs/hexagonal-woowahan-techblog-2023 — 우아한형제들 Hexagonal 적용 사례
- raw/company-tech-blogs/modulith-kakaobank-techblog-2025 — 카카오뱅크 수신상품 Modulith 적용
- raw/company-tech-blogs/modulith-arawn-github-modular-monoliths-spring — Modular Monoliths with Spring 참조 구현
- raw/company-tech-blogs/onion-allegro-tech-blog-2023 — Allegro Onion Architecture 적용기
- raw/project-notes/ca-skeleton-operational-contract — canonical operational contract (§20 Skeleton Blueprint Contract, §29 Topic 1)