The canonical event published by order-service and consumed by tracking-service and notification-service.
| Field | Type | Notes |
|---|---|---|
eventId |
UUID | Unique identifier for this event instance |
orderId |
UUID | Identifier of the originating order |
trackingCode |
String | Format: TF-XXXXXX (TF- prefix + 6 random uppercase alphanumeric chars) |
status |
OrderStatus | See enum values below |
origin |
String | Shipment origin location |
destination |
String | Shipment destination location |
recipientEmail |
String | Email address for notifications |
notes |
String | Optional; nullable |
occurredAt |
LocalDateTime | Timestamp of the status change, set by order-service |
- order-service publishes
ShipmentEventtoshipment-events. - tracking-service consumes the event and records a tracking entry.
- notification-service consumes the event and attempts to send a webhook notification.
- If notification delivery fails repeatedly, the event is published to
shipment-events.DLQ.
The ShipmentEvent schema is considered a stable contract between services.
Schema changes must follow backward-compatible rules:
- new optional fields may be added
- existing fields must not be removed
- field types must remain compatible
CREATED → PICKED_UP → IN_TRANSIT → OUT_FOR_DELIVERY → DELIVERED
↘
FAILED (reachable from any state)
Transitions are forward-only, enforced by ordinal comparison. FAILED is the only non-linear state.
| Topic | Partitions | Replication Factor | Producer | Consumers |
|---|---|---|---|---|
shipment-events |
3 | 1 | order-service | tracking-service, notification-service |
shipment-events.DLQ |
1 | 1 | notification-service | — |
| Group ID | Service |
|---|---|
tracking-service-group |
tracking-service |
notification-service-group |
notification-service |
Events are serialized as JSON. Kafka type headers are disabled on both sides:
- Producers —
ADD_TYPE_INFO_HEADERS=false - Consumers —
USE_TYPE_INFO_HEADERS=false+VALUE_DEFAULT_TYPEset to the local event class
This prevents cross-service ClassNotFoundException when the deserializer attempts to resolve the producer's fully-qualified class name. See engineering-decisions.md for rationale.