헥사고날 아키텍처와 MSA에서 Feature 중심 설계: Port와 Adapter를 통한 상호작용
헥사고날 아키텍처와 마이크로서비스 아키텍처(MSA)를 결합하면 시스템의 복잡성을 효과적으로 관리할 수 있습니다. 이 글에서는 Feature 중심 설계와 Port와 Adapter를 활용한 Feature 간 상호작용 방법을 설명합니다.
헥사고날 아키텍처와 MSA의 결합
헥사고날 아키텍처는 애플리케이션을 Core Domain과 외부 시스템으로 명확히 구분합니다. 모든 외부와의 상호작용은 Port와 Adapter를 통해 이루어지며, Core Domain은 외부 의존성 없이 비즈니스 로직을 중심으로 설계됩니다.
MSA는 애플리케이션을 독립적으로 배포 가능한 작은 서비스로 나누는 설계 방식으로, 각각의 서비스는 자율적으로 동작할 수 있습니다. 헥사고날 아키텍처와 MSA를 결합하면 다음과 같은 장점이 있습니다:
독립성: 각 Feature는 독립적으로 개발 및 배포할 수 있는 자체적인 Core Domain, Port, Adapter를 가집니다.
유연성: 새로운 Feature 추가나 기존 Feature 변경 시 다른 Feature에 미치는 영향을 최소화할 수 있습니다.
확장성: 다양한 Adapter(JPA, REST, gRPC 등)를 통해 기술적인 확장이 용이합니다.
Feature 간 상호작용: Port와 Adapter
MSA 환경에서 Feature 간 상호작용은 필수적입니다. 이때 Port와 Adapter를 사용하여 Feature 간 결합도를 낮추고 의존성을 관리합니다. 예를 들어, Notification
Feature가 User
Feature의 정보를 필요로 할 때 두 Feature는 직접 연결되지 않고 Port와 Adapter를 통해 상호작용합니다.
설계 원칙
Port 중심 상호작용:
Feature 간 데이터 요청은 Outbound Port를 통해 이루어집니다.
Outbound Adapter는 다른 Feature의 Inbound Port를 호출하여 데이터를 가져옵니다.
Feature 간 의존성 최소화:
한 Feature는 다른 Feature의 내부 구현을 알지 못하도록 설계해야 합니다.
상호작용은 Interface(Port)를 통해 추상화합니다.
기술 독립성 보장:
REST, gRPC, 메시징 등 다양한 통신 기술은 Adapter에서 처리하고, Core Domain은 이를 알 필요가 없습니다.
실무 예제: Notification과 User Feature 간 상호작용
아래 예제는 Notification
Feature와 User
Feature 간의 상호작용을 Port와 Adapter를 통해 처리하는 방식을 보여줍니다.
Notification Feature의 Outbound Port
Notification
Feature는 User
Feature와의 상호작용을 위해 Outbound Port를 정의합니다. 이를 통해 Notification
Feature는 User
Feature의 내부 구현을 알 필요가 없습니다.
public interface UserClientPort {
UserResponse getUser(String userId);
}
User Feature의 Adapter
User
Feature는 Notification
Feature의 Outbound Port를 구현하여 데이터를 제공합니다. 이 Adapter는 User
Feature 내에 위치하며, Notification
Feature에서 호출됩니다.
@Component
public class UserClientAdapter implements UserClientPort {
private final UserServicePort userServicePort; // User Feature의 Inbound Port
public UserClientAdapter(UserServicePort userServicePort) {
this.userServicePort = userServicePort;
}
@Override
public UserResponse getUser(String userId) {
return userServicePort.getUserById(userId);
}
}
User Feature의 Inbound Port
User
Feature는 내부 데이터를 외부에 제공하기 위해 Inbound Port를 정의합니다. 이 Port는 User
Feature의 Core Domain과 외부 시스템 간의 추상화된 인터페이스 역할을 합니다.
public interface UserServicePort {
UserResponse getUserById(String userId);
}
User Feature의 Inbound Port 구현체
UserServicePort
의 구현체는 User
Feature의 데이터 접근 로직을 처리하며, JPA Repository를 사용해 데이터를 관리합니다.
User Feature의 Repository
JPA를 활용하여 User 데이터를 관리하는 Repository는 다음과 같이 정의됩니다:
Notification Feature의 서비스
Notification
Feature는 Outbound Port를 통해 User
Feature와 상호작용하며, 비즈니스 로직을 처리합니다.
클래스 간 관계 도식화
설명
NotificationService:
NotificationService
는 비즈니스 로직에서UserClientPort
를 통해 User 정보를 요청합니다.
UserClientPort:
Notification
Feature의 Outbound Port로서,UserClientAdapter
의 호출 인터페이스 역할을 합니다.
UserClientAdapter:
UserClientPort
를 구현하며,UserServicePort
를 호출하여 User 정보를 가져옵니다.
UserServicePort:
User
Feature의 Inbound Port로서 외부 요청을 처리합니다.
UserServiceImpl:
UserServicePort
의 구현체로, 비즈니스 로직을 처리하며 JPA Repository(UserRepository
)와 상호작용합니다.
추가 설명: 인터페이스와 Port
**인터페이스(interface)**는 추상화를 제공하는 도구이지만, 무조건 Port로 간주할 수는 없습니다. 헥사고날 아키텍처에서 Port는 Core Domain의 경계를 정의하며 외부와의 상호작용을 추상화하는 데 사용됩니다.
UserRepository는 JPA의 인터페이스를 확장한 기술적 세부사항을 포함하며, 이는 헥사고날 아키텍처에서 Adapter의 역할에 해당합니다. Core Domain은
UserRepository
를 직접 알지 못하며, 대신 추상화된 Outbound Port를 통해 접근합니다.
Port와 Adapter를 사용한 설계의 장점
결합도 감소:
Feature 간 직접적인 의존성을 제거하고, Port를 통해 간접적으로 상호작용합니다.
독립적 개발과 배포:
각 Feature는 별도의 배포 단위로 관리할 수 있습니다.
테스트 용이성:
Mock을 활용해 다른 Feature와의 상호작용 없이 단위 테스트가 가능합니다.
기술 독립성:
REST, gRPC, 메시징 등 다양한 통신 방식을 Adapter에서 처리하여 Core Domain은 기술 변경에 영향을 받지 않습니다.
결론
헥사고날 아키텍처에서 MSA와 Feature 중심 설계를 도입하면 복잡한 시스템에서 유지보수성과 확장성을 크게 향상시킬 수 있습니다. Feature 간 상호작용을 Port와 Adapter로 처리함으로써 결합도를 낮추고 독립적인 개발과 배포가 가능합니다.
이 설계는 특히 Feature 간 상호작용이 많은 대규모 시스템에서 강력한 효과를 발휘하며, 테스트와 기술 변경에도 유연하게 대응할 수 있도록 도와줍니다.