BindProject

[Backend] Event(이벤트 역/직렬화)모듈 구현

dding-shark 2025. 6. 17. 14:49
728x90

Bind 프로젝트 - Event 모듈 설계 및 구현

1. 도입

이벤트 기반 아키텍처는 서비스 간의 결합도를 낮추고 비동기 처리를 가능하게 해준다.
Bind 프로젝트에서는 이러한 목적을 달성하기 위해 도메인 이벤트 시스템을 도입했고,
이벤트를 정의하고 직렬화하는 역할을 담당하는 event 모듈을 먼저 설계했다.

Kafka와 같은 메시지 브로커와의 연동은 messaging 모듈이 담당하고,
이 모듈은 오직 이벤트의 정의, 표현, 직렬화 책임만을 가진다.


2. 설계 목표

  • ✅ 모든 이벤트를 공통 인터페이스(CustomEvent)로 통합
  • ✅ 이벤트의 직렬화 및 역직렬화 책임을 명확히 분리
  • ✅ Kafka 전송에 맞는 메시지 포맷(KafkaEventPayload) 정의
  • ✅ Kafka 없이도 독립 테스트 가능하도록 구성

3. 설계 고려사항

  • 직렬화 실패 처리: Optional 반환 방식으로 강제적인 예외 처리를 유도
  • Spring 이벤트 발행기 추상화: SimpleEventPublisher를 도입해 느슨한 결합 유지
  • Kafka 의존성 분리: messaging 모듈이 Kafka 연동을 책임지고, 이 모듈은 메시지 포맷까지만 담당
  • 도메인 유연성: 다양한 이벤트를 쉽게 추가할 수 있는 구조로 유지

4. 구현 설명

CustomEvent

public interface CustomEvent {
    String name(); // 이벤트 식별자 (Kafka topic 등으로 활용 가능)
}

KafkaEventPayload

public record KafkaEventPayload(
    String type,
    String data,
    Instant occurredAt
) {}

KafkaEventSerializer

public class KafkaEventSerializer {

    public static String serialize(CustomEvent event) {
        String eventJson = DataSerializer.serialize(event)
            .orElseThrow(() -> new IllegalArgumentException("직렬화 실패: " + event.getClass().getSimpleName()));

        KafkaEventPayload payload = new KafkaEventPayload(
            event.name(),
            eventJson,
            Instant.now()
        );

        return DataSerializer.serialize(payload)
            .orElseThrow(() -> new IllegalStateException("KafkaEventPayload 직렬화 실패"));
    }

    public static KafkaEventPayload deserialize(String json) {
        return DataSerializer.deserialize(json, KafkaEventPayload.class)
            .orElseThrow(() -> new IllegalArgumentException("KafkaEventPayload 역직렬화 실패"));
    }
}

SimpleEventPublisher

@RequiredArgsConstructor
public class SimpleEventPublisher implements EventPublisher {

    private final ApplicationEventPublisher springPublisher;

    @Override
    public void publish(CustomEvent event) {
        springPublisher.publishEvent(event);
    }
}

5. 테스트 전략

KafkaEventSerializerTest

class KafkaEventSerializerTest {

    static class TestEvent implements CustomEvent {
        @Override
        public String name() { return "test.event"; }
        public String getMessage() { return "hello"; }
    }

    @Test
    void 직렬화_역직렬화_정상작동() {
        TestEvent event = new TestEvent();
        String serialized = KafkaEventSerializer.serialize(event);
        KafkaEventPayload payload = KafkaEventSerializer.deserialize(serialized);

        assertThat(payload.type()).isEqualTo("test.event");
        assertThat(payload.data()).contains("hello");
        assertThat(payload.occurredAt()).isNotNull();
    }
}

SimpleEventPublisherTest

class SimpleEventPublisherTest {

    @Test
    void 이벤트_발행_정상작동() {
        ApplicationEventPublisher springPublisher = mock(ApplicationEventPublisher.class);
        SimpleEventPublisher publisher = new SimpleEventPublisher(springPublisher);

        CustomEvent event = () -> "sample.event";
        publisher.publish(event);

        verify(springPublisher, times(1)).publishEvent(event);
    }
}

테스트 전부 통과 완료

6. 회고 및 확장 가능성

이번 event 모듈은 도메인 이벤트를 정의하고 직렬화하는 데 집중했다.
Kafka 같은 메시징 기술과의 직접적인 결합을 피함으로써 다음과 같은 장점을 얻었다:

  • 테스트 용이성
  • 책임 분리
  • 모듈 간 결합도 최소화

향후 messaging 모듈을 통해 Kafka 연동을 구현할 때 이 구조가 유연하게 확장될 수 있으며,
Outbox 패턴을 도입하거나 이벤트 버퍼링, 모니터링 기능도 안정적으로 추가할 수 있다.


728x90