And here is a simple C# example:
/// <summary>
/// Creates a user and tries to send a `UserCreated` event optimistically.
/// </summary>
public async Task CreateUserAsync(UserDto input)
{
// Create domain object
var user = new User { Id = Guid.NewGuid(), Email = input.Email };
_dbContext.Users.Add(user);
// Prepare outbox event alongside domain entity
var userCreatedEvent = new UserCreated { user.Id, user.Email }
var outboxEvent = new OutboxEvent
{
Id = Guid.NewGuid(),
EventType = userCreatedEvent.GetType().Name,
Payload = JsonSerializer.Serialize(userCreatedEvent),
CreatedAt = DateTime.UtcNow
};
_dbContext.OutboxEvents.Add(outboxEvent);
// Atomic insert of domain state and outbox event to database
await _dbContext.SaveChangesAsync();
try
{
// Attempt to publish right after commit
await _eventPublisher.PublishAsync(outboxEvent);
// Mark sent event for deletion
_dbContext.OutboxEvents.Remove(outboxEvent);
// Send delete command to database
await _dbContext.SaveChangesAsync();
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Optimistic send failed. Relay will handle it.");
}
}
Now let’s examine potential issues with this approach:
Because of most events get sent immediately and a few get delayed, the order of delivery isn’t guaranteed. If order matters, the consumers need to handle it, or this implementation is not an option for that particular problem. To be honest, the standard implementation also does not guarantee in-order delivery by default.
If the fallback logic has a “too short” acceptable delay window, the system might have many duplicates, but as with any other at-least-once delivery, the consumer should handle it.
The observability is still needed. Observability is inevitable, but this time it might be slightly simpler.
The good about this approach: we don’t have to rebuild anything, just slightly upgrade the happy/default path. If you already using something like MediatR or any cross-cutting concern implementation for sending outbox events, it’s just a simple upgrade in just a few places of your codebase.