Skip to content

이벤트 시스템

truloop-core의 Domain Event 시스템을 설명합니다. Kotlin Coroutine의 SharedFlow를 기반으로 한 EventBus 패턴으로 도메인 간 느슨한 결합을 실현합니다.


아키텍처 개요


핵심 컴포넌트

DomainEvent (Domain Layer)

도메인 이벤트는 :domain 모듈에 정의됩니다. 비즈니스적으로 의미 있는 상태 변화를 나타냅니다.

kotlin
// domain/event/DomainEvent.kt
interface DomainEvent

각 도메인 기능은 자체 이벤트를 정의합니다:

도메인이벤트 예시
Loop룹 생성됨, 룹 제목 변경됨, 룹 참여자 추가/제거됨, 가용 시간 업데이트됨, 전체 가용시간 조율 완료, 룹 날짜 확정됨, 일정 조율용 룹 생성됨
User사용자 등록됨, 프로필 변경됨
Recap (내부명: Story)리캡 생성됨
LoopMedia미디어 업로드 완료됨(MediaReadyEvent), 미디어 삭제됨(MediaDeletedEvent)
Chat채팅 메시지 수신됨

EventPublisher (Application Layer - Port)

Application 계층에서 사용하는 이벤트 발행 인터페이스입니다.

kotlin
// application/shared/port/EventPublisher.kt
interface EventPublisher {
    suspend fun publish(event: DomainEvent)
}

정보

EventPublisher.publish()fire-and-forget 방식입니다. 예외를 발생시키지 않으며, 호출자가 try-catch를 할 필요가 없습니다.

EventBus (Bootstrap Layer)

SharedFlow 기반의 중앙 이벤트 버스입니다.

주요 특징:

  • Type-safe 이벤트 구독 (reified generics)
  • Graceful shutdown (설정 가능한 타임아웃)
  • Buffer overflow 전략 설정
  • 메트릭 수집 (published, processed, errors, dropped)
  • 구독자별 에러 격리
kotlin
class EventBus(
    config: EventBusConfig,
    scope: CoroutineScope,
    errorReporter: EventErrorReporter
)

주요 메서드:

메서드설명
publish(event)이벤트 발행 (fire-and-forget, 예외 안 남)
subscribe<T>(handler)특정 타입 이벤트 구독
subscribeAll(handler)모든 이벤트 구독 (로깅/디버깅용)
shutdown(timeout)Graceful shutdown
getMetrics()메트릭 조회

EventHandlerRegistry (Bootstrap Layer)

모든 EventHandler를 자동으로 발견하고 등록합니다.

kotlin
// Koin이 @Single 어노테이션된 모든 EventHandler를 자동 발견
val allHandlers = koin.getAll<EventHandler<*>>()
eventHandlerRegistry.registerAll(allHandlers)
eventHandlerRegistry.startAll()

EventHandler (Bootstrap Layer - 구현)

EventHandlerabstract class로, 모든 이벤트 핸들러의 기반 클래스입니다. 제네릭 타입 파라미터를 통해 type-safe한 이벤트 처리를 보장합니다.

kotlin
// bootstrap/event/handler/EventHandler.kt
abstract class EventHandler<T : DomainEvent> {
    abstract suspend fun handle(event: T)
    val logger: Logger by lazy { LoggerFactory.getLogger(this::class.java) }
}

도메인별 이벤트 핸들러가 위치합니다:

bootstrap/event/handler/
├── EventHandler.kt           # 핸들러 추상 클래스
├── EventHandlerException.kt  # 핸들러 예외 (sealed class)
├── EventHandlerRegistry.kt   # 핸들러 등록/관리
├── chat/                     # 채팅 이벤트 핸들러
├── contact/                  # 연락처 이벤트 핸들러
├── device/                   # 디바이스 이벤트 핸들러
├── eta/                      # ETA 이벤트 핸들러
├── guest/                    # 비회원 참여자 이벤트 핸들러
├── liveactivity/             # Live Activity 이벤트 핸들러
├── loop/                     # 룹 이벤트 핸들러
├── loopcomment/              # 룹 댓글 이벤트 핸들러
├── loopmedia/                # 룹 미디어 이벤트 핸들러 + 이미지 그룹핑 임베딩 핸들러
├── secretary/                # AI 비서 이벤트 핸들러
├── story/                    # 리캡 이벤트 핸들러 (내부명: Story)
└── user/                     # 사용자 이벤트 핸들러

이벤트 흐름


에러 처리

에러 격리

각 구독자의 에러는 다른 구독자에 영향을 미치지 않습니다:

  • 핸들러 에러 → 로깅 + Sentry 보고 (EventErrorReporter)
  • 이벤트 발행 실패 → 로깅 + Sentry 보고 (호출자에 예외 전파 안 함)
  • 버퍼 오버플로우 → 이벤트 드롭 + 경고 로깅

EventErrorReporter

EventErrorReporter 인터페이스를 통해 에러를 외부 모니터링 시스템에 보고합니다. SentryEventErrorReporter가 프로덕션 구현체이며, 테스트용 NoOpEventErrorReporter도 제공됩니다.

kotlin
interface EventErrorReporter {
    suspend fun reportPublishFailure(event: DomainEvent, error: Throwable)
    suspend fun reportBufferOverflow(event: DomainEvent)
    suspend fun reportHandlerFailure(handlerName: String, event: DomainEvent, error: Throwable)
    suspend fun reportHandlerTimeout(handlerName: String, event: DomainEvent, timeout: Duration)
}

주의

BusinessException은 예상된 비즈니스 실패이므로 Sentry에 보고하지 않습니다. SentryEventErrorReporterEventHandlerRegistry 모두 BusinessException을 필터링합니다.


Shutdown 프로세스

정보

Shutdown 타임아웃 내에 모든 작업이 완료되지 않으면, EventBus는 남은 작업을 강제 취소하고 EventBusException.ShutdownTimeout을 발생시킵니다. EventHandlerRegistry는 별도로 graceful shutdown을 수행하며, 구독 취소 후 핸들러 목록을 정리합니다.


메트릭

EventBus.getMetrics()로 모니터링 정보를 조회할 수 있습니다:

메트릭설명
publishedCount발행된 이벤트 수
processedCount처리 완료된 이벤트 수
errorCount처리 중 에러 발생 수
droppedCount버퍼 오버플로우로 드롭된 이벤트 수
activeSubscriptions활성 구독 수
activeJobs현재 처리 중인 작업 수
isShuttingDown셧다운 진행 중 여부

변경 이력

날짜내용
2026-03-12LoopMedia 이벤트에 MediaDeletedEvent 추가, loopmedia 핸들러에 MediaReadyEmbeddingHandler/MediaDeletedEmbeddingHandler 추가
2026-03-11LoopTitleUpdatedEvent 추가 (채팅 채널 이름 동기화), LoopCreatedEventloopTitle 필드 추가
2026-03-10Loop 이벤트 목록을 소스 코드 기준으로 전면 갱신: 참여자 추가/제거, 전체 가용시간 조율 완료, 룹 날짜 확정됨(LoopDatetimeConfirmed), 일정 조율용 룹 생성됨(LoopCreatedForScheduling) 추가
2026-03-10EventHandler가 interface에서 abstract class로 변경된 사항 반영. EventErrorReporter 인터페이스에 reportHandlerFailure(), reportHandlerTimeout() 메서드 추가 반영. BusinessException Sentry 보고 제외 정책 명시. 메트릭에 isShuttingDown 필드 추가.
2026-03-10Loop 이벤트에 "가용 시간 업데이트됨" 추가 (LoopParticipantAvailabilityUpdated)