[volume-3] 도메인 & 객체 설계 및 아키텍처, 패키지 구성#91
[volume-3] 도메인 & 객체 설계 및 아키텍처, 패키지 구성#91YoHanKi wants to merge 50 commits intoLoopers-dev-lab:YoHanKifrom
Conversation
- BrandService 구현 (createBrand, deleteBrand) - BrandReader 구현 (getOrThrow, exists 패턴) - BrandRepository 인터페이스 정의 (Port) - BrandRepositoryImpl 구현 (Adapter, Port-Adapter 패턴) - BrandJpaRepository 구현 (Spring Data JPA) - 단위 테스트 작성 (Repository/Reader 모킹) - 통합 테스트 작성 (Spring Context, DatabaseCleanUp)
- BrandFacade 구현 (Service 조율, @transactional 경계) - BrandInfo 구현 (record, from(BrandModel) 팩토리) - BrandV1Controller 구현 (POST, DELETE 엔드포인트) - BrandV1Dto 구현 (CreateBrandRequest, BrandResponse) - BrandV1ApiSpec 구현 (OpenAPI 명세) - E2E 테스트 작성 (브랜드 생성→삭제 플로우) - .http/brand.http 작성 (수동 테스트용)
- ProductId VO 구현 (영문+숫자, 1-20자 검증) - ProductName VO 구현 (1-100자 검증) - Price VO 구현 (BigDecimal, 음수 불가, scale 2) - StockQuantity VO 구현 (int, 음수 불가) - ProductModel Entity 구현 (BaseEntity 상속, BrandId FK, soft delete 지원) - 재고 관리 메서드 구현 (decreaseStock, increaseStock) - JPA Converter 구현 (ProductId, ProductName, Price, StockQuantity) - 단위 테스트 작성 (VO 검증 규칙, Entity 도메인 메서드)
- ProductFacade 추가 (Application Layer)
- ProductInfo 추가 (Entity → DTO 변환)
- ProductV1Controller 추가 (REST API 엔드포인트)
- POST /api/v1/products (상품 생성)
- GET /api/v1/products (목록 조회, 페이징/필터링/정렬)
- DELETE /api/v1/products/{productId} (상품 삭제)
- ProductV1Dto 추가 (Request/Response DTOs)
- Jakarta Validation 적용
- ProductV1ApiSpec 추가 (OpenAPI 명세)
- E2E 테스트 작성 (8개 테스트, @nested 구조)
- .http/product.http 추가 (수동 테스트용)
- ArchUnit 테스트 통과 (레이어 아키텍처 검증)
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR implements a comprehensive architectural refactoring introducing layered architecture with DIP (Dependency Inversion Principle) across 4 domains: Brand, Product, Like, and Order. The PR successfully delivers 14 APIs with 37 tests (unit/integration/E2E) and enforces architectural rules using ArchUnit.
Changes:
- Introduced layered architecture: Interfaces → Application → Domain ← Infrastructure
- Separated App (single domain use cases) from Facade (cross-domain orchestration)
- Implemented Domain Repository interfaces as Ports with Infrastructure Adapters
- Moved transaction boundaries to Application layer
- Added comprehensive test coverage with ArchUnit validation
Reviewed changes
Copilot reviewed 164 out of 165 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| settings.gradle.kts | Added security module |
| modules/security/* | Extracted security (PasswordHasher) to separate module |
| domain//vo/ | Value Objects with validation for Brand, Product, Order domains |
| domain//.java | Domain Models, Services, and Repository interfaces |
| application//.java | App/Facade components with transaction management |
| infrastructure//.java | Repository implementations and JPA converters |
| interfaces/api//.java | Controllers, DTOs, and API specs |
| architecture/*.java | ArchUnit tests enforcing layered architecture rules |
| test//.java | 37 unit/integration/E2E tests |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
📌 Summary
🧭 Context & Decision
1. Application은 반드시 Service를 경유해야 하는가
문제 정의
repository.findBy...()를 위임만 하는 메서드들이 생겨났다. 이는 불필요한 간접 레이어를 만들고, 코드 추적 비용을 높인다.선택지와 결정
최종 결정: B 선택
2. App vs Facade — Controller는 무엇을 의존해야 하는가
문제 정의
선택지와 결정
최종 결정: B 선택
트레이드오프: Controller가 App과 Facade를 모두 의존할 수 있어 일관성이 낮아 보이지만, 각 컴포넌트의 책임이 명확히 분리되고 Facade가 실질적인 오케스트레이션 역할을 수행한다.
추후 개선 여지: ArchUnit 규칙으로 Facade가 반드시 2개 이상의 App을 의존해야 함을 자동 검증하도록 확장 가능하다.
적용 예시:
BrandFacadeBrandApp+ProductAppProductFacadeProductApp+BrandAppLikeFacadeLikeApp+ProductApp+BrandApp3. JPA Entity를 Infrastructure에서 분리하지 않은 이유
문제 정의
BrandModel,ProductModel등 Entity 클래스에@Entity,@Table,@Convert등 JPA 어노테이션이 직접 붙어있고, 이 클래스들은 Domain 레이어에 위치한다.@Entity,@Table)을 모르는 것이 이상적이다. Domain Entity와 JPA Entity를 별도 클래스로 분리하면 Domain이 기술 의존을 갖지 않는다.선택지와 결정
@Entity+@Table어노테이션을 Domain Model에 직접 적용하고 Domain 레이어에 배치최종 결정: B 선택
🏗️ Design Overview
변경 범위
영향 받는 모듈/도메인:
apps/commerce-api— application, domain, infrastructure, interfaces 전 레이어신규 추가:
BrandModel,ProductModel,LikeModel,OrderModel,OrderItemModel+ 각 VO/Repository/ServiceBrandApp/Facade,ProductApp/Facade,LikeApp/Facade,OrderApp,MemberApp*JpaRepository,*RepositoryImpl,*Converter(각 도메인)BrandV1Controller,ProductV1Controller,ProductAdminV1Controller,LikeV1Controller,MyLikeV1Controller,OrderV1Controller제거/대체:
*Facade(단일 도메인) →*App으로 대체:MemberFacade제거,OrderFacade제거Reader추상화 레이어 제거 →Repository로 통합EntityManager직접 사용 제거 →JpaRepository @Query로 대체@TransactionalService 레이어 제거 → App 레이어로 이관Ref*IdVO 중복 →domain.common.vo로 통합주요 컴포넌트 책임
BrandApp: 브랜드 생성(Service 경유), 단건 조회(Repository 직접), 삭제(Service 경유)BrandFacade: 브랜드 삭제 시 상품 연쇄 soft delete 오케스트레이션 (BrandApp+ProductApp)ProductApp: 상품 CRUD. 조회는 Repository 직접, 생성/수정/삭제는 Service 경유ProductFacade: 상품 응답에 브랜드명/좋아요 수 보강 (ProductApp+BrandApp)LikeApp: 좋아요 추가/취소(Service 경유), 목록 조회(Repository 직접)LikeFacade: 좋아요 목록에 상품명/브랜드명/가격 보강 (LikeApp+ProductApp+BrandApp)OrderApp: 주문 생성/취소(Service 경유), 상세/목록 조회 혼합OrderService: 재고 차감/복구, 데드락 방지(상품 ID 정렬), 소유권 검증 — 크로스 도메인 Repository 의존LikeService: 중복 방지(DB 유니크 + DataIntegrityViolationException 방어), 크로스 도메인 Repository 의존*RepositoryImpl: Domain Repository 인터페이스 구현 (Port-Adapter), JpaRepository 위임🔁 Flow Diagram
Interfaces Layer
HTTP 요청을 수신하고 응답을 반환하는 표현 계층. 웹 기술(HTTP 상태코드, 헤더, 직렬화)에 대한 처리를 이 레이어에서 처리합니다. Application 계층이 반드시 HTTP를 통해 호출된다는 보장이 없으므로, 웹 관련 처리는 이 레이어에서 모두 끝내고 순수한 파라미터만 전달합니다.
Application Layer
단일 도메인 유스케이스 처리(App)와 복수 도메인 조합(Facade)을 담당하는 경량 오케스트레이션 계층. 비즈니스 규칙은 Domain에 위임하고, 트랜잭션 경계 설정과 Model → Info 변환만 담당합니다.
*App*FacadeDomain Layer
비즈니스 핵심 정책과 규칙을 담는 계층. Spring·JPA·HTTP 등 기술을 직접 사용하지 않는 것이 이상적이나, JPA의 더티 체킹·1차 캐시 등 ORM 기능을 온전히 활용하기 위해
@Entity·@Table어노테이션은 Model에 직접 적용합니다. Repository는 인터페이스(Port)만 정의하여 Infrastructure와의 의존을 역전시킵니다.Infrastructure Layer
Domain Repository 인터페이스를 구현하는 기술 구현 계층(Adapter). JPA 등 구체 기술이 이 레이어에만 집중됩니다.