4.5 KiB
E-Commerce Example: Domain Events
Define and implement domain events for the e-commerce order system.
Event Design Principles
All events in this system follow these principles:
✅ Past tense naming - Events describe what happened ✅ Immutable records - Events cannot be changed once created ✅ Complete context - Events include all relevant data ✅ Business language - Events use domain terminology
Order Lifecycle Events
OrderPlacedEvent
Emitted when a customer places a new order.
public record OrderPlacedEvent
{
public string OrderId { get; init; } = string.Empty;
public string CustomerId { get; init; } = string.Empty;
public string CustomerName { get; init; } = string.Empty;
public string CustomerEmail { get; init; } = string.Empty;
public List<OrderLineDto> Lines { get; init; } = new();
public decimal TotalAmount { get; init; }
public DateTimeOffset PlacedAt { get; init; }
}
public record OrderLineDto
{
public string ProductId { get; init; } = string.Empty;
public string ProductName { get; init; } = string.Empty;
public int Quantity { get; init; }
public decimal UnitPrice { get; init; }
public decimal LineTotal { get; init; }
}
When to emit: After order validation passes
Stream: order-{orderId}
Triggers: Inventory reservation, payment processing
OrderPaidEvent
Emitted when payment for an order is successfully processed.
public record OrderPaidEvent
{
public string OrderId { get; init; } = string.Empty;
public string PaymentId { get; init; } = string.Empty;
public string PaymentMethod { get; init; } = string.Empty;
public decimal Amount { get; init; }
public DateTimeOffset PaidAt { get; init; }
}
When to emit: After successful payment processing
Stream: order-{orderId}
Triggers: Shipment creation
OrderShippedEvent
Emitted when an order is shipped to the customer.
public record OrderShippedEvent
{
public string OrderId { get; init; } = string.Empty;
public string ShipmentId { get; init; } = string.Empty;
public string TrackingNumber { get; init; } = string.Empty;
public string Carrier { get; init; } = string.Empty;
public DateTimeOffset EstimatedDelivery { get; init; }
public DateTimeOffset ShippedAt { get; init; }
}
When to emit: When warehouse marks order as shipped
Stream: order-{orderId}
Triggers: Email notification, tracking update
OrderDeliveredEvent
Emitted when an order is delivered to the customer.
public record OrderDeliveredEvent
{
public string OrderId { get; init; } = string.Empty;
public string ShipmentId { get; init; } = string.Empty;
public string SignedBy { get; init; } = string.Empty;
public DateTimeOffset DeliveredAt { get; init; }
}
When to emit: When carrier confirms delivery
Stream: order-{orderId}
Triggers: Completion email
OrderCancelledEvent
Emitted when an order is cancelled.
public record OrderCancelledEvent
{
public string OrderId { get; init; } = string.Empty;
public string CancelledBy { get; init; } = string.Empty;
public string Reason { get; init; } = string.Empty;
public bool RefundIssued { get; init; }
public DateTimeOffset CancelledAt { get; init; }
}
When to emit: When customer or admin cancels order
Stream: order-{orderId}
Triggers: Inventory release, refund processing
Event Stream Design
Each order has its own stream containing all lifecycle events:
Stream: order-12345
├── Offset 1: OrderPlacedEvent
├── Offset 2: InventoryReservedEvent
├── Offset 3: PaymentProcessedEvent
├── Offset 4: OrderPaidEvent
├── Offset 5: ShipmentCreatedEvent
└── Offset 6: OrderShippedEvent
Event Registration
Register events with the event store:
// In Program.cs
builder.Services.AddEventStreaming()
.AddPostgresEventStore(builder.Configuration.GetConnectionString("EventStore"));
// Register event handlers
builder.Services.AddEventHandler<OrderPlacedEvent, OrderPlacedEventHandler>();
builder.Services.AddEventHandler<OrderPaidEvent, OrderPaidEventHandler>();
builder.Services.AddEventHandler<OrderShippedEvent, OrderShippedEventHandler>();
See Also
- 03-commands.md - Commands that produce these events
- 04-queries.md - Queries that consume these events
- Event Design Best Practices