...
Code Block |
---|
public interface UserServicePort { UserResponse getUserById(String userId); } |
User Feature의 Inbound Port 구현체
UserServicePort
의 구현체는 User
Feature의 데이터 접근 로직을 처리하며, JPA Repository를 사용해 데이터를 관리합니다.
Code Block |
---|
@Service
public class UserServiceImpl implements UserServicePort {
private final UserRepository userRepository;
public UserServiceImpl(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());
}
}
|
User Feature의 Repository
JPA를 활용하여 User 데이터를 관리하는 Repository는 다음과 같이 정의됩니다:
Code Block |
---|
public interface UserRepository extends JpaRepository<User, String> {
}
|
Notification Feature의 서비스
...
Code Block |
---|
@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());
}
}
|
...
클래스 간 관계 도식화
Code Block |
---|
[Notification Feature] [User Feature] +---------------------+ +------------------------+ | NotificationService | | UserServiceImpl | | (Core Logic) | | (Business Logic) | +---------------------+ +------------------------+ | | v | +---------------------+ Request +------------------------+ | UserClientPort | -------------------------------> | UserServicePort | | (Outbound Port) | | (Inbound Port) | +---------------------+ +------------------------+ ^ ^ | | +---------------------+ +------------------------+ | UserClientAdapter | <------------------------------- | UserRepository | | (Outbound Adapter) | Response | (JPA Repository) | +---------------------+ +------------------------+ |
설명
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를 통해 접근합니다.
...
추가 설명: 의존성 문제와 해결 방안
Notification Feature에서 User Feature로의 의존성은 MSA 설계의 독립성 원칙을 위반할 가능성이 있습니다.
Code Block |
---|
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) |
이를 해결하기 위한 방법은 다음과 같습니다:
REST API를 사용한 간접 호출:
User Feature가 REST API를 제공하고, Notification Feature는 이를 호출하여 User 데이터를 가져옵니다.
두 Feature는 HTTP 통신을 통해 상호작용하며, 코드 의존성을 제거합니다.
Code Block @RestController @RequestMapping("/users") public class UserController { @GetMapping("/{userId}") public UserResponse getUser(@PathVariable String userId) { return userService.getUserById(userId); } }
Code Block @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); } }
이벤트 기반 통신:
User Feature가 사용자 데이터 변경 이벤트를 발행하면 Notification Feature가 이를 구독하여 비동기로 처리합니다.
Code Block @Component public class UserEventPublisher { private final ApplicationEventPublisher eventPublisher; public void publishUserEvent(UserEvent event) { eventPublisher.publishEvent(event); } }
Code Block @Component public class UserEventListener { @EventListener public void handleUserEvent(UserEvent event) { // 이벤트 데이터 처리 } }
...
Port와 Adapter를 사용한 설계의 장점
...