실생활에서의 포트 및 어댑터 아키텍처 (자바 사용)
카테고리
프로그래밍/소프트웨어 개발
서브카테고리
아키텍처 패턴
대상자
- 개발자 및 소프트웨어 아키텍처 담당자
- 중간~고급 수준의 Java 개발자
- 아키텍처 패턴 이해 및 실무 적용에 관심 있는 개발자
핵심 요약
- 포트 및 어댑터 아키텍처는 비즈니스 로직(핵심)을 외부 의존성(데이터베이스, UI 등)과 완전히 분리하여 유연성과 테스트 가능성을 극대화
- 포트(인터페이스)는 구현 방식과 무관한 추상 계약으로, 어댑터(구현체)는 포트에 맞는 구체 기술(예: 인메모리 DB, REST API)을 연결
- 자바 예제에서
TaskRetrievalPort
인터페이스와InMemoryTaskAdapter
를 통해 비즈니스 로직(TaskService)이 외부 기술 변화에 영향을 받지 않도록 설계
섹션별 세부 요약
1. 전통적인 계층 아키텍처의 문제점
- UI → Service → Repository → DB 구조로 인해 레이어 간 강한 결합 발생
- ORM 엔티티(예: JPA)가 서비스/UI 계층에 스며들어 비즈니스 로직과 인프라 구현 세부 사항이 혼합
- 테스트 어려움: 비즈니스 로직이 데이터베이스 접근 로직과 엮여 독립적인 테스트 불가능
- 인프라 변경 시 모든 레이어 수정 필요 → 비용 및 복잡성 증가
2. 포트 및 어댑터 아키텍처의 핵심 원리
- 핵심 로직(비즈니스 로직)은 포트(인터페이스)에 의존, 구현 세부 사항과 분리
- 포트: 외부 시스템과의 상호작용 규칙 정의 (예:
TaskRetrievalPort
) - 어댑터: 포트에 맞는 구체 기술 구현 (예:
InMemoryTaskAdapter
) - Configurator: 런타임 시 포트와 어댑터 연결 (예:
TaskServiceConfigurator
)
3. 자바 예제: Task Manager App 구현
- 포트 정의:
```java
public interface TaskRetrievalPort {
List
```
- 어댑터 구현:
```java
public class InMemoryTaskAdapter implements TaskRetrievalPort {
private final Map
```
- 비즈니스 로직:
```java
public class TaskService {
private final TaskRetrievalPort taskPort;
public TaskService(TaskRetrievalPort taskPort) { this.taskPort = taskPort; }
```
- Configurator:
```java
public class TaskServiceConfigurator {
public static TaskService createDefaultService() {
TaskRetrievalPort adapter = new InMemoryTaskAdapter();
return new TaskService(adapter);
```
- 테스트 유연성:
- 단위 테스트: TaskRetrievalPort
인터페이스를 모킹 가능
- 적합성 테스트: TestContainers
등으로 어댑터 별도 테스트 가능
4. 아키텍처의 주요 장점
- 인프라 결정 연기: 비즈니스 로직과 기술 선택을 분리
- 변화 대응 용이: DB 인메모리 → 실제 DB 전환 시 TaskService 변경 없이 어댑터만 교체 가능
- 테스트 간결성: 포트 인터페이스 기반 테스트로 비즈니스 로직과 인프라 로직 분리
결론
- 포트 및 어댑터 아키텍처는 비즈니스 로직의 순수성과 테스트 가능성을 보장하며, 기술 변화에 대한 유연성 제공
- 예제에서는
TaskRetrievalPort
인터페이스와InMemoryTaskAdapter
를 통해 비즈니스 로직이 외부 기술에 의존하지 않도록 설계 가능 - 실무 적용 시 기존 레이어 아키텍처를 포트-어댑터로 전환해 변경 관리 효율성 극대화