Microservices Architecture(MSA)에서 Feature 간 의존성은 독립성 원칙을 위반할 위험이 있습니다. 이 문서에서는 Notification Feature가 User Feature에 의존하는 문제를 예시로 들어, 의존성 문제와 이를 해결하기 위한 방법들을 설명합니다.
의존성 문제의 정의
Notification Feature → User Feature 의존성
com.example.notification.service.NotificationService ↓ com.example.notification.port.UserClientPort (Outbound Port) ↓ com.example.notification.adapter.UserClientAdapter (Outbound Adapter) <---- 여기 ↓ com.example.user.port.UserServicePort (Inbound Port) ↓ com.example.user.service.UserServiceImpl (Business Logic) ↓ com.example.user.repository.UserRepository (JPA Repository)
문제점:
강한 결합:
Notification Feature가 User Feature에 강하게 결합되어, User Feature 변경 시 Notification Feature도 영향을 받습니다.
MSA 독립성 원칙 위반:
각 Feature는 독립적으로 배포 및 유지보수가 가능해야 하지만, 위와 같은 구조에서는 두 Feature가 독립적으로 동작하기 어렵습니다.
테스트 어려움:
Notification Feature를 테스트하려면 User Feature의 전체 구현이 필요할 수 있습니다.
해결 방안
1. REST API를 사용한 간접 호출
User Feature가 REST API를 제공하고, Notification Feature는 이를 호출하여 필요한 데이터를 가져옵니다. 이 접근법은 코드 의존성을 HTTP 통신으로 대체합니다.
구현 예시:
User Feature (REST API 제공):
@RestController @RequestMapping("/users") public class UserController { @GetMapping("/{userId}") public UserResponse getUser(@PathVariable String userId) { return userService.getUserById(userId); } }
Notification Feature (REST Client 사용):
@Component public class UserClientAdapter implements UserClientPort { private final RestTemplate restTemplate; public UserClientAdapter(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @Override public UserResponse getUser(String userId) { return restTemplate.getForObject("http://user-service/users/" + userId, UserResponse.class); } }
장점:
Feature 간 코드 의존성 제거.
각 Feature의 독립적 배포 및 유지보수 가능.
단점:
REST 호출 시 네트워크 오버헤드 발생.
호출 실패 시 복구 로직 필요.
2. 이벤트 기반 통신
User Feature가 사용자 데이터 변경 이벤트를 발행하고, Notification Feature가 이를 구독하여 처리합니다. 이는 비동기적으로 동작하며, 두 Feature 간 결합도를 낮춥니다.
구현 예시:
User Feature (이벤트 발행):
@Component public class UserEventPublisher { private final ApplicationEventPublisher eventPublisher; public void publishUserEvent(UserEvent event) { eventPublisher.publishEvent(event); } }
Notification Feature (이벤트 구독):
@Component public class UserEventListener { @EventListener public void handleUserEvent(UserEvent event) { // 이벤트 데이터 처리 } }
장점:
비동기적으로 동작하여 성능 개선 가능.
두 Feature 간 직접적인 의존성 제거.
단점:
이벤트 전송 지연 또는 손실 가능성.
추가적인 메시징 시스템(Kafka, RabbitMQ 등) 도입 필요.
Kafka를 활용한 이벤트 기반 통신 구현 예시:
User Feature (이벤트 발행):
@Service public class UserEventPublisher { private final KafkaTemplate<String, UserEvent> kafkaTemplate; public UserEventPublisher(KafkaTemplate<String, UserEvent> kafkaTemplate) { this.kafkaTemplate = kafkaTemplate; } public void publishUserEvent(UserEvent event) { kafkaTemplate.send("user-events", event); } }
Notification Feature (이벤트 구독):
@Service @KafkaListener(topics = "user-events", groupId = "notification-group") public class UserEventListener { public void handleUserEvent(UserEvent event) { // 이벤트 데이터 처리 System.out.println("Received event: " + event); } }
RabbitMQ를 활용한 이벤트 기반 통신 구현 예시:
User Feature (이벤트 발행):
@Service public class UserEventPublisher { private final RabbitTemplate rabbitTemplate; public UserEventPublisher(RabbitTemplate rabbitTemplate) { this.rabbitTemplate = rabbitTemplate; } public void publishUserEvent(UserEvent event) { rabbitTemplate.convertAndSend("userExchange", "userRoutingKey", event); } }
Notification Feature (이벤트 구독):
@Service @RabbitListener(queues = "userQueue") public class UserEventListener { public void handleUserEvent(UserEvent event) { // 이벤트 데이터 처리 System.out.println("Received event: " + event); } }
각 해결 방안의 비교
항목 | REST API 호출 | 이벤트 기반 통신 |
---|---|---|
결합도 | 낮음 | 매우 낮음 |
성능 | 동기적 호출로 인해 네트워크 오버헤드 존재 | 비동기적 호출로 높은 성능 가능 |
복구 로직 필요성 | 있음 | 메시징 시스템 설정에 따라 다름 |
구현 복잡도 | 낮음 | 높음 (메시징 시스템 필요) |
결론
Notification Feature와 User Feature 간 의존성 문제를 해결하기 위해 REST API 호출과 이벤트 기반 통신 두 가지 방법을 고려할 수 있습니다.
REST API 호출은 구현이 간단하고 동기적 처리가 필요한 경우 적합합니다.
이벤트 기반 통신은 성능이 중요한 비동기 환경에 적합하며, 결합도를 최소화할 수 있습니다.
프로젝트 요구사항에 맞는 방식을 선택하거나, 상황에 따라 두 방법을 조합하여 사용할 수도 있습니다.