Skip to content

Navigation Menu

Sign in
Sign up

이벤트

RAprogramm edited this page Jan 7, 2026 · 2 revisions

엔티티 생명주기 변경에 대한 도메인 이벤트를 생성합니다. 이벤트는 감사 로깅, 이벤트 소싱, 메시지 큐 통합을 가능하게 합니다.

빠른 시작

#[derive(Entity)]
#[entity(table = "orders", events)]
pub struct Order {
 #[id]
 pub id: Uuid,
 #[field(create, response)]
 pub customer_id: Uuid,
 #[field(create, update, response)]
 pub status: String,
 #[field(create, response)]
 pub total_cents: i64,
 #[field(response)]
 #[auto]
 pub created_at: DateTime<Utc>,
}

생성되는 코드

events 속성은 이벤트 열거형을 생성합니다:

/// entity-derive에 의해 생성됨
#[derive(Debug, Clone)]
pub enum OrderEvent {
 /// 엔티티가 생성됨.
 Created(Order),
 /// 엔티티가 업데이트됨.
 Updated {
 id: Uuid,
 changes: UpdateOrderRequest,
 },
 /// 엔티티가 삭제됨.
 Deleted(Uuid),
}

사용 예제

기본 이벤트 발행

use async_trait::async_trait;
#[async_trait]
pub trait EventBus: Send + Sync {
 async fn publish<E: Send + Sync>(&self, event: E);
}
async fn create_order(
 repo: &impl OrderRepository,
 bus: &impl EventBus,
 dto: CreateOrderRequest,
) -> Result<Order, sqlx::Error> {
 let order = repo.create(dto).await?;
 // 성공적인 생성 후 이벤트 발행
 bus.publish(OrderEvent::Created(order.clone())).await;
 Ok(order)
}
async fn update_order(
 repo: &impl OrderRepository,
 bus: &impl EventBus,
 id: Uuid,
 dto: UpdateOrderRequest,
) -> Result<Order, sqlx::Error> {
 let order = repo.update(id, dto.clone()).await?;
 bus.publish(OrderEvent::Updated { id, changes: dto }).await;
 Ok(order)
}
async fn delete_order(
 repo: &impl OrderRepository,
 bus: &impl EventBus,
 id: Uuid,
) -> Result<bool, sqlx::Error> {
 let deleted = repo.delete(id).await?;
 if deleted {
 bus.publish(OrderEvent::Deleted(id)).await;
 }
 Ok(deleted)
}

감사 로깅

struct AuditLogger {
 pool: PgPool,
}
#[async_trait]
impl EventHandler<OrderEvent> for AuditLogger {
 async fn handle(&self, event: OrderEvent) {
 let (action, entity_id, details) = match &event {
 OrderEvent::Created(order) => (
 "created",
 order.id,
 serde_json::to_string(order).unwrap(),
 ),
 OrderEvent::Updated { id, changes } => (
 "updated",
 *id,
 serde_json::to_string(changes).unwrap(),
 ),
 OrderEvent::Deleted(id) => (
 "deleted",
 *id,
 String::new(),
 ),
 };
 sqlx::query(
 "INSERT INTO audit_log (entity_type, entity_id, action, details, created_at)
 VALUES ('order', 1,ドル 2,ドル 3,ドル NOW())"
 )
 .bind(entity_id)
 .bind(action)
 .bind(details)
 .execute(&self.pool)
 .await
 .ok();
 }
}

메시지 큐 통합

use rdkafka::producer::FutureProducer;
struct KafkaEventBus {
 producer: FutureProducer,
 topic: String,
}
#[async_trait]
impl EventBus for KafkaEventBus {
 async fn publish<E: Serialize + Send + Sync>(&self, event: E) {
 let payload = serde_json::to_vec(&event).unwrap();
 self.producer
 .send(
 FutureRecord::to(&self.topic)
 .payload(&payload)
 .key(&Uuid::new_v4().to_string()),
 Duration::from_secs(5),
 )
 .await
 .ok();
 }
}

이벤트 소싱 패턴

struct OrderAggregate {
 events: Vec<OrderEvent>,
 current_state: Option<Order>,
}
impl OrderAggregate {
 fn apply(&mut self, event: OrderEvent) {
 match &event {
 OrderEvent::Created(order) => {
 self.current_state = Some(order.clone());
 }
 OrderEvent::Updated { changes, .. } => {
 if let Some(ref mut order) = self.current_state {
 if let Some(status) = &changes.status {
 order.status = status.clone();
 }
 }
 }
 OrderEvent::Deleted(_) => {
 self.current_state = None;
 }
 }
 self.events.push(event);
 }
 fn replay(events: Vec<OrderEvent>) -> Self {
 let mut aggregate = Self {
 events: Vec::new(),
 current_state: None,
 };
 for event in events {
 aggregate.apply(event);
 }
 aggregate
 }
}

소프트 삭제와 함께 사용

soft_delete가 활성화되면 추가 이벤트가 생성됩니다:

#[derive(Entity)]
#[entity(table = "documents", events, soft_delete)]
pub struct Document {
 #[id]
 pub id: Uuid,
 #[field(create, response)]
 pub title: String,
 #[field(skip)]
 pub deleted_at: Option<DateTime<Utc>>,
}

생성됨:

pub enum DocumentEvent {
 Created(Document),
 Updated { id: Uuid, changes: UpdateDocumentRequest },
 Deleted(Uuid), // 소프트 삭제
 Restored(Uuid), // 소프트 삭제에서 복원
 HardDeleted(Uuid), // 영구 삭제
}

모범 사례

  1. 커밋 후 발행 — 데이터베이스 트랜잭션이 성공한 후에만 이벤트 발행
  2. 멱등성 핸들러 — 이벤트 핸들러는 at-least-once 전달을 위해 멱등성이어야 함
  3. 컨텍스트 포함 — 메타데이터 추가 고려 (user_id, timestamp, correlation_id)
  4. 비동기 처리 — 무거운 이벤트 처리에는 백그라운드 워커 사용
  5. Dead letter queue — 실패한 이벤트를 우아하게 처리

훅과 결합

이벤트와 훅은 잘 함께 작동합니다:

#[derive(Entity)]
#[entity(table = "orders", events, hooks)]
pub struct Order { /* ... */ }
struct OrderService {
 repo: PgPool,
 bus: EventBus,
}
#[async_trait]
impl OrderHooks for OrderService {
 type Error = AppError;
 async fn after_create(&self, entity: &Order) -> Result<(), Self::Error> {
 // 훅에서 이벤트 발행
 self.bus.publish(OrderEvent::Created(entity.clone())).await;
 Ok(())
 }
 async fn after_update(&self, entity: &Order) -> Result<(), Self::Error> {
 // 여기서도 이벤트를 발행할 수 있음
 Ok(())
 }
 async fn after_delete(&self, id: &Uuid) -> Result<(), Self::Error> {
 self.bus.publish(OrderEvent::Deleted(*id)).await;
 Ok(())
 }
}

참고

  • — 생명주기 이벤트에서 커스텀 로직 실행
  • 커맨드 — 커맨드 이벤트를 포함한 CQRS 패턴
  • 모범-사례 — 프로덕션 팁

🌐 Language

🇬🇧 English | 🇷🇺 Русский | 🇰🇷 한국어 | 🇪🇸 Español | 🇨🇳 中文


🇬🇧 English

Home

Getting Started

Features

Advanced


🇷🇺 Русский

Главная

Начало работы

Возможности

Продвинутое


🇰🇷 한국어

시작하기

기능

고급


🇪🇸 Español

Inicio

Comenzando

Características

Avanzado


🇨🇳 中文

首页

入门

功能

高级


📚 Reference

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /