Domain Events
📝 Note: This guide uses generic placeholder names to be reusable across any Spring Boot microservice. Replace with your actual implementation:
{YourService}→ Your service name (e.g.,OrderService,PaymentService)BusinessService→ Your core service (e.g.,OrderService,UserService)DataService→ Your data processing service (e.g.,PaymentService,InventoryService)IntegrationService→ Your external integration (e.g.,PaymentGatewayService){RequestType}→ Your request DTO (e.g.,CreateOrderRequest){ResponseType}→ Your response DTO (e.g.,OrderResponse)
Purpose
Provides domain event classes for asynchronous communication with external systems via AWS SQS message queues. Events represent significant business occurrences published to downstream consumers.
Package Structure
event/
└── DomainEvent.java # workflow activation notification event
Key Concepts
Domain Event Pattern
Events represent business facts that have occurred (e.g., workflow activation).
Event-Driven Architecture
- Decouples producers from consumers through asynchronous messaging
- Uses AWS SQS FIFO queues for reliable, ordered delivery
- Ensures exactly-once delivery per message group
- Idempotent publishing prevents duplicate notifications
Event Schema: DomainEvent
| Field | Type | Description |
|---|---|---|
account_id |
UUID | AMC account identifier |
mvpd |
String | external provider name (lowercase) |
adobe_id |
String | Adobe Audience Manager person ID |
household_id |
String | Adobe household identifier |
activated_at |
String | ISO-8601 UTC timestamp |
channel_id |
String | MVPD channel identifier |
Example JSON:
{
"account_id": "550e8400-e29b-41d4-a716-446655440000",
"mvpd": "spectrum",
"adobe_id": "adobe-person-id",
"household_id": "household-id",
"activated_at": "2025-06-02T14:30:45.123Z",
"channel_id": "amcplus"
}
Implementation Patterns
Builder Pattern
Events use static nested builder for fluent creation:
DomainEvent event = DomainEvent.MvpdActivationEventBuilder.builder()
.withAccountId(accountId)
.withMvpd("spectrum")
.withAdobeId("adobe-id-123")
.withActivatedAt("2025-06-02T14:30:45.123Z")
.build();
Idempotent Publishing
Database-backed deduplication prevents duplicate events:
if (mvpdActivationHistoryRepository.existsByAccountIdAndMvpd(accountId, mvpd)) {
return; // Already notified
}
sqsPublisher.publishEventToFifoQueue(event, messageGroupId);
FIFO Message Grouping
Message group ID ensures ordering per entity:
var messageGroupId = accountId + "." + mvpd;
sqsPublisher.publishEventToFifoQueue(event, messageGroupId);
UTC Timestamp Normalization
Always use UTC for consistency:
var activatedAt = ZonedDateTime.now(ZoneId.of("UTC"))
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX"));
Quick Start
Create Event
DomainEvent event = MvpdActivationEventBuilder.builder() .withAccountId(accountId) .withMvpd(mvpd) .withActivatedAt(timestamp) .build();Check Idempotency
if (repository.existsByAccountIdAndMvpd(accountId, mvpd)) { return; }Publish to SQS
var messageGroupId = accountId + "." + mvpd; sqsPublisher.publishEventToFifoQueue(event, messageGroupId);
Testing Strategy
- Unit tests: Verify event creation, idempotency, message grouping
- Integration tests: Use Testcontainers with LocalStack for SQS
- Mock: Repository and SQS publisher in unit tests
- Verify: Message group IDs, event fields, idempotency behavior
Configuration
mvpd.activate.sqs.queue.url=${MVPD_ACTIVATE_SQS_QUEUE_URL}
Documentation
Guides
- Architecture - Event patterns and flow diagrams
- Best Practices - Builder pattern, idempotency, FIFO ordering
- Anti-Patterns - Common mistakes to avoid
- Testing - Unit and integration testing strategies
- SQS Configuration - Queue setup and monitoring
Examples
- DomainEvent.java - Event class with builder
- EventPublishingServiceExample.java - Publishing service
- EventPublishingServiceExampleTest.java - Unit tests
- EventProcessingServiceExample.java - Service integration
Code Formatting
All Java code formatted using Spotless with Google Java Format (AOSP style):
./gradlew spotlessApply