Bỏ qua

Domain Events - Giao Tiếp Giữa Các Bounded Context

1. Domain Events Là Gì?

Định Nghĩa Đơn Giản

Domain Event là một thông báo về việc gì đó quan trọng đã xảy ra trong domain.

Ví dụ thực tế: - Khi khách hàng đặt hàng → OrderPlacedEvent - Khi thanh toán thành công → PaymentCompletedEvent
- Khi hàng được giao → OrderDeliveredEvent

Tại Sao Cần Domain Events?

** Không có Events:**

OrderService.createOrder() {
    // 1. Tạo đơn hàng
    order = new Order(...)
    orderRepo.save(order)

    // 2. Trừ kho
    inventoryService.reserveStock(order.items)

    // 3. Gửi email
    emailService.sendOrderConfirmation(order)

    // 4. Tính point thưởng
    loyaltyService.addPoints(order.customerId)

    // 5. Ghi log
    auditService.logOrderCreated(order)
}

Vấn đề: OrderService biết quá nhiều thứ! Nếu thêm tính năng mới phải sửa OrderService.

** Có Events:**

OrderService.createOrder() {
    // 1. Tạo đơn hàng
    order = new Order(...)

    // 2. Phát sự kiện
    order.addEvent(new OrderPlacedEvent(order))

    // 3. Lưu
    orderRepo.save(order)  // Events tự động được publish
}

// Các service khác tự động lắng nghe
InventoryService.handle(OrderPlacedEvent event) {...}
EmailService.handle(OrderPlacedEvent event) {...}
LoyaltyService.handle(OrderPlacedEvent event) {...}

Lợi ích: OrderService chỉ lo tạo đơn hàng. Các logic khác tự động kích hoạt.


2. Workflow Domain Events

sequenceDiagram
    participant User
    participant OrderController
    participant OrderService
    participant Order
    participant EventBus
    participant InventoryService
    participant EmailService
    participant LoyaltyService

    User->>OrderController: POST /orders
    OrderController->>OrderService: createOrder()
    OrderService->>Order: new Order()
    Order->>Order: addEvent(OrderPlacedEvent)
    OrderService->>OrderService: orderRepo.save(order)
    OrderService->>EventBus: publish(OrderPlacedEvent)

    EventBus->>InventoryService: handle(OrderPlacedEvent)
    EventBus->>EmailService: handle(OrderPlacedEvent)
    EventBus->>LoyaltyService: handle(OrderPlacedEvent)

    InventoryService-->>EventBus: StockReservedEvent
    EmailService-->>EventBus: EmailSentEvent
    LoyaltyService-->>EventBus: PointsAddedEvent

3. Ví Dụ Thực Tế: Hệ Thống E-commerce

Tình Huống: Khách Hàng Đặt Hàng

Events Được Tạo Ra:

flowchart TD
    A["🛒 User đặt hàng"] --> B["📢 OrderPlacedEvent"]
    B --> C[" InventoryService<br/>trừ kho"]
    B --> D[" EmailService<br/>gửi email"]
    B --> E[" LoyaltyService<br/>tính điểm"]
    B --> F[" AnalyticsService<br/>tracking"]

    C --> G[" StockReservedEvent"]
    D --> H[" EmailSentEvent"]
    E --> I[" PointsEarnedEvent"]

    G --> J[" ShippingService<br/>chuẩn bị giao hàng"]

Chi Tiết Events:

1. OrderPlacedEvent

{
    "eventId": "evt_001",
    "eventType": "OrderPlaced",
    "occurredOn": "2024-01-15T10:30:00Z",
    "data": {
        "orderId": "ORD_12345",
        "customerId": "CUST_999",
        "items": [
            {"productId": "PROD_A", "quantity": 2, "price": 500000},
            {"productId": "PROD_B", "quantity": 1, "price": 300000}
        ],
        "totalAmount": 1300000,
        "orderStatus": "CONFIRMED"
    }
}

2. StockReservedEvent

{
    "eventId": "evt_002", 
    "eventType": "StockReserved",
    "occurredOn": "2024-01-15T10:30:05Z",
    "data": {
        "orderId": "ORD_12345",
        "reservations": [
            {"productId": "PROD_A", "quantityReserved": 2, "warehouseId": "WH_001"},
            {"productId": "PROD_B", "quantityReserved": 1, "warehouseId": "WH_002"}
        ]
    }
}

3. PaymentProcessedEvent

{
    "eventId": "evt_003",
    "eventType": "PaymentProcessed", 
    "occurredOn": "2024-01-15T10:32:00Z",
    "data": {
        "orderId": "ORD_12345",
        "paymentId": "PAY_777",
        "amount": 1300000,
        "paymentMethod": "CREDIT_CARD",
        "status": "SUCCESS"
    }
}

Event Handlers:

InventoryService

handle(OrderPlacedEvent event) {
    foreach item in event.items {
        if (hasStock(item.productId, item.quantity)) {
            reserveStock(item.productId, item.quantity)
        } else {
            publish(OutOfStockEvent(item.productId))
        }
    }
    publish(StockReservedEvent(...))
}

EmailService

handle(OrderPlacedEvent event) {
    customer = customerRepo.findById(event.customerId)
    orderDetails = formatOrderDetails(event)
    sendEmail(
        to: customer.email,
        subject: "Xác nhận đơn hàng #" + event.orderId,
        body: orderDetails
    )
    publish(EmailSentEvent(...))
}

LoyaltyService

handle(OrderPlacedEvent event) {
    points = calculatePoints(event.totalAmount)
    addPointsToCustomer(event.customerId, points)
    publish(PointsEarnedEvent(...))
}


4. Pattern: Event Sourcing vs Domain Events

Domain Events (Event-Driven)

Mục đích: Giao tiếp giữa các Bounded Context Lưu trữ: Event chỉ để thông báo, data chính vẫn ở database thường Ví dụ: OrderPlacedEvent → InventoryService trừ kho

flowchart LR
    A[" Order DB<br/>(State)"] --> B["📢 OrderPlacedEvent<br/>(Notification)"]
    B --> C[" Inventory Service"]
    B --> D[" Email Service"]

Event Sourcing

Mục đích: Lưu trữ toàn bộ lịch sử thay đổi Lưu trữ: Events chính là source of truth Ví dụ: OrderCreated → OrderItemAdded → OrderConfirmed → OrderShipped

flowchart TD
    A[" Event Store"] --> B["OrderCreatedEvent"]
    A --> C["OrderItemAddedEvent"] 
    A --> D["OrderConfirmedEvent"]
    A --> E["OrderShippedEvent"]

    F[" Current State"] --> G["Rebuild từ Events"]
    B --> G
    C --> G
    D --> G
    E --> G

️ Lưu ý: Domain Events ≠ Event Sourcing. Có thể dùng riêng lẻ hoặc kết hợp.


5. Các Loại Events Thường Gặp

1. Entity Lifecycle Events

Khi Entity được tạo/sửa/xóa: - CustomerRegisteredEvent - ProductUpdatedEvent
- OrderCancelledEvent

2. Business Process Events

Khi quy trình kinh doanh xảy ra: - PaymentProcessedEvent - InventoryReplenishedEvent - ShipmentDeliveredEvent

3. Integration Events

Khi cần đồng bộ với hệ thống ngoài: - OrderSyncedToERPEvent - CustomerExportedToCRMEvent

4. Time-based Events

Khi có sự kiện định kỳ: - MonthlyReportGeneratedEvent - SubscriptionExpiredEvent


6. Event Implementation Patterns

Pattern 1: In-Memory Events

// Trong cùng process
EventBus.publish(event)
EventBus.subscribe(handler)

 Ưu điểm: Đơn giản, nhanh
 Nhược điểm: Mất event nếu crash

Pattern 2: Message Queue

// Qua RabbitMQ, Kafka, AWS SQS
queue.send(event)
consumer.receive(event)

 Ưu điểm: Reliable, scalable
 Nhược điểm: Phức tạp, eventually consistent

Pattern 3: Database Events

// Lưu events vào DB, polling hoặc CDC
eventsTable.insert(event)
eventProcessor.poll(events)

 Ưu điểm: Transactional, audit trail
 Nhược điểm: Performance overhead

Pattern 4: Hybrid (Outbox Pattern)

// Lưu event cùng transaction, sau đó publish
transaction {
    orderRepo.save(order)
    eventsTable.insert(event)
}
eventPublisher.publishPendingEvents()

 Ưu điểm: Best of both worlds
 Nhược điểm: Implementation complexity

7. Best Practices

Naming Convention

  • Past Tense: OrderPlaced, PaymentProcessed (đã xảy ra)
  • Domain Language: Dùng thuật ngữ business hiểu được
  • Specific: CustomerRegistered thay vì CustomerChanged

Event Design

  • Immutable: Event không bao giờ thay đổi
  • Self-contained: Chứa đủ thông tin cần thiết
  • Versioned: Có version để tương thích backward

Handler Design

  • Idempotent: Chạy nhiều lần cũng cùng kết quả
  • Fast: Không block quá lâu
  • Error Handling: Retry logic, dead letter queue

Testing

  • Unit Test: Test từng handler riêng lẻ
  • Integration Test: Test end-to-end flow
  • Event Store: Test event được tạo đúng

8. Common Pitfalls

Lỗi 1: Event Coupling

// SAI: Event chứa quá nhiều detail
CustomerRegisteredEvent {
    customer: Customer,
    preferences: UserPreferences,
    billingInfo: BillingInfo,
    shippingInfo: ShippingInfo
}

// ĐÚNG: Event chỉ chứa essential info
CustomerRegisteredEvent {
    customerId: string,
    email: string,
    registeredAt: Date
}

Lỗi 2: Synchronous Event Handling

// SAI: Handler chậm làm chậm toàn bộ process
OrderPlacedEvent → EmailHandler (5s) → timeout

// ĐÚNG: Async processing
OrderPlacedEvent → Queue → EmailHandler (background)

Lỗi 3: Event Versioning Issues

// SAI: Thay đổi event structure
OrderPlacedEvent v1: { orderId, amount }
OrderPlacedEvent v2: { orderId, items[] }  // Break consumers

// ĐÚNG: Backward compatible
OrderPlacedEvent v1: { orderId, amount }
OrderPlacedEvent v2: { orderId, amount, items[] }  // Keep old fields

9. Kết Luận

Domain Events giúp: - Loose Coupling: Các service không phụ thuộc trực tiếp - Scalability: Dễ dàng thêm tính năng mới - Auditability: Tracking được mọi sự kiện - Integration: Giao tiếp giữa Bounded Context

Nguyên tắc vàng:

"Don't call us, we'll call you" - Events cho phép inversion of control, hệ thống linh hoạt hơn rất nhiều!