Skip to content

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
Hold "Alt" / "Option" to enable pan & zoom

βœ… 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
Hold "Alt" / "Option" to enable pan & zoom

βœ… 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)
Hold "Alt" / "Option" to enable pan & zoom

βœ… 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