Domain Events vs Integration Events cover image

Domain Events vs Integration Events

By Julian Krause · Monday, January 5, 2026 · ~3 min read

Events are everywhere in modern architectures, but not all events are created equal. The distinction between domain events and integration events is fundamental, and confusing them leads to brittle systems, lost messages, and subtle data inconsistencies. Let me clarify the boundary.

Domain Events Are In-Process

A domain event represents something that happened within a single bounded context. It is dispatched and handled within the same process, typically within the same database transaction. Domain events are part of your domain model.

public record OrderConfirmedDomainEvent(
    Guid OrderId,
    Guid CustomerId,
    decimal TotalAmount) : IDomainEvent;

// Dispatched inside the aggregate
public class Order
{
    private readonly List<IDomainEvent> _domainEvents = [];

    public void Confirm()
    {
        Guard.Against(Status != OrderStatus.Pending, "Only pending orders can be confirmed");
        Status = OrderStatus.Confirmed;
        ConfirmedAt = DateTimeOffset.UtcNow;
        _domainEvents.Add(new OrderConfirmedDomainEvent(PublicId, CustomerId, TotalAmount));
    }
}

// Handled in-process, same transaction
public class WhenOrderConfirmed_UpdateCustomerStats(AppDbContext db)
    : IDomainEventHandler<OrderConfirmedDomainEvent>
{
    public async Task Handle(OrderConfirmedDomainEvent @event, CancellationToken ct)
    {
        var customer = await db.Customers.FindAsync([@event.CustomerId], ct);
        customer!.IncrementOrderCount();
        customer.AddToTotalSpend(@event.TotalAmount);
    }
}

Because domain events execute within the same transaction, they are inherently consistent. If the handler fails, the entire transaction rolls back, including the original aggregate change.

Integration Events Cross Boundaries

An integration event is a message published to other bounded contexts or services, typically via a message broker. It represents a fact that external systems might care about. Integration events are part of your contract with the outside world.

// Integration event — published to a message broker
public record OrderConfirmedIntegrationEvent(
    Guid OrderId,
    Guid CustomerId,
    decimal TotalAmount,
    DateTimeOffset ConfirmedAt,
    string CurrencyCode);

// Published after the transaction commits
public class OrderConfirmedIntegrationEventPublisher(IMessageBus messageBus)
    : IDomainEventHandler<OrderConfirmedDomainEvent>
{
    public async Task Handle(OrderConfirmedDomainEvent @event, CancellationToken ct)
    {
        // Map domain event to integration event
        var integrationEvent = new OrderConfirmedIntegrationEvent(
            @event.OrderId,
            @event.CustomerId,
            @event.TotalAmount,
            DateTimeOffset.UtcNow,
            "EUR");

        await messageBus.PublishAsync(integrationEvent, ct);
    }
}

Why the Distinction Matters

Domain events can contain internal implementation details — entity IDs, internal state, references to domain objects. Integration events must not. They are a public API and must be versioned, documented, and backward-compatible.

Domain events are synchronous and transactional. Integration events are asynchronous and eventually consistent. This difference affects error handling: a domain event handler failure rolls back the transaction; an integration event handler failure requires retry policies, dead letter queues, and compensating actions.

The Outbox Bridge

The gap between domain events and integration events is bridged by the transactional outbox pattern. When a domain event triggers an integration event, you do not publish directly to the message broker (which could fail independently of your transaction). Instead, you write the integration event to an outbox table within the same transaction. A background process reads the outbox and publishes to the broker.

public class OutboxDomainEventHandler(AppDbContext db)
    : IDomainEventHandler<OrderConfirmedDomainEvent>
{
    public async Task Handle(OrderConfirmedDomainEvent @event, CancellationToken ct)
    {
        var outboxMessage = new OutboxMessage
        {
            Id = Guid.NewGuid(),
            Type = nameof(OrderConfirmedIntegrationEvent),
            Payload = JsonSerializer.Serialize(new OrderConfirmedIntegrationEvent(
                @event.OrderId, @event.CustomerId, @event.TotalAmount,
                DateTimeOffset.UtcNow, "EUR")),
            CreatedAt = DateTimeOffset.UtcNow
        };

        db.OutboxMessages.Add(outboxMessage);
        // Saved as part of the same transaction as the order confirmation
    }
}

Getting this distinction right early saves enormous pain later. Domain events are cheap and simple. Integration events require infrastructure, versioning, and operational maturity. Start with domain events and promote to integration events only when another bounded context genuinely needs to react.


Comments (2)

Anna Tuesday, March 3, 2026 5:08 PM

Great post! I learned a lot from this.

Clara Wednesday, February 25, 2026 8:08 PM

Good question, I'd like to know too.

Ben Wednesday, March 4, 2026 5:08 PM

Interesting perspective, I hadn't thought of it that way.

Leave a Comment