LiteBus
CatalogDurable core

Processor Envelope Hooks

  • ID: durable-core.processor-hooks
  • Name: Processor Envelope Hooks
  • Maturity: GA; saga integration is Extension tier
  • Summary: Runs feature-owned logic around one leased inbox or outbox envelope without adding feature references to either durable axis.

What It Does

Inbox and outbox processors adapt their envelopes to IProcessorEnvelope and invoke every registered IProcessorEnvelopeHook in dependency-injection registration order. The contract supports asynchronous state loading, synchronous ambient-scope attachment, post-dispatch persistence, and cleanup when processing cannot finish.

Saga uses this seam to load and persist correlated state. Inbox and outbox packages reference LiteBus.DurableMessaging.Abstractions; they do not reference saga types.

Hook Lifecycle

MethodInvocation PointConstraint
BeforeDispatchAsyncAfter lease acquisition and before axis dispatchMay perform asynchronous I/O such as saga state loading
PrepareDispatchScopeImmediately before the dispatcher callSynchronous so AsyncLocal assignments occur on the dispatch flow
AfterDispatchAsyncAfter successful dispatch and before terminal persistenceRuns while the message lease remains active
AbandonDispatchScopeAfter cancellation, failed dispatch, or an interrupted after-hook sequenceReleases in-memory state; must not persist business changes

PrepareDispatchScope and AbandonDispatchScope have default no-op implementations. A hook that owns ambient or keyed state implements both methods as a pair.

Failure Semantics

Before-hook, prepare-hook, and dispatcher exceptions fail the current dispatch attempt. The processor invokes AbandonDispatchScope before it maps the failure to retry or dead letter.

An AfterDispatchAsync exception follows ProcessorHookFailurePolicy:

PolicyInbox or Outbox Result
DeadLetterPersist a dead-letter terminal state; do not execute the handler or publisher again
CompleteDespiteHookFailureLog the hook failure and persist the successful completed or published state

The runner invokes AbandonDispatchScope for every hook when an after-hook stops the sequence. This removes state owned by hooks that had not yet reached their normal cleanup path.

Inbox defaults to DeadLetter. In-process outbox dispatch also defaults to DeadLetter. Transport outbox dispatch defaults to CompleteDespiteHookFailure because the broker send has already succeeded and another send could duplicate the message.

Public Surface

SurfacePackageRole
IProcessorEnvelopeHookLiteBus.DurableMessaging.AbstractionsAxis-neutral lifecycle contract
IProcessorEnvelopeLiteBus.DurableMessaging.AbstractionsRead-only message ID, contract, trace, and tenant metadata
ProcessorHookFailurePolicyLiteBus.Messaging.AbstractionsPost-dispatch failure policy
InboxProcessorOptions.HookFailurePolicyLiteBus.Inbox.AbstractionsInbox policy selection
OutboxProcessorOptions.HookFailurePolicyLiteBus.Outbox.AbstractionsOutbox policy selection

Registration

Register hook implementations through IModuleConfiguration.DependencyRegistry. Processors resolve IEnumerable<IProcessorEnvelopeHook> and preserve registration order.

Saga registration remains on the inbox builder:

registry.AddInbox(inbox =>
{
    inbox.EnableSaga(saga =>
    {
        saga.MapState<OrderSagaState>("orders.saga.advance");
    });
});

EnableSaga registers SagaProcessorHook as a singleton hook. SagaExecutionContext keys active entries by IProcessorEnvelope.MessageId, while persisted state remains keyed by SagaCorrelation.

Packages

PackageRole
LiteBus.DurableMessaging.AbstractionsHook and envelope contracts
LiteBus.Inbox, LiteBus.OutboxHook runners, envelope adapters, and failure-policy integration
LiteBus.SagaSaga hook and execution context
LiteBus.Saga.InboxIntegrationInbox builder and command mediation attachment

Invariants

  • Hooks run once per leased envelope, not once per processor pass.
  • Hooks receive an adapted read-only envelope rather than a storage entity.
  • Post-dispatch hooks run before terminal persistence while the lease is active.
  • A failed dispatcher never causes saga state to persist for that attempt.
  • Hook cleanup does not bypass store lease checks or alter terminal state.
  • Inbox and outbox packages remain independent of saga packages.

Non-Goals

  • A transaction spanning external dispatch, hook persistence, and terminal store persistence.
  • Hook execution on direct mediator calls outside durable processors.
  • Automatic ordering beyond dependency-injection registration order.
  • A workflow engine built from arbitrary processor hooks.

Observability

Hooks use processor logs, counters, and activities. There is no hook-specific meter.

SignalMeaning
Inbox failedFailure before successful dispatch, including before or prepare hooks
Inbox dead_letteredAfter-hook failure under DeadLetter, or exhausted dispatch retry
Outbox failedPublish failure before a terminal state
Outbox published terminal state with hook error logCompleteDespiteHookFailure preserved a successful broker send

Test Coverage

TestBehavior
InboxProcessorCorrectnessTestsBefore and after failures, dead-letter policy, complete-despite policy, no handler redispatch
OutboxProcessorCorrectnessTestsPublished-state policy, dead-letter policy, no second publish
SagaProcessorHookTests.ProcessPendingAsync_PersistsSagaStateAfterSuccessfulDispatchSynchronous scope preparation reaches the dispatcher and state persists afterward
SagaProcessorHookTests.ParallelDispatches_WithSameCorrelation_IsolateScopeByMessageIdConcurrent messages for one correlation retain separate active scopes
EF Core and PostgreSQL after-dispatch integration testsHook failure transitions use real storage concurrency checks

Deep Docs

On this page