This page defines the current LiteBus public API model: named types, CLR shapes, package placement, method signatures, parameter limits, mapping boundaries, and durable-messaging examples.
- Contributors adding public types to
*.Abstractionspackages - Reviewers checking whether a new API matches an existing pattern
- Maintainers extending existing surfaces during feature work
Prerequisites: Architecture (dependency role model), Dependency graph (package inventory).
| Suffix | Responsibility | Examples |
|---|---|---|
*Item |
One command unit (message payload + metadata) | InboxAcceptItem, OutboxEnqueueItem |
*Metadata |
Per-message durable annotations on an *Item |
InboxAcceptMetadata, OutboxEnqueueMetadata |
*Request |
Operation input without a message body | InboxLeaseRequest, TransportPublishRequest |
*Settings |
Per-invocation mediation tuning | CommandMediationSettings, EventMediationSettings |
*Options |
Module, host, processor, or adapter configuration | ProcessorOptions, PostgreSqlInboxStoreOptions |
*HostOptions |
Background-service lifecycle | InboxProcessorHostOptions |
*Filter |
Query or purge predicates | InboxMessageFilter |
*Binding |
Framework adapter input at the HTTP or host edge | InboxQueryBinding, OutboxPurgeBinding |
| (no suffix) | Small, shared value objects | SagaCorrelation, MessageIdentity |
- Mediation keeps
*Settings(CommandMediationSettings,EventMediationSettings,QueryMediationSettings) for per-call pipeline tuning. Custom mediators built onIMessageMediatoruseMessageMediationRequest<TMessage, TResult>for strategy and resolve overrides;CancellationTokenstays onMediateonly, not inside the request bag. - Durable writers use
*Itemplus*Metadata. Scheduling and visibility belong on metadata (MessageVisibility), not separate scheduler interfaces. - Framework bindings use
*Bindingfor ASP.NET management and similar adapter input. Bindings map once at the adapter edge; they are not writer*Itemtypes.
Public types use a consistent CLR kind per role.
| Kind | Use in LiteBus | Do not use for |
|---|---|---|
sealed record |
*Item, *Metadata, *Filter, *Request, *Receipt, *Settings; immutable *Options with init only; static From / named factories on *Item |
Mutable host bags |
record (non-sealed) |
Rare bases (ProcessorOptions, PostgreSqlSchemaStoreOptions) |
New public APIs unless inheritance is required |
sealed class |
*HostOptions with get/set; modules, mediators, stores; optional thin construction namespaces (OutboxEnqueue); exceptions |
New immutable value objects |
readonly struct / readonly record struct |
Internal perf helpers and store keys only | Public contracts |
interface |
*.Abstractions contracts |
: |
*HostOptions stay sealed class: host and processor lifecycle bags remain mutable after instantiation (ProcessorHostOptions, InboxCleanupHostOptions, and similar).
Durable writer vocabulary is message-centric on both axes. Do not use Payload as the public item body property; it collides with transport encryption wording (IPayloadEncryptor) and with metadata documented as outside the payload.
flowchart LR
subgraph writerAPI [Writer API boundary CQRS]
InboxAccept["IInbox.AcceptAsync"]
OutboxEnqueue["IOutbox.EnqueueAsync"]
TMessage["InboxAcceptItem of TMessage"]
TEvent["OutboxEnqueueItem of TEvent"]
end
subgraph itemShape [Shared item shape]
MessageProp["Message property on both"]
MessageType["MessageType on untyped batch item"]
Metadata["*Metadata variants"]
end
subgraph persistence [Persistence and query]
Envelope["*Envelope"]
Filter["*MessageFilter"]
Receipt["*Receipt / Receipt of T"]
end
InboxAccept --> TMessage --> MessageProp
OutboxEnqueue --> TEvent --> MessageProp
MessageProp --> Envelope
Envelope --> Filter
| API shape | Inbox | Outbox |
|---|---|---|
| Typed item | InboxAcceptItem<TMessage> with Message |
OutboxEnqueueItem<TEvent> with Message (renamed from Event) |
| Untyped batch item | InboxAcceptItem |
OutboxEnqueueItem with MessageType (renamed from EventType) |
| Typed accept/enqueue return | InboxReceipt<TMessage> |
OutboxReceipt<TEvent> (unchanged generic param name) |
| Batch return | InboxReceipt |
OutboxReceipt |
| Receipt outcome | InboxAcceptOutcome |
OutboxEnqueueOutcome |
| Store append return | InboxAppendResult |
OutboxAppendResult |
| Default metadata | InboxAcceptMetadata.Immediate |
Immediate enqueue via metadata variants |
| Item order in source | Typed first, untyped second | Typed first, untyped second (aligned in naming pass) |
CQRS method names stay asymmetric (AcceptAsync vs EnqueueAsync; TMessage vs TEvent on generic items). Only the shared item shape (Message, MessageType, filters, envelopes) is symmetric.
Shared durable primitives live in LiteBus.Messaging.Abstractions.DurableMessaging:
| Type | Role |
|---|---|
MessageIdentity |
Stable message id and correlation handles |
Idempotency |
Idempotency key and deduplication scope |
MessageVisibility |
Delayed or scheduled availability (visible_after) |
MessageTrace |
Distributed trace context propagation |
TenantScope |
Tenant partition for routing and lease filters |
MessageContractReference |
Contract name and version for persisted envelopes |
Outbox adds PublicationTarget in LiteBus.Outbox.Abstractions for destination routing.
*Item types compose these value objects through *Metadata. Internal mappers (for example DurableEnvelopeMetadataMapper in LiteBus.Messaging) project metadata fields onto envelope columns. Receipt types (InboxReceipt, OutboxReceipt) expose MessageContractReference, MessageTrace, and TenantScope rather than duplicating primitive fields.
Writer facades use a small, predictable surface. The canonical input is always *Item; optional body-only sugar overloads delegate to it.
Task<InboxReceipt<TMessage>> AcceptAsync<TMessage>(InboxAcceptItem<TMessage> item, CancellationToken cancellationToken)
where TMessage : notnull;
Task<InboxReceipt<TMessage>> AcceptAsync<TMessage>(TMessage message, CancellationToken cancellationToken)
where TMessage : notnull;
Task<IReadOnlyList<InboxReceipt>> AcceptBatchAsync(
IReadOnlyList<InboxAcceptItem> items,
CancellationToken cancellationToken);Outbox mirrors the pattern with EnqueueAsync / EnqueueBatchAsync and OutboxEnqueueItem. Typed single-message paths return axis-specific receipts; the one canonical batch path accepts non-generic items and returns non-generic receipts. There is no separate homogeneous EnqueueBatchAsync<TEvent> overload.
Append stores return InboxAppendResult or OutboxAppendResult, not an envelope alone. The result preserves whether the insert succeeded or resolved an existing message ID or tenant-scoped idempotency key. Writers map that store fact to InboxReceipt.Outcome or OutboxReceipt.Outcome; they never infer it by comparing envelope values.
- The message body is the generic argument on
*Item<TMessage>when typed; untyped batch paths use non-genericInboxAcceptItem. CancellationTokenis always the last parameter.- At most one sugar overload per writer operation family: message body only, no metadata scalars.
- Batch methods accept
IReadOnlyList<*Item>. Callers may use collection expressions ([item1, item2]); do not add no-opFrom(params *Item[])helpers. - Store, lease, and processor APIs use
*Requestfor operation inputs that are not full message accepts. - Custom append stores must return an axis-specific append result for every input slot. Batch results retain input order and mark later duplicates as
AlreadyAcceptedorAlreadyEnqueued.
Build *Item values at the call site in this order of preference:
- Body-only default: sugar overload or
*Item<T>.From(message)when metadata is*Metadata.Immediate. - Named static on
*Item<T>:OutboxEnqueueItem<T>.ScheduledAt(message, when)when a metadata combination is common and domain-named. withon item and metadata: full control without new API surface.- Non-generic
*Item: heterogeneous batch entries viaOutboxEnqueueItem.From(message, messageType)(static on the untyped record). - Thin namespace helper:
OutboxEnqueue/InboxAcceptonly for glue that does not fit one record (avoid plural*Itemsnames).
Do not use implicit conversions from the message body to *Item. Do not add writer overloads that take topic, idempotency key, visibility, or other metadata as separate parameters.
| Kind | Package |
|---|---|
| Shared durable value objects | LiteBus.Messaging.Abstractions (DurableMessaging namespace) |
| Axis-specific items, metadata, receipts | LiteBus.Inbox.Abstractions, LiteBus.Outbox.Abstractions |
| Mediation settings | LiteBus.Commands.Abstractions, LiteBus.Events.Abstractions, LiteBus.Queries.Abstractions |
| Module and host options | Feature or adapter package that registers the service |
| Internal envelope projection | Core feature package (LiteBus.Messaging, LiteBus.Inbox, LiteBus.Outbox) as internal helpers |
Abstractions packages own the public value-object types. Adapters map them; they do not define alternate option bags for the same concern.
// Default metadata
await inbox.AcceptAsync(command, cancellationToken);
// Custom metadata
await inbox.AcceptAsync(
InboxAcceptItem<PlaceOrderCommand>.From(
command,
InboxAcceptMetadata.Immediate with
{
Idempotency = Idempotency.Keyed("order-123"),
Visibility = MessageVisibility.After(TimeSpan.FromMinutes(5)),
Trace = MessageTrace.Correlated(correlationId),
Tenant = TenantScope.Isolated(tenantId),
}),
cancellationToken);// Default metadata
await outbox.EnqueueAsync(integrationEvent, cancellationToken);
// Topic and idempotency
await outbox.EnqueueAsync(
OutboxEnqueueItem<OrderSubmittedIntegrationEvent>.WithTopic(integrationEvent, "orders.events") with
{
Metadata = OutboxEnqueueMetadata.Immediate with
{
Idempotency = Idempotency.Keyed(eventId),
}
},
cancellationToken);Feature guides (Inbox, Outbox, Transactional messaging writes) show end-to-end usage. Historical names and package mappings remain in the Migration Guide.
- Put
Fromand domain-named factories on*Itemrecords. Writer batch signatures stay onIReadOnlyList<*Item>; use collection expressions for batches. - One body-only sugar overload per writer operation family is allowed; see Writer construction.
- Keep module-builder configuration on
*Optionstypes registered at compose time. Do not thread those options through accept or enqueue calls. - When a feature needs both mediation and durable persistence, call
ICommandMediator.SendAsyncwith*Settingsseparately fromITransactionalOutbox.EnqueueAsyncwithOutboxEnqueueItem; do not merge the two concerns into one type.
Error handlers use HandleErrorAsync(MessageErrorContext<TMessage, TResult>, CancellationToken). The typed context shares Outcome and HandledResult with the mediation pipeline, so observing a failure leaves it unhandled while recovery is an explicit state transition. Cancellation is always the token supplied to the mediation call; error handlers do not depend on ambient token lookup.
Related durable surfaces include ILeaseRenewable.RenewLeaseAsync(LeaseRenewalRequest, ...) on inbox and outbox lease stores; ISagaStore.SaveAsync(SagaSaveItem<TState>, ...), SagaCompleteItem, SagaSaveItem.From, SagaCorrelation.SagaDefinitionId, and tenant-scoped saga primary keys. See Saga.
Track broader roadmap items in Roadmap.
- Dependency role rules (see Dependency graph)
- Module registration and hosted-service manifest (see Hosted services)
- Mediation pipeline behavior (see The handler pipeline)
- Architecture: storage accept/process paths and dependency roles
- Inbox / Outbox: feature guides for durable messaging
- Migration Guide: historical API and database transitions