Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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)       |
+---------------------+                                  +------------------------+

설명

  1. NotificationService:

    • NotificationService는 비즈니스 로직에서 UserClientPort를 통해 User 정보를 요청합니다.

  2. UserClientPort:

    • Notification Feature의 Outbound Port로서, UserClientAdapter의 호출 인터페이스 역할을 합니다.

  3. UserClientAdapter:

    • UserClientPort를 구현하며, UserServicePort를 호출하여 User 정보를 가져옵니다.

  4. UserServicePort:

    • User Feature의 Inbound Port로서 외부 요청을 처리합니다.

  5. 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)

이를 해결하기 위한 방법은 다음과 같습니다:

  1. 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);
        }
    }

  2. 이벤트 기반 통신:

    • 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를 사용한 설계의 장점

...