반응형

이벤트 소싱 (Event Sourcing)

Event Sourcing 은 데이터 상태를 단순히 현재 상태로 저장하는 대신, 상태 변화를 일으킨 모든 이벤트를 순차적으로 저장하는 아키텍처 패턴입니다.

이 방식은 기존의 CRUD 기반 시스템과 다르게 데이터 변경의 이력(history) 을 완전하게 기록하며, 이를 기반으로 현재 상태를 복원합니다.


핵심 개념

  1. 이벤트(event) 중심의 데이터 저장

    • 시스템에서 발생하는 중요한 모든 변경 사항을 이벤트로 정의합니다.
    • 예: 사용자가 계좌에 돈을 입금 → DepositMade 이벤트로 기록.
  2. 현재 상태를 재구성

    • 데이터를 읽을 때는 저장된 이벤트를 시간 순서대로 재생(replay)하여 현재 상태를 복원합니다.
  3. 이벤트 로그(Event Log)

    • 이벤트는 불변(immutable)으로 저장됩니다.
    • 각 이벤트는 시스템의 상태 변화만 기술하며, 변경 이전의 상태는 덮어쓰지 않습니다.

예시

전통적인 데이터 저장 방식:

계좌 ID: 12345
잔고: 500원
  • 마지막 상태만 저장.

Event Sourcing 방식:

[이벤트 로그]
1. AccountCreated(accountId=12345)
2. DepositMade(accountId=12345, amount=300)
3. DepositMade(accountId=12345, amount=700)
4. WithdrawalMade(accountId=12345, amount=500)
  • 모든 상태 변화가 이벤트로 기록됩니다.
  • 현재 잔고는 이벤트를 "재생(Replay)"하여 계산: 300 + 700 - 500 = 500

장점

  1. 완전한 감사(audit) 및 추적 가능성

    • 모든 변경 사항을 이벤트로 기록하므로, 데이터 변경의 원인을 쉽게 추적할 수 있습니다.
  2. 재생 가능

    • 이벤트 로그를 다시 재생하여 과거의 특정 상태를 복원할 수 있습니다.
    • 시스템 장애 발생 시 현재 상태를 빠르게 복구 가능.
  3. 분산 시스템에 적합

    • 이벤트는 독립적이고 불변이므로, 메시징 시스템을 통해 다른 서비스와 쉽게 공유할 수 있습니다.
  4. CQRS(Command Query Responsibility Segregation)와의 궁합

    • Event Sourcing은 CQRS 패턴 과 함께 사용하면, 읽기/쓰기 모델을 최적화 하고 고성능 시스템을 설계할 수 있습니다.

단점

  1. 복잡성 증가

    • 이벤트를 관리하는 추가적인 작업이 필요합니다.
    • 이벤트 스키마 변경 시, 기존 로그와의 호환성 유지가 어려울 수 있습니다.
  2. 읽기 성능 저하

    • 현재 상태를 빠르게 복원하려면 많은 이벤트를 재생해야 하므로 읽기 성능이 저하될 수 있습니다.
    • 이를 해결하기 위해 스냅샷(snapshot) 기법을 사용합니다.
  3. 디버깅 난이도

    • 버그가 발생할 경우, 이벤트 로그를 일일이 분석해야 합니다.

코드 예제

kotlin

// 이벤트 정의
sealed class AccountEvent
data class AccountCreated(val accountId: String) : AccountEvent()
data class DepositMade(val accountId: String, val amount: Int) : AccountEvent()
data class WithdrawalMade(val accountId: String, val amount: Int) : AccountEvent()

// 현재 상태 복원
data class Account(val accountId: String, var balance: Int = 0) {
    fun apply(event: AccountEvent) {
        when (event) {
            is AccountCreated -> println("Account created: ${event.accountId}")
            is DepositMade -> balance += event.amount
            is WithdrawalMade -> balance -= event.amount
        }
    }
}

// 이벤트 재생
fun replayEvents(accountId: String, events: List<AccountEvent>): Account {
    val account = Account(accountId)
    events.forEach { event -> account.apply(event) }
    return account
}

// 예제 실행
val events = listOf(
    AccountCreated("12345"),
    DepositMade("12345", 500),
    DepositMade("12345", 300),
    WithdrawalMade("12345", 200)
)
val account = replayEvents("12345", events)
println("최종 잔고: ${account.balance}") // 출력: 최종 잔고: 600

적용 사례

  • 금융 시스템: 거래 내역 및 잔고 관리.
  • 전자상거래: 주문 상태 추적.
  • IoT: 센서 데이터 이력 관리.
  • 분산 시스템: 마이크로서비스 간 이벤트 기반 통신.

Event Sourcing은 시스템의 투명성을 높이고 복잡한 비즈니스 로직을 효율적으로 관리할 수 있는 강력한 패턴이지만, 적용 전 충분한 고려와 설계가 필요합니다.

728x90
반응형

+ Recent posts