Skip to content

Refactor: 단일 모듈 계층형에서 헥사고널 아키텍처 멀티모듈 전환 - #18

Open
leepg038292 wants to merge 41 commits into
LIKELION-HALLYM:평강from
leepg038292:hx
Open

Refactor: 단일 모듈 계층형에서 헥사고널 아키텍처 멀티모듈 전환#18
leepg038292 wants to merge 41 commits into
LIKELION-HALLYM:평강from
leepg038292:hx

Conversation

@leepg038292

@leepg038292 leepg038292 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

전환 배경

기존 구조는 단일 Spring Boot 모듈 안에 controller / service / repository 패키지 계층형으로 구성되어 있었습니다.

기존 문제점

  • 비즈니스 로직이 Spring 어노테이션과 결합 → 도메인이 인프라에 종속
  • CartItemProductEntity@ManyToOne으로 직접 참조 → 피처 간 결합
  • 도메인 순수성이 규칙으로만 강제 → 실수로 @Entity import해도 빌드 통과
  • 테스트 시 Spring 컨텍스트 전체를 올려야 하는 구조

변경 사항

1. Java + Lombok → Kotlin 전환

data class + val로 불변 도메인 객체를 언어 수준에서 지원.
상태 변경은 반드시 copy()를 통해 새 객체를 반환하므로 사이드 이펙트가 줄고, Lombok 어노테이션 프로세서 의존이 제거됩니다.


2. 단일 모듈 → Gradle 멀티모듈 (피처 우선 분리)

레이어 우선 분리가 아닌 피처 우선 분리를 채택했습니다.

project/
├── shared/
├── product/
│   ├── model/           # 순수 Kotlin, Spring/JPA 의존 없음
│   ├── infrastructure/  # 아웃바운드 포트 인터페이스
│   ├── service/         # 유스케이스 (interface + internal 구현체)
│   ├── repository-jpa/  # JPA 어댑터
│   ├── api/             # REST 컨트롤러 + DTO
│   └── schema/          # SQL 마이그레이션 파일만
├── cart/  (동일 구조)
├── user/  (동일 구조)
└── application-api/     # 전체 모듈 조립 진입점
단일 모듈 멀티모듈
경계 강제 규칙 (convention) 빌드 실패 (compile-time)
도메인 순수성 실수로 오염 가능 Unresolved reference로 즉시 차단
테스트 격리 전체 컨텍스트 스캔 모듈별 classpath 분리

3. CartItem의 ProductEntity 직접 참조 제거

// Before
class CartItemEntity {
    @ManyToOne
    val product: ProductEntity
}

// After
class CartItemEntity {
    val productId: Long
}

cart가 필요한 상품 정보의 형태를 스스로 정의(ProductSummary)하고, 조회 방법은 ProductQueryPort 인터페이스로 추상화했습니다.

cart:model          → product 모름  ✓
cart:service        → product 모름 (ProductQueryPort 인터페이스만)  ✓
cart:repository-jpa → product:infrastructure (인터페이스만 의존)  ✓

4. ProductQueryPort/Adapter를 cart 모듈로 이동

기존에 product:repository-jpacart:modelProductSummary를 반환하면서 product 모듈이 cart 모듈을 알게 되는 역방향 의존이 존재했습니다.

Before: product:repository-jpa → cart:model (역방향)  

After:
cart:infrastructure  → ProductQueryPort 정의 (cart가 필요한 계약을 cart가 정의)
cart:repository-jpa  → ProductQueryAdapter 구현 (product:infrastructure 인터페이스만 의존)
product 모듈         → cart 모름  

5. UseCase 과분리 → Service 인터페이스 통합

// Before
GetProductsUseCase, GetProductUseCase
GetCartUseCase, AddCartItemUseCase, UpdateCartItemUseCase ...

// After
interface CartService {
    fun getCart(userId: Long): CartView
    fun addItem(userId: Long, productId: Long, quantity: Int): CartView
    fun updateItemQuantity(userId: Long, cartItemId: Long, quantity: Int): CartView
    fun deleteItem(userId: Long, cartItemId: Long): CartView
    fun clearCart(userId: Long)
}

구현체는 internal class로 선언해 모듈 외부에서 직접 접근 불가. 외부는 인터페이스만 사용합니다.


6. DTO를 api 레이어에 위치

도메인 객체에 직렬화 어노테이션이 침투하지 않도록 DTO는 api 서브모듈에만 존재합니다.
변환 로직은 컨트롤러 내 확장 함수(private fun Domain.toResponse())로 처리합니다.


7. schema 모듈 = SQL 마이그레이션 전용

각 피처가 자신의 테이블 스키마를 소유합니다.

product/schema/.../V1__create_products.sql
user/schema/.../V2__create_users.sql
cart/schema/.../V3__create_carts.sql

빌드 이슈 해결 내역

이슈 원인 해결
Gradle GAV 충돌 모든 서브모듈이 동일 group:name:version allprojects에서 도메인별 group 분리
IntelliJ 런타임 ClassNotFoundException implementation이 IntelliJ에서 전이 안 됨 application-api에 runtimeOnly로 명시
Flyway 미실행 (Spring Boot 4.x) auto-configuration 모듈 분리 spring-boot-flyway, flyway-database-postgresql 추가
Kotlin 스마트 캐스트 에러 외부 모듈 val은 스마트 캐스트 불가 지역 변수로 먼저 추출 후 사용
kotlin-reflect 누락 Spring Data JPA가 Kotlin 생성자 탐색 시 필요 application-api에 명시적 추가

Test plan

  • GET /products 전체 조회 (커서 페이지네이션)
  • GET /products/{id} 상세 조회
  • POST /carts/items 상품 담기
  • PATCH /carts/items/{id} 수량 변경
  • DELETE /carts/items/{id} 아이템 삭제
  • DELETE /carts 장바구니 초기화
  • 존재하지 않는 유저/상품/카트 아이템 → 404 응답 확인

yuki1256 and others added 30 commits May 1, 2026 15:53
- GetPageInfoForWeb → GetSearchResult 엔드포인트로 변경
- sellable_status 필드로 품절 필터 수정
- thumbnail_nudge_badge_list 서브필드 누락 오류 수정
- 홈 피드 탐색(fetch_home)과 카테고리 크롤링(fetch_page) 함수 분리
- zigzag_final_list.csv, zigzag_100k.csv, benchmark 결과 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
[data] 무신사 데이터 크롤링
[data] 지그재그 데이터 크롤링
* tags, description 컬럼 제거
* 숫자 컬럼 결측값 0으로 처리
* 에이블리 재수집 데이터로 병합
* 29cm 재수집 및 전처리된 데이터로 병합
* 크림 쇼핑물 데이터 추가
@leepg038292 leepg038292 self-assigned this Jun 28, 2026
@leepg038292 leepg038292 added Refactor 코드 개선 평강 평강작업 labels Jun 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Refactor 코드 개선 평강 평강작업

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants