알림 기능 고도화: 강결합으로 파생되는 문제 해결

문제

어떠한 문제를 해결하기 위해선, 해결하기에 앞서 문제를 정확하게 식별하고 정의하는 것이 무엇보다 중요합니다. 먼저 Ludo 알림 서비스의 구조에 대해 간단히 설명하고, 무엇이 문제인지 살펴보고자 합니다.

Ludo의 알림 서비스는 다양한 트리거에 의해 사용자에게 알림을 전송합니다. 핵심 비즈니스를 담당하는 서비스에서 알림 서비스를 의존하고 있고, 상황에 맞게 알림 서비스의 메서드를 호출하는 구조입니다. 이를 그림으로 표현하면 아래와 같습니다.

한 가지 예를 들어보겠습니다. RecruitmentService는 모집공고와 관련한 비즈니스 흐름을 담당하는 클래스입니다. NotificationService는 알림 관련 기능으로 응집되어 있는 클래스입니다. 사용자가 모집공고를 작성하면, 해당 모집공고에 적합한 알림 대상자에게 알림을 전송합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
@Transactional
@RequiredArgsConstructor
public class RecruitmentService {
  
  private final NotificationService notificationService;

  public RecruitmentDetailsResponse write(final User user, 
                                          final WriteRecruitmentRequest request, 
                                          final Long studyId)
  {
    // 비즈니스 로직.. (생략)

    // 모집 공고 알림 트리거
    notificationService.recruitmentNotice(recruitment);
    return new RecruitmentDetailsResponse(recruitment, recruitment.getStudy());
  }
}
1
2
3
4
5
6
7
8
9
10
11
@Service
@Transactional
@RequiredArgsConstructor
public class NotificationService {
  
  public void recruitmentNotice(final Recruitment recruitment) {
    // 1. 알림 대상자 조회 (생략)
    // 2. 알림 저장 (생략)
    // 3. 실시간 알림 전송 (생략)
  }
}

1. 알림 기능 실패시 핵심 비즈니스도 함께 실패

여기서 두가지 문제가 있습니다. 첫번째는 핵심 비즈니스의 트랜잭션알림 기능의 트랜잭션으로 전파되는 것입니다. 현재 구조에서는 알림 기능이 모종의 이유로 실패하면, 핵심 비즈니스 기능은 성공을 했음에도 알림 기능과 함께 롤백이 되어버립니다. 핵심 비즈니스 기능이 성공했다면, 알림 기능의 성공 / 실패 여부와 상관없이 커밋되어야 할 것입니다.

2. 단위 테스트하기 어려운 구조

두 번째 문제는 BusinessServiceNotificationService강결합함으로써, 두 클래스 모두 단위 테스트하기 어려운 구조가 됩니다. 알림 기능은 총 4가지 단계를 거쳐서 이루어집니다.

  1. 핵심 비즈니스 로직 과정에서의 알림 메서드 호출
  2. 알림 대상자 조회
  3. 알림을 RDB에 저장
  4. 실시간 알림 전송

BusinessService 관점에서 보면, 기존에 작성한 단위 테스트에 NotificationService를 Mocking하는 코드를 추가해야 합니다. 알림 요구사항이 변경되어 NotificationService 내부 구현을 수정한다면, 기존에 성공했던 BusinessService 단위 테스트에도 영향을 받아 실패하게 됩니다. 이를 해결하려면 Mocking한 코드를 재차 수정해야할 것입니다.

NotificationService 관점에서 살펴보면, 알림 전송 트리거가 잘 동작하는지 테스트하기 위해선 BusinessService의 모든 내부 구현을 알아야하며 그에 맞게 Mocking해야 합니다. 이것도 마찬가지로 BusinessService의 내부 구현이 수정되면, NotificationService의 단위 테스트에도 영향을 받고, Mocking한 코드를 재차 수정해야 합니다.

두 문제 모두 핵심은 BusinessServiceNotificationService간의 강결합으로 인해 파생되는 문제라고 볼 수도 있겠습니다. 정리하면 이번 고도화의 목표는 아래와 같습니다.

  • 문제1: 트랜잭션 전파로 인해 알림 기능의 성공 / 실패 여부가 핵심 비즈니스 로직에도 영향을 미친다.
    • 목표: 알림 기능이 실패해도 핵심 비즈니스는 영향을 받지 않는다.
  • 문제2: 강결합으로 인해 테스트하기 어려운 코드 구조이다.
    • 목표: 핵심 비즈니스 기능 또는 알림 기능이 변경되어도, 두 클래스에 대한 단위 테스트는 영향을 받지 않는다.