| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- synchronized
- Spring
- java
- 일급 컬렉션
- OAuth 2.0
- Google OAuth
- builder
- 일급 객체
- spring security
- Volatile
- factory
- Dependency Injection
- lombok
- Today
- Total
HJW's IT Blog
Port & Adapter 패턴을 왜 사용할까 본문
0. 들어가며
Hexagonal 아키텍처의 핵심인 Port & Adapter 패턴을 도입하는 것은, Repository 를 Port(인터페이스)로 추상화 하고, Adapter(구현체)에서 실제 구현을 맡기는 구조이다.
이를 통해, 잦은 요구사항의 변경에도 내성이 강한 코드를 구현할 수 있다.
하지만, 직접 구현해 보며 한가지 의문이 들었다. 단순히 Layered Architecture를 사용하던, Port & Adapter를 사용하던 Datasource 가 바뀌게 되면 코드의 변경이 필수적이라는 생각이 들었다.
1. Adapter의 변경과 서비스의 변경
다음은 현재 사용중인 JPA 기반의 Adapter와 Port이다.
public interface UserRepositoryPort {
User save(User user);
Optional<User> findById(Long id);
}
public class UserJpaRepositoryAdapter implements UserRepositoryPort {
private final UserJpaRepository userRepository;
}
만약 여기서 저장소를 MongoDB, 혹은 Redis로 교체하는 상황이라면 새로운 Adapter (ex. UserMongoRepositoryAdapter)를 구현해야 한다. 이때 새로운 Adapter를 어떻게 서비스에 주입할까?
@Qualifier를 수정하거나, DI 설정을 변경해야 할 것이다.
여기서 딜레마가 발생한다:
- 구현체가 바뀌는 순간 코드의 구성이 바뀐다면 결국 Service 의 변경이 동반되는 것이 아닌가?
- Service 코드가 간접적으로라도 어댑터를 알고 있다면 Port & Adapter 패턴의 기술로부터의 도메인 격리의 의의는 사라지는것이 아닌가?
UserService 코드를 한번 보자.
public class UserService {
private final UserRepositoryPort userRepositoryPort;
}
UserService는 명확하게 UserRepositoryPort라는 인터페이스에만 의존하고 있다. JPA인지, Mongo인지, 기술적인 세부 사항은 전혀 모르는 상태이다.
하지만, 저장소가 교체될때 스프링 컨테이너에 DI를 맡긴다 하더라도, 누군가는 JPA 대신 Mongo 구현체를 사용하라고 지시해야 한다.
이 부분에 대해 많은 자료를 찾아보고 고민을 해본 결과, 다음과 같은 결론을 내렸다.
변경되는 것은 Service 가 아닌 Configuration이다
2. Domain, Infrastructure 그리고 Configuration
지금껏 나는 어플리케이션을 Domain과 Infrastructure 두가지 영역으로 나누어 생각해왔다. 하지만, 헥사고날과 같은 클린 아키텍처가 온전하게 동작하기 위해 제 3의 영역을 고려해야 함을 깨달았다.
각 계층을 다음과 같이 정의해보겠다.
- Service (Domain) : 무엇을 (What) 할지 정의한다
- Adapter (Infrastructure) : 어떻게 (How)를 정의한다
- Configuration : 누구와 누구를 연결할지 정의한다
그렇다면 이전의 의문점인, Adapter를 교체하게 되었을때 변경이 일어나는 지점은 어디일까?
- Adapter 와 Configuration 이며, Service코드는 단 한글자도 수정되지 않는다. 즉, Service의 변경이 없는 DB 교체가 달성될 수 있다.
3. 도메인을 보호한다는 것
클린 아키텍처에서는, 모든 의존성을 주입하고, 시스템을 조립하는 가장 바깥쪽의 컴포넌트를 메인 컴포넌트 라고 한다. Configuration 이 이 메인 컴포넌트의 역할을 수행한다.
결론적으로, 특정 아키텍처를 도입한다고 하여 모든 코드의 수정을 제거할 수 있는것은 아니다. 하지만 중요한것은 변경이 어디까지 영향을 미치는가라고 생각한다.
저장소가 바뀌었을때, Service를 바꿔야 하는지 혹은 DB가 바뀌었을때 추가 구현과 Configuration의 변경만 필요할지 처럼 말이다.
Port & Adapter 패턴을 사용하는것의 의의는 여기에 있다. Configuration을 분리하고, Infrastructure를 분리함으로서, 세부 구현이 바뀌더라도 핵심 자산인 Domain은 영향을 받지 않도록 하기 위함이다.
4. 마무리
다시 처음 질문으로 돌아가 보자.
DB가 바뀌면 결국 코드를 수정해야 하는거 아닌가?
내 대답은 "그렇다". 하지만 수정이 일어나는 위치가 다르다.
Port & Adapter 패턴을 도입하며 얻고자 했던 것은, 변경이 전혀 일어나지 않는 기적이 아니다. 변화가 전파되는 방향을 통제하는 힘을 얻고자 했던 것이다.
개발자로서 언제나 요구사항의 변경, 기술의 발전에 직면하기 마련이다. 이러한 모든 순간에 핵심 비즈니스 로직을 변경하고, 수정하기엔 리스크도 크고 유지보수성도 떨어진다.
Configuration이라는 제 3의 영역을 인지하고, 이를 명확하게 사용함으로써, 비로소 도메인을 온전히 격리할 수 있게 되는 것이다. 이것이 우리가 다소간의 복잡함을 감수하고서라도 아키텍처적 고민을 하고 적용하는 이유라 생각한다.
'개발 개념' 카테고리의 다른 글
| Port & Adapter 패턴 도입기 (1) | 2025.09.07 |
|---|---|
| 3 Layered vs Clean Architecture (0) | 2025.01.31 |
| PRG Pattern 은 왜 사용할까? (0) | 2025.01.16 |
| Builder Pattern 에 대한 고찰 (0) | 2025.01.10 |
| SOLID 와 TradeOff (0) | 2025.01.09 |