API Design Reference
This page is the concrete inventory for LiteBus public API shapes: named types, package placement, method signatures, and the v6 durable-messaging exemplar. The rules that govern every axis (suffix taxonomy, banned shapes, parameter budget, mapping boundary) live in AGENTS.md under API and value object design. Read that section first when adding or renaming types; use this page when you need examples and placement.
Who This Is For
- Contributors adding public types to
*.Abstractionspackages - Reviewers checking whether a new API matches an existing pattern
- Agents and maintainers aligning legacy surfaces during feature work
Prerequisites: Architecture (dependency role model), Dependency graph (package inventory).
Model Taxonomy (with Examples)
| 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 vs Durable Writers
- 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.IInboxScheduler,IOutboxScheduler,InboxOptions, andOutboxOptionsare removed in v6. - Framework bindings use
*Bindingfor ASP.NET management and similar adapter input. Bindings map once at the adapter edge; they are not writer*Itemtypes.
CLR Kind Selection
Public types use a consistent CLR kind per role. Full rename inventory: Migration guide v6.
| 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).
Inbox/Outbox Naming Model
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.
Discriminated Value Objects (Durable Messaging)
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 Method Shape
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. The redundant homogeneous EnqueueBatchAsync<TEvent> overload is not part of v6.
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.
Writer Construction
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.
Package Placement
| 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.
Durable Messaging Exemplar
Accept (Inbox)
// 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);Enqueue (Outbox)
// 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. Migration guide v6 documents the break from InboxOptions / OutboxOptions, scheduler interfaces, and the v6.0 rename tables (outbox Message property, package renames, and related tables).
Package Rename Reference (v6.0)
Legacy to v6.0 mapping for application upgrades. Shipping libraries use v6 names exclusively; see Migration guide v6 for the full inventory.
| Area | Legacy | Target |
|---|---|---|
| Durable outbox writer | IOutboxWriter.AddAsync | IOutbox.EnqueueAsync with OutboxEnqueueItem |
| In-process dispatch | UseInProcessDispatcher() | UseInProcessDispatch() |
| EF Core storage | UseEfCoreStorage(...), EfCore*StoreOptions | UseEntityFrameworkCoreStorage(...), EntityFrameworkCore*StoreOptions |
| AWS SQS packages | *.AwsSqsSqs NuGet IDs | *.AwsSqs (for example LiteBus.Transport.AwsSqs, LiteBus.Inbox.Dispatch.AwsSqs, LiteBus.Inbox.Ingress.AwsSqs) |
| ASP.NET management | *QueryParameters, *PurgeParameters | *QueryBinding, *PurgeBinding |
| Mediation pipeline bag | MessageMediationRequest with embedded CancellationToken | MessageMediationRequest<TMessage, TResult>; pass CancellationToken only on Mediate |
| Handler exceptions | MultipleCommandHandlerFoundException, MultipleMessageHandlerFoundException, MultipleQueryHandlerFoundException | MultipleHandlerFoundException |
| Outbox item body | OutboxEnqueueItem.Event, untyped EventType | Message, MessageType |
| Batch construction helpers | InboxAcceptItems, OutboxEnqueueItems | (removed): static factories on InboxAcceptItem / OutboxEnqueueItem |
| Saga module extensions | InboxModuleBuilderExtensions (saga packages) | InboxModuleBuilderSagaExtensions, InboxModuleBuilderPostgreSqlSagaExtensions |
| Event mediation settings | Nested bags on EventMediationSettings | EventRoutingSettings, EventExecutionSettings, EventHandlerFilter |
| Testing doubles | Fake* in the former aggregate testing package | Test* in the matching concern package (for example TestCommandMediator in LiteBus.Testing.Mediation, TestInboxStore in LiteBus.Testing.DurableMessaging) |
Ergonomics
- 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 Handler Context
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.
Other aligned v6 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 and Changelog.
Track broader roadmap items in Roadmap.
What This Does Not Cover
- Dependency role rules (see Dependency graph)
- Module registration and hosted-service manifest (see Hosted services)
- Mediation pipeline behavior (see The handler pipeline)
Next
- Architecture: storage accept/process paths and dependency roles
- Inbox / Outbox: feature guides for durable messaging
- Migration guide v6: greenfield API break summary