Domain Events¶
Info
At ConnectSoft, Domain Events are first-class citizens β
capturing business facts, enabling event-driven architectures, and ensuring systems are resilient, reactive, and loosely coupled.
Introduction¶
Domain Events represent something significant that happened in the business domain.
They capture facts, not commands.
Once something happens (like an order placed, a patient admitted, or a transaction completed), a Domain Event records it permanently β
allowing other parts of the system (or even other systems) to react, adapt, or evolve based on that fact.
At ConnectSoft, Domain Events are central to achieving:
- Resilient Microservices
- Event-Driven Architectures (EDA)
- Event Sourcing patterns
- Decoupled integrations
- Real-time reactions across SaaS, AI, and financial platforms
Concept Definition¶
A Domain Event:
-
Describes a Past Fact
It records something that has already happened, e.g.,OrderPlaced,PaymentCompleted,PatientRegistered. -
Is Immutable
Once raised, an event must not be changed β it becomes part of the system history. -
Is Raised by Aggregates
Domain Events are typically emitted as a side effect of state changes inside aggregates. -
Is Asynchronous by Nature
The raiser of an event does not expect an immediate reaction. -
May Trigger Multiple Reactions
Different subsystems may react differently to the same event.
π Why Domain Events Matter at ConnectSoft¶
β Loosely Coupled Systems
- Producers and consumers are independent β systems evolve without breaking each other.
β Scalable Event-Driven Architecture
- React to important business changes across microservices, workflows, and distributed systems.
β Resilient Integration
- Events are reliable, auditable, and can be replayed or reprocessed safely.
β Business Auditability
- Domain Events naturally create a historical record of critical business actions.
β Foundation for Event Sourcing
- Domain Events form the basis of event-sourced systems when needed.
π§© Visual: Domain Events in System Interaction¶
sequenceDiagram
participant OrderAggregate
participant MessagingBus
participant InventoryService
participant NotificationService
OrderAggregate->>MessagingBus: Publish OrderPlacedEvent
MessagingBus->>InventoryService: OrderPlacedEvent Consumed
MessagingBus->>NotificationService: OrderPlacedEvent Consumed
β
An OrderPlacedEvent flows asynchronously to multiple consumers.
β
Systems are reactive, decoupled, and resilient.
Strategic Design Principles for Domain Events¶
At ConnectSoft, Domain Events are not optional add-ons β
they are core structural elements for system communication, business transparency, and resilience.
π Core Principles¶
1. Events Represent Completed Facts¶
β Domain Events describe what already happened, never intentions or commands.
Examples:
- β
OrderPlaced
- β
PaymentAuthorized
- β
AppointmentScheduled
2. Events Are Immutable¶
β
Once created, Domain Events must never change.
β
Treat events like historical records β auditable, reliable, and permanent.
3. Aggregates Raise Events Internally¶
β Events should be raised inside Aggregate methods immediately after business rule validations succeed.
β
Example:
Inside Order.Place(), after successful validation β raise OrderPlacedEvent.
4. Publish After Transaction Commit¶
β Only publish events to external systems after the transaction commits successfully to avoid "ghost events" that announce actions that never happened.
β Use Outbox Pattern to reliably handle event publishing with transactional safety.
5. Asynchronous First¶
β Event consumers should not expect immediate results from event publishing.
β Systems must be designed to react eventually, not synchronously.
6. Version Domain Events¶
β
Over time, events evolve.
β
Version them safely to allow backward compatibility.
π§© Visual: Proper Event Lifecycle¶
flowchart TD
UserAction("User Action / API Request")
DomainLogic("Aggregate Executes Business Logic")
RaiseEvent("Aggregate Raises Domain Event")
TransactionCommit("Transaction Commits Successfully")
PublishEvent("Event Published to Messaging Bus")
ConsumeEvent("Consumers React to Event")
UserAction --> DomainLogic
DomainLogic --> RaiseEvent
RaiseEvent --> TransactionCommit
TransactionCommit --> PublishEvent
PublishEvent --> ConsumeEvent
β
Events are raised inside the domain.
β
Publishing happens only after persistence succeeds.
β
Consumers handle events asynchronously and idempotently.
Anti-Patterns to Avoid with Domain Events¶
| Anti-Pattern | Symptom | Why It's Dangerous |
|---|---|---|
| Command-like Events | Events named like commands (PlaceOrder) that describe intention instead of facts. |
Violates event immutability and decoupling. |
| Mutable Events | Changing event data after creation. | Breaks auditability, traceability, and downstream consistency. |
| Publishing Before Commit | Publishing events before DB transaction commits. | Risk of ghost events if transaction fails. |
| Tight Coupling to Specific Consumers | Raising events with consumer-specific knowledge. | Breaks independence, limits flexibility. |
| Overloading Event Semantics | Making one event mean multiple things. | Causes confusion, brittle event handlers. |
| Ignoring Event Versioning | Changing event schema without backward compatibility. | Breaks older consumers, causes system failures. |
π― Quick ConnectSoft Checklist for Domain Events¶
| β Good Practice | π« Bad Practice |
|---|---|
| Name events as facts | Name events like commands |
| Immutable after creation | Modify event after raising |
| Raise inside Aggregates | Raise from Application Service |
| Publish after commit | Publish during domain operations |
| Use outbox pattern if needed | Rely on at-least-once delivery only |
| Version carefully | Break old consumers |
C# Examples: Domain Event Lifecycle in Practice¶
At ConnectSoft, Domain Events are modeled, raised, and dispatched with clear structure and tactical precision.
π οΈ Raising a Domain Event inside an Aggregate¶
// Domain Event Interface
public interface IDomainEvent
{
DateTime OccurredOn { get; }
}
// Specific Domain Event
public class OrderPlacedEvent : IDomainEvent
{
public Guid OrderId { get; }
public Guid CustomerId { get; }
public DateTime OccurredOn { get; }
public OrderPlacedEvent(Guid orderId, Guid customerId)
{
OrderId = orderId;
CustomerId = customerId;
OccurredOn = DateTime.UtcNow;
}
}
// Aggregate Root
public class Order
{
private readonly List<IDomainEvent> _domainEvents = new();
public Guid OrderId { get; private set; }
public Guid CustomerId { get; private set; }
public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
public Order(Guid orderId, Guid customerId)
{
OrderId = orderId;
CustomerId = customerId;
}
public void Place()
{
// Business rule validations...
_domainEvents.Add(new OrderPlacedEvent(OrderId, CustomerId));
}
public void ClearDomainEvents()
{
_domainEvents.Clear();
}
}
β
Aggregate raises the event internally.
β
Domain Event stored temporarily until unit of work completes.
π οΈ Publishing Domain Events with Outbox Pattern¶
public class UnitOfWork
{
private readonly DbContext _dbContext;
private readonly IOutboxService _outboxService;
public UnitOfWork(DbContext dbContext, IOutboxService outboxService)
{
_dbContext = dbContext;
_outboxService = outboxService;
}
public async Task CommitAsync(AggregateRoot aggregateRoot)
{
var domainEvents = aggregateRoot.DomainEvents.ToList();
await _dbContext.SaveChangesAsync();
foreach (var domainEvent in domainEvents)
{
await _outboxService.SaveEventAsync(domainEvent);
}
aggregateRoot.ClearDomainEvents();
}
}
β
Events are persisted reliably after the domain transaction.
β
Publishing can be handled by an outbox dispatcher asynchronously.
π Supporting Outbox Dispatcher Example¶
public class OutboxDispatcher : BackgroundService
{
private readonly IOutboxService _outboxService;
private readonly IMessageBus _messageBus;
public OutboxDispatcher(IOutboxService outboxService, IMessageBus messageBus)
{
_outboxService = outboxService;
_messageBus = messageBus;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var pendingEvents = await _outboxService.GetPendingEventsAsync();
foreach (var domainEvent in pendingEvents)
{
await _messageBus.PublishAsync(domainEvent);
await _outboxService.MarkEventAsPublishedAsync(domainEvent);
}
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
}
β
Guaranteed delivery even across service restarts or failures.
β
Events are processed asynchronously, supporting true Event-Driven resilience.
π§© Advanced ConnectSoft Event-Driven Flow¶
sequenceDiagram
participant UserInterface
participant ApplicationService
participant Aggregate
participant Database
participant Outbox
participant MessageBus
participant ExternalService
UserInterface->>ApplicationService: Submit Action (e.g., PlaceOrder)
ApplicationService->>Aggregate: Execute Domain Behavior
Aggregate-->>ApplicationService: Raise Domain Event
ApplicationService->>Database: Persist Aggregate Changes
ApplicationService->>Outbox: Save Domain Events
MessageBus->>ExternalService: Deliver Event (OrderPlaced)
β Events are first-class part of the flow β not an afterthought.
Best Practices for Domain Events¶
At ConnectSoft, Domain Events are treated as strategic infrastructure β
not optional enhancements β for enabling resilient, scalable, event-driven systems.
π Best Practices Checklist¶
β Model Events as Past Facts
- Always describe something that already happened.
β Keep Events Immutable
- Once raised, event data must never change.
β Raise Events Inside Aggregates
- Domain Events are natural side effects of aggregate behaviors.
β Publish After Persistence
- Only publish Domain Events after the transaction commits.
β Persist with Outbox Pattern
- Save events reliably alongside transactional data to guarantee delivery.
β Design for Asynchronous Processing
- Event consumers must not assume immediate consistency or responses.
β Version Events Safely
- Plan for backward compatibility β systems evolve, events must too.
β Use Strong Typing
- Prefer typed domain event classes over generic messages.
β Emit Business-Relevant Events
- Only publish events that matter to external consumers or workflows.
β Track Event History for Auditability
- Domain Events form part of the systemβs traceable history.
Conclusion¶
Domain Events are fundamental to building dynamic, reactive, and evolvable systems at ConnectSoft.
They capture meaningful business milestones,
enable event-driven architectures across microservices,
and provide a resilient communication backbone between autonomous systems.
When modeled and handled properly:
- Systems are decoupled yet cohesive.
- Business workflows react in real-time across distributed services.
- Scaling new capabilities becomes natural, not dangerous.
- Historical business facts become assets, not mysteries.
Domain Events are not just a technical artifact.
They are a strategic bridge between the real-world domain and the evolving software system.
At ConnectSoft, every meaningful business state change deserves to be captured, respected, and leveraged.
"Domain Events are the heartbeat of evolving software.
If you listen to the events, you can hear your system grow."
References¶
-
Books and Literature
- Eric Evans β Domain-Driven Design: Tackling Complexity in the Heart of Software
- Vaughn Vernon β Implementing Domain-Driven Design
- Greg Young β Event Sourcing and CQRS Essentials
-
Online Resources
-
ConnectSoft Internal Standards
- ConnectSoft Event-Driven Microservices Architecture
- ConnectSoft Outbox and Messaging Reliability Guidelines
- ConnectSoft Event Versioning and Evolution Playbook