Pular para o conteúdo principal
Versão: main (dev) 🚧

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

TipoNomeResponsabilidade
EnumEMessageDispositionSettlement disposition for a processed broker message: Complete, Abandon, DeadLetter
RecordMessageHandlingResultOutcome of processing a single broker message, carrying disposition, optional Error, transient flag and retry delay
Static ClassMessagingErrorsCatalog of well-known messaging errors (InvalidPayload, ProcessingFailed)
EnumEventBusTypeSelects the publish transport: InMemory or External
InterfaceIDomainEventsBusPublishes domain events (PublishAsync<T> where T : class, IDomainEvent)
InterfaceIDomainEventsBusFactoryResolves an IDomainEventsBus for an EventBusType (keyed DI)
InterfaceINotificationsBusPublishes notification events (PublishAsync<T> where T : class, INotificationEvent)
InterfaceIDomainEventMessageHandler<in T>Handles a broker-delivered domain event, returning MessageHandlingResult
ClassInMemoryDomainEventsQueue / InMemoryNotificationsQueueUnbounded Channel<T> buffering events for the in-process processors
ClassInMemoryDomainEventsBus / InMemoryNotificationsBusIn-process bus that enqueues onto the matching queue
ClassDomainEventsProcessor / NotificationEventsProcessorBackgroundService that drains the queue and republishes via Mediator IPublisher in a fresh scope
ClassDomainEventsBusFactoryIDomainEventsBusFactory resolving keyed IDomainEventsBus; throws a clear error for an unregistered transport
ClassRetryBackoffGate<T>SemaphoreSlim-based global backoff window for a receiver type
ClassServiceBusEventReceiver<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
ClassServiceBusReceiverHostedService<T>IHostedService that starts/stops the receiver for a queue
RecordServiceBusReceiverQueue<T>Binds a queue name to a ServiceBusEventReceiver<T> registration
Static ClassDevPackMessagingServiceCollectionExtensionsAddDevPackInMemoryEventBus, 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.External publisher here — it lives in the consumer (canon owns the Args[0] routing convention).
  • Do not use Newtonsoft — the receiver uses System.Text.Json end 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 ServiceBusEventsBus publisher as the keyed External IDomainEventsBus.
  • The consumer registers one IDomainEventMessageHandler<T> per consumed event type.
  • Worker hosts need ICurrentUser / AsyncLocalCurrentUserAccessor registered — the receiver swaps the user to a servicebus-worker scoped to the event's TenantId.