-
Notifications
You must be signed in to change notification settings - Fork 16
Architecture
LiteBus is an in-process mediator with semantic modules for commands, queries, and events. Inbox and outbox modules add storage boundaries without turning the mediator into a broker.
| Layer | Responsibility | Main packages |
|---|---|---|
| Runtime | Container-neutral module and dependency registration |
LiteBus.Runtime.Abstractions, LiteBus.Runtime
|
| Messaging | Message registry, handler descriptors, mediation pipeline, execution context, contract registry |
LiteBus.Messaging.Abstractions, LiteBus.Messaging
|
| Semantic modules | Command, query, and event mediators with type-specific APIs |
LiteBus.Commands, LiteBus.Queries, LiteBus.Events
|
| Storage modules | Explicit command scheduling and event publication storage |
LiteBus.Inbox, LiteBus.Outbox
|
| Infrastructure packages | Optional DI and storage adapters | Microsoft DI, Autofac, PostgreSQL packages |
Applications call AddLiteBus and register modules. Each module contributes dependency descriptors to the runtime registry. The selected DI adapter translates those descriptors into container registrations.
Message modules also register message and handler metadata. RegisterFromAssembly scans for concrete handlers, concrete messages, and supported open generic handlers. Registration order does not matter because the message registry links handlers to messages after each registration.
Commands, queries, and events all use the messaging layer, but each semantic module chooses its own strategy.
| Message kind | Entry point | Handler rule | Result rule |
|---|---|---|---|
| Command without result | ICommandMediator.SendAsync(ICommand) |
exactly one main handler | returns Task
|
| Command with result | ICommandMediator.SendAsync(ICommand<TResult>) |
exactly one main handler | returns Task<TResult>
|
| Query | IQueryMediator.QueryAsync |
exactly one main handler | returns the query result |
| Stream query | IQueryMediator.StreamAsync |
exactly one stream handler | returns IAsyncEnumerable<TResult>
|
| Event | IEventPublisher.PublishAsync |
zero or more handlers | returns after selected handlers complete |
Pre-handlers run before the main handler. Post-handlers run after the main handler. Error handlers run when the main handler path throws. Event handlers can be ordered by priority and executed sequentially or concurrently according to event mediation settings.
The messaging layer creates an ambient execution context for each mediation call. Handlers can read tags, cancellation, and Items from AmbientExecutionContext.Current. The context uses async flow, so it follows awaited work and stream query enumeration.
Use context data for technical policy such as tracing, tenant id, metrics, handler filters, or inbox execution flags. Keep business data in the message contract.
The command inbox has a separate API from the command mediator.
caller
-> ICommandScheduler.ScheduleAsync
-> IMessageContractRegistry.GetContract(runtime command type)
-> IMessageSerializer.SerializeAsync
-> ICommandInboxWriter.AddAsync
-> CommandReceipt<TCommand>
worker
-> ICommandInboxProcessor.ProcessPendingAsync
-> ICommandInboxLeaseStore.LeasePendingAsync
-> IMessageContractRegistry.GetMessageType(contract name, version)
-> IMessageSerializer.DeserializeAsync
-> ICommandMediator.SendAsync
-> ICommandInboxStateStore.MarkCompletedAsync / MarkFailedAsync / MoveToDeadLetterAsync
SendAsync always executes immediately. ScheduleAsync always stores for later execution. Queries are never scheduled.
The outbox has a separate API from event mediation.
application transaction
-> IIntegrationOutbox.AddAsync or IOutboxWriter.AddAsync
-> IMessageContractRegistry.GetContract(runtime event type)
-> IMessageSerializer.SerializeAsync
-> IOutboxMessageWriter.AddAsync
-> OutboxReceipt<TEvent>
publisher
-> IOutboxProcessor.ProcessPendingAsync
-> IOutboxMessageLeaseStore.LeasePendingAsync
-> IOutboxDispatcher.DispatchAsync
-> IOutboxMessageStateStore.MarkPublishedAsync / MarkFailedAsync / MoveToDeadLetterAsync
PublishAsync notifies in-process handlers now. AddAsync stores an event for later publication. The LiteBus event dispatcher can replay stored events into the local event pipeline. Broker dispatchers can publish the same envelope to external transports.
Stores are split by capability.
| Capability | Inbox | Outbox |
|---|---|---|
| Append accepted work | ICommandInboxWriter |
IOutboxMessageWriter |
| Lease due work | ICommandInboxLeaseStore |
IOutboxMessageLeaseStore |
| Record result state | ICommandInboxStateStore |
IOutboxMessageStateStore |
The PostgreSQL stores implement all roles because one table owns the data. Application services and processors still depend on narrow interfaces.
Stored rows contain contract name, contract version, and serialized payload. The default registry maps concrete CLR types to names and versions. The default serializer uses System.Text.Json with web defaults.
Closed generic messages are supported when each closed type has a registered contract. Open generic contracts are rejected because a stored row must map to one concrete CLR type during deserialization.
Processors own retry timing. Stores own state. A failed attempt records error text and a next visible timestamp. After the maximum attempts are reached, the processor moves the row to dead-letter state.
Handlers and dispatchers must be idempotent. The inbox and outbox provide at-least-once behavior, not exactly-once side effects.
PostgreSQL stores use raw Npgsql commands. Tables use jsonb payloads and FOR UPDATE SKIP LOCKED leasing. Schema helpers support migration-owned DDL (GetCreateScript, GetUpgradeScript), explicit bootstrap (EnsureAsync), opt-in host bootstrap, version metadata, advisory-locked upgrades, and startup validation. See PostgreSQL Schema Management.
- LiteBus mediates in process. It does not own broker connections in core packages.
- Inbox and outbox modules store commands and events, but workers decide when processors run.
- Storage packages are optional and stay outside semantic modules.
- DI adapters translate descriptors; mediator code does not depend on Microsoft DI or Autofac.