ITLab.Template.DevPack.Messaging
Provides the event bus abstractions (domain events + notification events) and a generic transport harness: an in-process channel-backed bus plus an Azure Service Bus receiver harness with settlement (complete / abandon / dead-letter), retry backoff, and worker impersonation. Also hosts the broker-side settlement contracts (EMessageDisposition, MessageHandlingResult, MessagingErrors).
The boundary: in-process domain events of the canon are dispatched post-SaveChanges by Infrastructure.EfCore.DispatchDomainEventsInterceptor → Mediator. This namespace adds an explicit channel for integration / bus events (in-memory or cross-process via Service Bus) without removing that pipeline. See ADR-011. The concrete Service Bus publisher (EventBusType.External keyed bus) and the routing convention live in the consumer (the canon), not here.
Componentes
| Tipo | Nome | Responsabilidade |
|---|---|---|
| Enum | EMessageDisposition | Settlement disposition for a processed broker message: Complete, Abandon, DeadLetter |
| Record | MessageHandlingResult | Outcome of processing a single broker message, carrying disposition, optional Error, transient flag and retry delay |
| Static Class | MessagingErrors | Catalog of well-known messaging errors (InvalidPayload, ProcessingFailed) |
| Enum | EventBusType | Selects the publish transport: InMemory or External |
| Interface | IDomainEventsBus | Publishes domain events (PublishAsync<T> where T : class, IDomainEvent) |
| Interface | IDomainEventsBusFactory | Resolves an IDomainEventsBus for an EventBusType (keyed DI) |
| Interface | INotificationsBus | Publishes notification events (PublishAsync<T> where T : class, INotificationEvent) |
| Interface | IDomainEventMessageHandler<in T> | Handles a broker-delivered domain event, returning MessageHandlingResult |
| Class | InMemoryDomainEventsQueue / InMemoryNotificationsQueue | Unbounded Channel<T> buffering events for the in-process processors |
| Class | InMemoryDomainEventsBus / InMemoryNotificationsBus | In-process bus that enqueues onto the matching queue |
| Class | DomainEventsProcessor / NotificationEventsProcessor | BackgroundService that drains the queue and republishes via Mediator IPublisher in a fresh scope |
| Class | DomainEventsBusFactory | IDomainEventsBusFactory resolving keyed IDomainEventsBus; throws a clear error for an unregistered transport |
| Class | RetryBackoffGate<T> | SemaphoreSlim-based global backoff window for a receiver type |
| Class | ServiceBusEventReceiver<T> | Service Bus consumer: deserialize (System.Text.Json), validate, impersonate servicebus-worker, dispatch to IDomainEventMessageHandler<T>, settle (max 5 deliveries). Exposes pure DecideDisposition and an optional transient classifier hook |
| Class | ServiceBusReceiverHostedService<T> | IHostedService that starts/stops the receiver for a queue |
| Record | ServiceBusReceiverQueue<T> | Binds a queue name to a ServiceBusEventReceiver<T> registration |
| Static Class | DevPackMessagingServiceCollectionExtensions | AddDevPackInMemoryEventBus, AddDevPackServiceBusClient, AddDevPackServiceBusEventReceiver<T> |
Como usar
In-memory bus (default, no Azure)
services.AddDevPackInMemoryEventBus(); // queues + processors + keyed InMemory bus + factory
// publish
await factory.Create(EventBusType.InMemory).PublishAsync(new OrderPlacedEvent(orderId));
// the DomainEventsProcessor republishes through Mediator IPublisher in a fresh scope
Azure Service Bus receiver (worker host)
services.AddDevPackServiceBusClient(); // connection string OR managed identity
services.AddDevPackServiceBusEventReceiver<OrderPlacedEvent>("orders"); // gate + receiver + hosted service
services.AddScoped<IDomainEventMessageHandler<OrderPlacedEvent>, OrderPlacedMessageHandler>();
// handler decides the settlement outcome:
public Task<MessageHandlingResult> HandleAsync(OrderPlacedEvent msg, CancellationToken ct)
=> Task.FromResult(MessageHandlingResult.Completed());
The ServiceBusClient is built from ConnectionStrings:ServiceBus when present, otherwise from
ServiceBusOptions.Namespace using DefaultAzureCredential (managed identity).
Extending transient classification
ServiceBusEventReceiver<T> classifies ServiceBusException.IsTransient and TimeoutException as
transient out of the box. Inject a Func<Exception, TimeSpan?>? to extend this — return a non-null
delay to treat an exception as transient (pause + abandon); return null to fall back to the built-in
rules.
O que não fazer
- Do not use this namespace for in-process CQRS commands or queries — those live in
Cqrs/. - Do not invent custom dispositions outside
EMessageDisposition— broker semantics are fixed. - Do not register the
EventBusType.Externalpublisher here — it lives in the consumer (canon owns theArgs[0]routing convention). - Do not use Newtonsoft — the receiver uses
System.Text.Jsonend to end. - Do not assume exactly-once delivery — publication is best-effort; transactional outbox is a future follow-up (ADR-011).
Extensão pelo produto
- The consumer registers the Azure
ServiceBusEventsBuspublisher as the keyedExternalIDomainEventsBus. - The consumer registers one
IDomainEventMessageHandler<T>per consumed event type. - Worker hosts need
ICurrentUser/AsyncLocalCurrentUserAccessorregistered — the receiver swaps the user to aservicebus-workerscoped to the event'sTenantId.