프로젝트

[Project] 4dollar - 경계간 매핑하기

덴마크초코우유 2025. 5. 12. 19:50
반응형

시작하며

헥사고날 아키텍처를 사용해보고 싶어서 시작한 사이드 프로젝트

  • 책이나 기술블로그, 세미나 등등 다양한 경로를 통해 접해봤지만 실제 적용해본 적은 없어서 코드를 작성해보며 체득하고 싶었다. 업무에서는 정해진 컨벤션이 있어서 시도해보기 어려워서 사이드 프로젝트에 적용해보면서 어떤 장점이 있는지 공부해보고 싶었다.
  • 대략적인 개념은 만들면서 배우는 클린 아키텍처 도서를 보면서 공부했다. 얇은 책이지만 예시코드가 잘 나와있어서 기본적인 갈피를 잡는데 많은 도움이 됐다.
  • 나름 DDD도 열심히 공부했는데 코드에 녹여보고 싶다.
  • 기술스택 : Spring Boot, Spring Data JPA, Postresql
    • 기회가 되면 redis도…
    • 여유가 있으면 프론트 작업도 하면 재미있을 것 같다.

패키지 구성 - 카테고리 도메인

패키지 구조에 익숙하지 않아 간단한 기능부터 구현을 시작했다. 상품이 속한 카테고리를 표현하는 카테고리 도메인부터 구성했다. 상품 도메인을 구현할 때 필요하기도 하고 등록, 조회 정도면 충분하기 때문에 시작하기 좋을 것 같았다.

먼저 패키지 구조를 다음과 같이 구성했다.

📦fourdollar
┗ 📂category
  ┣ 📂adapter
  ┃ ┣ 📂in
  ┃ ┃ ┗ 📂web
  ┃ ┃   ┗ 📜CategoryRestController
  ┃ ┗ 📂out
  ┃   ┗ 📂persistence
  ┃     ┣ 📂jpa
  ┃     ┃  ┣ 📜CategoryJpaEntity
  ┃     ┃  ┗ 📜CategoryJpaRepository
  ┃     ┗ 📜CategoryPersistenceAdapter
  ┣ 📂domain
  ┃ ┗ 📜Category
  ┗ 📂application
    ┣ 📜RegisterCategoryService // 유즈케이스를 구현하는 서비스들
      ┣ 📜GetCategoryListService
    ┗ 📂port
      ┣ 📂in
      ┃ ┣ 📜RegisterCategoryUseCase
      ┃ ┗ 📜GetCategoryListUseCase
      ┗ 📂out
        ┣ 📜SaveCategoryPort
        ┗ 📜LoadCategoryPort

아직 시작단계이지만 이렇게 구성해보니 이런 점이 좋았다.

  • 각 계층의 역할이 명확해져서 "이 코드를 어디에 작성해야 할까?" 하는 고민이 줄어들었다. 비즈니스 로직은 도메인에, 외부 연동은 어댑터에
  • Repository를 세분화해서 서비스에서는 필요한 부분만 사용할 수 있었다. 테스트 코드 작성도 쉬워졌다.

하지만 패키지나 클래스를 너무 세분화해서 파일이 너무 많아져 귀찮은 부분도 있었다. 특히 각 계층마다 사용하는 dto 클래스를 정의했는데, 계층간 변환을 위한 매퍼 메서드 구현이 반복되는게 너무 번거로웠다.

마침 읽고 있던 클린 아키텍처 도서에 관련 내용이 나와있어서 나름 타협점을 찾을 수 있었다.

매핑 전략 선택하기

카테고리에 대한 유스게이스 별 경계간 매핑을 전략을 다르게 구성했다. 기본적으로 모든 유스케이스에 대해 애플리케이션 계층 ↔ 영속성 계층간 매핑은 양방향 매핑을 사용했다.

  • 유스케이스마다 별도의 모델이 필요없이 도메인모델과 JPA 엔티티간 변환만 이루어지면 충분하다.
  • JPA의 복잡한 연관관계와 도메인 로직을 분리하여 각 계층의 책임을 명확히 한다.

카테고리 등록

  • 웹 계층 ↔ 애플리케이션 계층 : 완전 매핑
    • 웹 계층과 도메인 로직을 분리한다.
    • 유스케이스간 결합도를 없앤다.(ex: 수정 유스케이스에서는 별도의 클래스를 만든다)
  • 애플리케이션 계층 ↔ 영속성 계층 : 양방향 매핑

// RegisterCategoryRequest는 웹 요청에 집중
// RegisterCategoryCommand는 비즈니스 로직에 집중
@PostMapping
RegisterCategoryResponse registerCategory(@RequestBody RegisterCategoryRequest request) {
    return new RegisterCategoryResponse(registerCategoryUseCase.registerCategory(
            new RegisterCategoryCommand(
                    request.parentId(),
                    request.name(),
                    request.description(),
                    request.displayOrder()
            )
    ));
}

카테고리 조회

  • 웹 계층 ↔ 애플리케이션 계층 : 매핑하지 않기
    • 조회의 경우 단순하게 유지해도 충분하다.
    • 불필요한 객체 생성이나 추가 매핑 코드가 없다.
  • 애플리케이션 계층 ↔ 영속성 계층 : 양방향 매핑
// CategoryDetail은 애플리케이션 계층의 모델을 그대로 사용한다.
@GetMapping("/{categoryId}")
CategoryDetail getCategory(@PathVariable Long categoryId) {
    return getCategoryUseCase.getCategory(categoryId);
}

현재 상품 도메인을 작업 중인데 JPA 엔티티를 도메인 모델로 사용할지 고민 중이다. JPA를 사용할 예정이고 다른 기술로 교체하지 않을 텐데 굳이 귀찮게 모델을 나누고 영속성 컨텍스트를 활용하지 못하는게 아쉽다. 이 부분은 좀 더 고민해보고 진행해야겠다.

반응형