Hexagonal Architecture 정리
이 글은 헥사고날 아키텍처를 다시 볼 때 핵심 개념부터 포트/어댑터 흐름까지 한 번에 훑기 좋게 정리한 메모입니다.. 실무에서 바로 구현할 때 어디를 도메인으로 두고 어디를 어댑터로 빼야 하는지 감이 안 올 때 다시 보는 용도에 가깝습니다.
1. 구조
헥사고날 아키텍처(포트와 어댑터 아키텍처)의 주요 구성 요소:
- 도메인: 핵심 비즈니스 로직
- 포트: 인터페이스
- 어댑터: 외부 시스템과의 연결
┌─────────────────────┐
│ 외부 │
│ ┌───────────┐ │
│ │ 어댑터 │ │
│ └─────┬─────┘ │
│ │ │
┌─────────────────────────────┐
│ │ 포트 │ │
│ └─────┬─────┐│ │
│ │ ││ │
│ ┌───▼───┐ ││ │
│ │도메인 │ ││ │
│ └───┬───┘ ││ │
│ │ ││ │
│ ┌─────▼─────┘│ │
│ │ 포트 │ │
└─────────────────────────────┘
│ │ │
│ ┌─────▼─────┐ │
│ │ 어댑터 │ │
│ └───────────┘ │
│ 외부 │
└─────────────────────┘
2. MVC와의 차이
MVC (Model-View-Controller)
- 프레젠테이션 계층 중심
- 비즈니스 로직과 데이터 접근 로직 혼재 가능
- 구현이 간단함
헥사고날 아키텍처
- 비즈니스 로직 중심 설계
- 외부 의존성으로부터 코어 로직 보호
- 복잡하지만 유지보수와 테스트 용이
3. 장단점
장점
- 높은 모듈성과 유연성
- 비즈니스 로직의 독립성 보장
- 테스트 용이성
- 기술 스택 변경 용이
단점
- 초기 설계와 구현이 복잡
- 소규모 프로젝트에는 과도할 수 있음
- 높은 러닝 커브
4. 자바-스프링 구현
- Domain: 순수 자바 객체 (POJO)
- Ports: 자바 인터페이스
- Adapters:
@Controller@Repository- 외부 API 클라이언트 등
- 의존성 주입(DI)으로 포트와 어댑터 연결
5. 학습 수준
- 초급: 기본 개념 이해
- 중급: 실제 프로젝트 적용, 포트와 어댑터 설계
- 고급: 복잡한 비즈니스 로직 구현, 성능 최적화
6. 구축 과제 적용 시 고려사항
- 프로젝트 규모와 복잡성 평가
- 팀의 기술 수준 고려
- 점진적 도입 전략 수립
- 기존 코드베이스와의 통합 방안 마련
- 테스트 전략 수립 (단위 테스트, 통합 테스트)
ports and adapters
1. 포트 (Ports)
포트는 애플리케이션의 외부와 내부를 연결하는 인터페이스입니다.
1.1 인바운드 포트 (Inbound Ports)
- 정의: 애플리케이션이 외부에서 호출되는 방식을 정의
- 예시:
MemberLookupUseCase,MemberResisterUseCase - 특징:
- 주로 유스케이스(use case) 인터페이스로 구현
- 애플리케이션의 기능을 추상화
public interface MemberLookupUseCase {
Member findMemberById(Long id);
}
1.2 아웃바운드 포트 (Outbound Ports)
- 정의: 애플리케이션이 외부 시스템을 호출하는 방식을 정의
- 예시:
MemberRepositoryAdapter - 특징:
- 데이터 영속성, 외부 API 호출 등을 추상화
- 구체적인 구현은 어댑터에서 담당
public interface MemberRepositoryAdapter {
Member save(Member member);
Optional<Member> findById(Long id);
}
2. 어댑터 (Adapters)
어댑터는 포트 인터페이스의 실제 구현체입니다.
2.1 인바운드 어댑터 (Inbound Adapters)
- 정의: 외부 요청을 애플리케이션의 포트로 변환
- 예시:
MemberController,MemberControllerAdvice - 특징:
- 주로 웹 컨트롤러, 메시지 리스너 등으로 구현
- 요청을 받아 적절한 포트(유스케이스)를 호출
@RestController
public class MemberController {
private final MemberLookupUseCase memberLookupUseCase;
@GetMapping("/members/{id}")
public ResponseEntity<Member> getMember(@PathVariable Long id) {
return ResponseEntity.ok(memberLookupUseCase.findMemberById(id));
}
}
2.2 아웃바운드 어댑터 (Outbound Adapters)
- 정의: 애플리케이션의 요청을 외부 시스템에 맞게 변환
- 예시:
MemberRepositoryAdapterImpl,ExternalApiAdapter - 특징:
- 데이터베이스 연동, 외부 API 호출 등을 구현
- 아웃바운드 포트 인터페이스를 구현
@Repository
public class MemberRepositoryAdapterImpl implements MemberRepositoryAdapter {
private final JpaMemberRepository jpaMemberRepository;
@Override
public Member save(Member member) {
MemberEntity entity = MemberMapper.toEntity(member);
return MemberMapper.toDomain(jpaMemberRepository.save(entity));
}
}
3. 주요 개념 관계
- 도메인 중심: 모든 포트와 어댑터는 도메인 모델을 중심으로 동작합니다.
- 의존성 방향: 어댑터 → 포트 → 도메인 (의존성은 항상 안쪽으로)
- 교체 가능성: 어댑터는 쉽게 교체 가능해야 합니다 (예: JPA → MongoDB)
- 테스트 용이성: 포트를 통해 모의 객체(mock)를 쉽게 만들 수 있습니다.
4. 구현 시 주의사항
- 포트는 도메인 언어로 정의해야 합니다.
- 어댑터는 특정 기술에 종속적일 수 있지만, 포트는 기술 중립적이어야 합니다.
- 동일한 포트에 대해 여러 어댑터를 구현할 수 있어야 합니다.
- 도메인 로직은 포트와 어댑터에 의존하지 않아야 합니다.
1. 전체 구조
kr.co.visibleray.prop
├── application
│ ├── port
│ │ ├── inbound
│ │ └── outbound
│ └── usecase
├── domain
│ ├── enumeration
│ ├── exception
│ └── model
├── infrastructure
│ └── adapter
│ ├── inbound
│ └── outbound
└── util
2. 주요 패키지 설명
2.1 도메인 계층 (Domain Layer)
위치: kr.co.visibleray.prop.domain
enumeration: 도메인 관련 열거형ResultCode.java: 결과 코드 정의
exception: 도메인 특화 예외ApplicationException.java: 애플리케이션 레벨 예외InvalidEmailFormatException.java: 이메일 형식 관련 예외
model: 도메인 모델 클래스들Email.java,FileInfo.java,RecodedDateTime.java등: 값 객체member/Member.java: 회원 도메인 모델organization/Organization.java: 조직 도메인 모델
2.2 애플리케이션 계층 (Application Layer)
위치: kr.co.visibleray.prop.application
portinbound: 인바운드 포트 (유스케이스 인터페이스)MemberLookupUseCase.javaMemberResisterUseCase.java
outbound: 아웃바운드 포트MemberPersistenceAdapter.java
usecase: 유스케이스 구현MemberLookupUseCaseImpl.javaMemberResisterUseCaseImpl.java
2.3 인프라스트럭처 계층 (Infrastructure Layer)
위치: kr.co.visibleray.prop.infrastructure
adapterinboundrest: REST API 관련 구성aop: 예외 처리 AOPconfig: 보안 및 Swagger 설정constants: API 상수controller: REST 컨트롤러dto: 데이터 전송 객체filter: 요청 필터
outboundpersistence: 데이터 영속성 관련config: QueryDSL 설정member: 회원 관련 영속성 구현organization: 조직 관련 영속성 구현
2.4 유틸리티 (Utility)
위치: kr.co.visibleray.prop.util
CollectionUtils.java: 컬렉션 관련 유틸리티StringUtils.java: 문자열 관련 유틸리티
3. 주요 컴포넌트 설명
3.1 인바운드 포트 (Inbound Ports)
MemberLookupUseCase.java: 회원 조회 유스케이스MemberResisterUseCase.java: 회원 등록 유스케이스
이들은 애플리케이션의 주요 기능을 정의하는 인터페이스입니다.
3.2 아웃바운드 포트 (Outbound Ports)
MemberPersistenceAdapter.java: 회원 정보 영속성 인터페이스
외부 시스템(예: 데이터베이스)과의 상호작용을 추상화합니다.
3.3 유스케이스 구현 (Use Case Implementations)
MemberLookupUseCaseImpl.javaMemberResisterUseCaseImpl.java
인바운드 포트의 실제 구현체로, 비즈니스 로직을 포함합니다.
3.4 인바운드 어댑터 (Inbound Adapters)
MemberController.java: 회원 관련 REST API 컨트롤러
외부 요청을 받아 적절한 유스케이스로 전달합니다.
3.5 아웃바운드 어댑터 (Outbound Adapters)
MemberPersistenceAdapterImpl.java: 회원 정보 영속성 구현
실제 데이터베이스 작업을 수행하는 구현체입니다.
4. 특징 및 장점
- 관심사의 분리: 각 계층이 명확히 구분되어 있어 유지보수가 용이합니다.
- 도메인 중심 설계:
domain패키지에 비즈니스 핵심 로직이 집중되어 있습니다. - 의존성 역전: 애플리케이션 코어(
domain,application)가 외부 계층에 의존하지 않습니다. - 유연성: 인터페이스를 통한 느슨한 결합으로 구현체 교체가 용이합니다.
- 테스트 용이성: 각 계층과 컴포넌트를 독립적으로 테스트할 수 있습니다.
5. 구현 시 주의사항
- 도메인 모델의 순수성 유지:
domain패키지의 클래스들은 외부 의존성이 없어야 합니다. - 포트 인터페이스 설계: 비즈니스 요구사항을 잘 반영하도록 설계해야 합니다.
- 어댑터 구현: 특정 기술에 종속적일 수 있지만, 교체 가능성을 고려해야 합니다.
- 의존성 주입: 구체적인 구현체 대신 인터페이스에 의존하도록 합니다.
Dev flow
1. 도메인 모델 정의 (domain 패키지)
model폴더:-
Member.java -
Organization.java -
Notice.java
-
enumeration폴더:-
FileType.java -
ResultCode.java -
SubscriptionType.java
-
exception폴더:-
ApplicationException.java -
InvalidEmailFormatException.java
-
2. 유스케이스 정의 (application.port.inbound 패키지)
-
MemberLookupUseCase.java -
MemberResisterUseCase.java
3. 영속성 어댑터 인터페이스 정의 (application.port.outbound 패키지)
-
MemberPersistenceAdapter.java
4. 유스케이스 구현 (application.usecase 패키지)
-
MemberLookupUseCaseImpl.java -
MemberResisterUseCaseImpl.java
5. 영속성 계층 구현 (infrastructure.adapter.outbound.persistence 패키지)
member폴더:-
MemberEntity.java -
MemberRepository.java -
MemberRepositoryCustom.java -
MemberRepositoryImpl.java -
MemberPersistenceAdapterImpl.java -
MemberFactory.java
-
6. REST API 컨트롤러 구현 (infrastructure.adapter.inbound.rest.controller 패키지)
-
MemberController.java -
OrganizationController.java -
NoticeController.java -
FileController.java -
ImageController.java -
SubscriptionController.java
7. DTO 클래스 작성 (infrastructure.adapter.inbound.rest.controller.dto 패키지)
request폴더:-
MemberSaveRequest.java -
MemberModifyRequest.java
-
response폴더:-
MemberInfo.java -
PageResult.java
-
8. 설정 및 보안 (infrastructure.adapter.inbound.rest.config 패키지)
-
SecurityConfig.java -
SwaggerConfig.java
9. 예외 처리 (infrastructure.adapter.inbound.rest.aop 패키지)
-
ApplicationExceptionHandler.java -
DefaultExceptionHandler.java
10. 유틸리티 클래스 작성 (util 패키지)
-
CollectionUtils.java -
StringUtils.java
11. 애플리케이션 설정 (resources 폴더)
-
application.yml -
application-local.yml
12. 테스트 코드 작성
- 도메인 모델 테스트
- 유스케이스 테스트
- 영속성 계층 테스트
- 컨트롤러 테스트
- 통합 테스트
개발 순서
- 핵심 도메인 모델 (예:
Member) 부터 시작 - 해당 도메인 관련 유스케이스 정의 및 구현
- 영속성 계층 구현
- REST API 컨트롤러 구현
- 다른 도메인 모델에 대해 1-4 과정 반복
- 전역 설정, 보안, 예외 처리 구현
┌──────────────────────────────────────────────────────┐
│ 개발 흐름 다이어그램
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ 1. 도메인 모델 정의 (domain 패키지)
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ 2. 유스케이스 정의 (application.port.inbound)
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ 3. 영속성 어댑터 인터페이스 (application.port.outbound)
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ 4. 유스케이스 구현 (application.usecase)
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ 5. 영속성 계층 구현 (infrastructure.outbound)
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ 6. REST API 컨트롤러 구현 (infrastructure.inbound)
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ 7. DTO 클래스 작성
│ 8. 설정 및 보안 구현
│ 9. 예외 처리 구현
│ 10. 유틸리티 클래스 작성
│ 11. 애플리케이션 설정
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ 12. 테스트 코드 작성 (각 단계마다 병행)
└──────────────────────────────────────────────────────┘
https://git.hnine.com/HNINE-Ex/prop/prop-api