알게된사실

Queue Port/Adapter 구조

HEEJUNDID 2026. 3. 30. 23:50
728x90
반응형

대기열 로직을 구현하면서 한 가지 문제가 있었다.
서비스 로직이 Redis에 직접 붙기 시작하면 테스트가 어려워진다는 점이다.

예를 들어 QueueService 내부에서 곧바로 StringRedisTemplate을 사용하게 되면,
테스트를 실행할 때마다 Redis 환경이 필요해진다.
이렇게 되면 단위 테스트의 장점이 사라진다.

그래서 QueueService는 Redis를 직접 알지 않도록 하고,
중간에 QueueStore라는 인터페이스를 두었다.

public interface QueueStore {

    void enqueue(Long concertId, Long userId, double score);

    Optional<Long> findRank(Long concertId, Long userId);

    long getGate(Long concertId);

    void saveQueueToken(QueueToken queueToken, Long concertId, Long userId, long ttlSeconds);

    Optional<TokenMapping> findByQueueToken(QueueToken queueToken);

    void saveAccessToken(String accessToken, Long concertId, Long userId, long ttlSeconds);

    Optional<TokenMapping> findByAccessToken(String accessToken);

    record TokenMapping(Long concertId, Long userId) {}
}

QueueService는 이 인터페이스만 의존한다.
운영 환경에서는 Redis 구현체를 연결하고,
테스트에서는 Fake 구현체를 연결한다.

테스트용 구현체는 FakeQueueStore로 만들었다.

이 Fake는 실제 Redis ZSET 대신 메모리 자료구조를 사용한다.
예를 들어 콘서트별 대기열은 다음과 같이 표현했다.

private final Map<Long, List<Entry>> queues = new ConcurrentHashMap<>();

여기서 key는 concertId, value는 해당 콘서트의 대기열이다.
Entry에는 userId와 score만 두고, score 기준으로 정렬하게 했다.

이렇게 하면 Redis 없이도 다음 흐름을 테스트할 수 있다.

  • enter 호출 시 대기열 등록
  • status 호출 시 rank 계산
  • gate 비교 후 Waiting/Admitted 판단
  • access token 저장 여부 검증

이 구조의 장점은 명확하다.

  • 비즈니스 로직을 인프라와 분리할 수 있다
  • Redis 없이도 테스트가 가능하다
  • 실제 구현체를 나중에 붙여도 서비스 코드를 바꿀 필요가 없다

결국 Port/Adapter 구조를 적용한 이유는
복잡한 구조를 만들기 위해서가 아니라,
Queue 로직을 독립적으로 검증하기 위해서였다.

다음 글에서는 실제 Redis 구현체를 붙이는 과정을 정리할 예정이다.

728x90
반응형