개발일지

Realtime API 응답 EventType에 따른 처리 구분

khw7385 2025. 6. 23. 20:24

목표

웹 소켓 핸들러에서 Realtime AI 의 응답의 이벤트 타입에 따라 분기문을 통해 처리 방법을 구분하는 것이 클린 코드가 아니라는 생각이 들었다. 객체 지향의 10대 원칙 중 하나가 else if, else 문을 활용하지 않는 것이다. 각 이벤트 타입에 맞게 처리(handle) 로직을 클린하게 구현하는 방법에 대해 학습한다.

과정

분기문을 통해서 구분짓지 않는다면 이제 다른 방법으로는 switch 문 혹은 인터페이스 와 Map 을 활용한 방법이 있을 것이다

switch 문을 활용하는 방법은 if 문보다 성능과 가독성 측면에서 더 뛰어난 구현 방식이라고 볼 수 있으나 각각의 케이스마다 처리 로직이 커진다면 이 switch 문을 품고 있는 메서드 또한 무거워지므로 클린코드라고 볼 수는 없을 것이다.

인터페이스와 각 케이스에 맞게 구현체를 구성하고 Map 자료구조를 통해서 해당 타입에 맞는 구현체를 가져와 처리한다면 클린코드라 볼 수 있을 것이다.

인터페이스

public interface RealtimeEventHandler {
    RealtimeEventType getSupportedType();
    void handle(String sessionId, AiToServerRealtimeMessage message);
}

인터페이스는 두 개의 메서드로 이루어진다.

getSupportedType() 는 해당 핸들러가 어떤 타입에 대한 핸들러인지를 반환하는 메서드이다.

handle(sessionId, message) 는 이벤트 타입에 맞는 처리를 위한 메서드이다.

구현체

위의 인터페이스를 구현한 예시 구현체이다.

@Component
@RequiredArgsConstructor
public class AudioDeltaEventHandler implements RealtimeEventHandler {
    private final AudioStreamingUseCase audioStreamingUseCase;

    @Override
    public RealtimeEventType getSupportedType(){
        return RealtimeEventType.RESPONSE_AUDIO_DELTA;
    }

    @Override
    public void handle(String sessionId, AiToServerRealtimeMessage message){
        audioStreamingUseCase.forward(sessionId, new ServerToClientAudioMessage(message.delta()));
    }
}

디스패처

@Slf4j
@Component
public class RealtimeEventDispatcher {
    private final Map<RealtimeEventType, RealtimeEventHandler> handlerMap;

    @Autowired
    public RealtimeEventDispatcher(List<RealtimeEventHandler> handlers) {
        this.handlerMap = handlers.stream()
                .collect(Collectors.toUnmodifiableMap(
                        RealtimeEventHandler::getSupportedType,
                        handler -> handler
                ));
    }

    public void dispatch(String sessionId, AiToServerRealtimeMessage message){
        RealtimeEventType type = message.type();
        log.info("Realtime Event type: {}", type);

        if(!handlerMap.containsKey(type)) return;

        RealtimeEventHandler handler = handlerMap.get(type);
        handler.handle(sessionId, message);
    }
}

디스패처는 내부 필드에 Map 저장 공간을 가져 핸들러를 보관한다. 여기서 주목할 부분이 생성자의 파라미터이다. 파라미터의 타입을 List<RealtimeEventHandler> 로 정의한다면 스프링 빈 객체로 등록된 RealtimeEventHandler 구현체들이 자동 주입이 된다. 따라서, 해당 객체를 생성하는 시점에 Map 에 핸들러를 등록할 수 있다.