헥사고날 아키텍처 도입
- 데이터베이스 변경 리펙토링 과정에서 두가지 문제점을 발견했습니다.
- DB 엔티티와 도메인 로직의 결합
- 서비스 로직과 데이터베이스 강결합
- 두 문제점을 통해 DDD와 Clean Architecture의 필요성을 느꼈습니다.
- 이를 구현하기 위해 헥사고날 아키텍처를 도입하기로 결정했습니다.
- 레이어드 아키텍처 → 헥사고날 아키텍처
- 기존 패키지 구조
- controller
- service
- entity
- repository
- dto
헥사고날 아키텍처 적용 범위
- 헥사고날 아키텍처의 핵심은 포트와 어댑터를 통해 각 계층별 의존성을 줄여 변경이나 확장에 유리하도록 하는 것 입니다.
- 따라서 아래와 같은 구조로 설계할 계획을 세웠습니다.
- 패키지 구조
- controller - controller.java - dto - port - service.interface - service - serviceImpl.java - domain - domain.java - port - repository.interface - infrastructure - repository - repositoryImpl.java - entity - entity.java
- 하지만 해당 구조로 설계를 할 경우, 각 계층별 의존성을 낮추기 위해 dto를 controller → service, service → controller로 직접 줄 수 없습니다. service → repository, repository → service 역시 마찬가지 입니다.
- 이를 해결하기 위해서는 각 계층별로 데이터를 받을 수 있도록 포트 모델(in, out)를 설계해야합니다. 하지만 이 경우 현재 프로젝트에서 과하다는 판단이 들었습니다.
- 현재 프로젝트에서 문제점은 엔티티와 도메인의 결합, 서비스로직과 데이터베이스의 강결합입니다.
- 다시말해 도메인과 핵심 서비스 로직을 외부로부터 독립시켜야 합니다.
- 따라서 다음과 같이 설계하기로 했습니다.
- infrastructure 계층만 헥사고날 아키텍처를 도입한다.
- service, domain은 변경 가능성이 낮다고 판단.
- 포트 모델(in, out)를 설계 X
- controller 패키지에 있는 dto 패키지를 밖으로 뺀다.
- request dto는 controller에서 데이터를 받아서 service까지 넘겨주는 역할로 둔다.
- response dto는 service에서 생성하여 controller로 넘겨준다.
- dto가 엔티티나 도메인에 직접적인 의존성을 가지지 않도록 한다.
- dto의 값 하나하나를 직접 넘겨주거나, 중간 매퍼 클래스를 이용한다.
- 도메인에서는 어떤 외부 정보도 모르게 한다.
- dto, entity를 직접 사용하지 않도록 하기
- service, mapper를 제외한 어떠한 외부에서 도메인의 존재를 모르게 한다.
- 수정된 패키지 구조
- controller - controller.java - dto - mapper - service - serviceImpl.java - domain - domain.java - port - repository.interface - infrastructure - repository - repositoryImpl.java - entity - entity.java
- infrastructure 계층만 헥사고날 아키텍처를 도입한다.
infrastructure(repository)만 의존성 역전을 한 이유
- 서비스의 핵심은 비즈니스 로직과 도메인 로직
- 제공하는 서비스의 본질은 비즈니스 로직(service)과 도메인 로직(domain)에 있으며, 이는 구체적으로 구현되어야 합니다.
- 추상화된 service 인터페이스를 도입하더라도, 비즈니스 로직이 변경되면 해당 인터페이스와 구현체 모두 수정해야 하기 때문에, 현재 규모에서는 오히려 불필요한 복잡성을 초래한다고 판단했습니다.
- 프로젝트 규모에 적합한 설계
- 현재 프로젝트에서는 한 도메인에 대해 기능을 세분화하거나 여러 구현체로 나누어 개발하지 않습니다.
- 따라서, service를 추상화하지 않아도 충분히 유연성을 확보할 수 있습니다.
- 단방향 의존성 유지
- controller → service → domain으로 이어지는 단방향 의존성은 이미 잘 유지되고 있습니다.
- controller와 service를 추가로 추상화하지 않아도, 외부 → 내부로만 흐르는 단방향 설계가 보장됩니다.
- 도메인 독립성 유지
- 도메인 계층은 여전히 외부 기술(infrastructure)이나 구체적인 구현(repository)에 영향을 받지 않고, 비즈니스 로직만 처리하도록 독립성을 유지합니다.
- 테스트 용이성
- repository에 대해 의존성 역전을 통해 service 계층의 테스트에서 H2 등 데이터베이스 없이도 단위 테스트가 가능하도록 했습니다.
DTO 패키지를 따로 분리한 이유
- 서비스에서 제공하는 데이터(response DTO)와 요청받는 데이터(request DTO)는 서비스가 제공하거나 처리해야 할 구체적인 정보입니다.
- 요청과 응답 데이터의 형식은 서비스의 책임과 긴밀하게 연결되어 있으므로, DTO와 서비스 간 의존성이 생기는 것이 괜찮다고 생각했습니다.
- 예를 들어, 요청 데이터가 변경되거나 서비스에서 제공하는 응답 데이터가 수정된다면, 이는 비즈니스 요구사항의 변경을 의미하므로, 해당 서비스 로직도 함께 변경되어야 합니다.
- DTO는 service 계층까지만 의존성을 가져야 합니다.
- 이는 도메인 영역을 외부로부터 완전히 독립시켜, 변경 가능성을 최소화하기 위함입니다.
- 도메인 모델은 비즈니스 로직에만 집중해야 하며, 외부 요청이나 응답 형식(DTO)에 종속되지 않아야 합니다.
이미지 출처 : https://www.inflearn.com/course/자바-스프링-테스트-개발자-오답노트/dashboard 강의자료
'개발' 카테고리의 다른 글
테스트 코드 작성중 데이터 삭제 방식 결정: delete VS truncate (0) | 2025.02.10 |
---|---|
친구 목록 동기화 시 기존 데이터를 업데이트하지 않고 새로운 데이터로 저장되는 문제 (0) | 2025.01.17 |
DDD(Domain-Driven Design)와 Clean Architecture의 원칙을 반영한 이유 (6) | 2025.01.10 |
친구 정보, Redis에서 MySQL로 옮긴 이유 (0) | 2025.01.10 |
permitAll()로 요청한 api들이 JwtFilter를 거쳐서 가는 문제 (0) | 2024.04.29 |