헥사고날 아키텍처와 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 간 데이터 요청은 Inbound 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를 통해 처리하는 방식을 보여줍니다.
User Feature의 Inbound Port
public interface UserServicePort { UserResponse getUserById(String userId); }
User Feature의 Adapter
@Service public class UserServiceAdapter implements UserServicePort { private final UserRepository userRepository; public UserServiceAdapter(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserResponse getUserById(String userId) { User user = userRepository.findById(userId) .orElseThrow(() -> new RuntimeException("User not found")); return new UserResponse(user.getId(), user.getName(), user.getEmail()); } }
Notification Feature의 Outbound Port
public interface UserClientPort { UserResponse getUser(String userId); }
Notification Feature의 Outbound Adapter
@Component public class UserClientAdapter implements UserClientPort { private final RestTemplate restTemplate; @Override public UserResponse getUser(String userId) { return restTemplate.getForObject("http://user-service/users/" + userId, UserResponse.class); } }
Notification Feature의 서비스
@Service public class NotificationService { private final UserClientPort userClientPort; public NotificationService(UserClientPort userClientPort) { this.userClientPort = userClientPort; } public NotificationResponse sendNotification(String userId, String message) { UserResponse user = userClientPort.getUser(userId); // 비즈니스 로직 수행 return new NotificationResponse("Sent to " + user.getEmail()); } }
Port와 Adapter를 사용한 설계의 장점
결합도 감소:
Feature 간 직접적인 의존성을 제거하고, Port를 통해 간접적으로 상호작용합니다.
독립적 개발과 배포:
각 Feature는 별도의 배포 단위로 관리할 수 있습니다.
테스트 용이성:
Mock을 활용해 다른 Feature와의 상호작용 없이 단위 테스트가 가능합니다.
기술 독립성:
REST, gRPC, 메시징 등 다양한 통신 방식을 Adapter에서 처리하여 Core Domain은 기술 변경에 영향을 받지 않습니다.
결론
헥사고날 아키텍처에서 MSA와 Feature 중심 설계를 도입하면 복잡한 시스템에서 유지보수성과 확장성을 크게 향상시킬 수 있습니다. Feature 간 상호작용을 Port와 Adapter로 처리함으로써 결합도를 낮추고 독립적인 개발과 배포가 가능합니다.
이 설계는 특히 Feature 간 상호작용이 많은 대규모 시스템에서 강력한 효과를 발휘하며, 테스트와 기술 변경에도 유연하게 대응할 수 있도록 도와줍니다.