v7.0.0-preview.2
Pre-releaseThis is a pre-release of 7.0.0. Packages carry the
7.0.0-preview.2version suffix, so
dotnet add packageresolves them only when a pre-release is requested explicitly or--prerelease
is passed. The notes below describe the change from7.0.0-preview.1. For everything landing in
7.0.0 against 6.0.2, see the v7.0.0 changelog section.
This preview answers ten items raised by a consumer application planning its upgrade from 6.0.1. Two of them blocked adoption of a documented feature outright; the rest were friction. 22 new public types, 123 files changed.
The theme: the declaration model stops being an auditing feature and becomes the general mechanism it was already shaped like, and the pipeline gains a commit position the framework's own writers can be ordered against.
Added
A commit position in the pipeline. HandlerPriorities.ReservedCeiling closes the reserved window and HandlerPriorities.UnitOfWork names the position above it where an application commits its unit of work. A completion handler there runs after every LiteBus handler, in this and every future release, which is what makes an audit record staged by the writer at Observability part of the transaction that applies the change it describes. See Making a Record Atomic With Its Change.
IExecutionContext.Data, an IHandleContextData keyed by the CLR type of the value rather than by a string. A guard that reads an aggregate to decide whether the caller may act hands that instance to the main handler instead of forcing a second load, which was the cost keeping authorization inside handlers rather than in the stage that owns the decision. Get<T> throws HandleContextDataNotFoundException naming the type, TryGet<T> covers the optional case, and access is lock-guarded because parallel event handlers share one execution context.
IMessageMetadataAccessor, which reads a message type's declarations through ForMessage and TryGet. Previously this meant injecting IMessageRegistry, calling Find, and reaching through IMessageDescriptor.Metadata, which made the registry's descriptor shape part of any application reading a declaration it wrote itself. An unregistered type raises MessageMetadataNotFoundException rather than answering with an empty collection, because an empty answer turns a missing registration into a permission check that silently passes.
A general "this message must declare X".
MessageModuleBuilder.RequireDeclaration<TValue>()fails composition for any registered message that neither declares the value nor records an exemption, naming every offender grouped by the declaration each omits.LB1020reports the same omission at compile time, reading the required types fromlitebus_required_declarationsin.editorconfig.LB1021reports a configured name that does not resolve, because a name that silently did nothing would disable the requirement it configures.[DeclarationExempt(typeof(TValue), rationale)]records a deliberate omission. Repeatable, aggregated into oneDeclarationExemptionsvalue, readable through the accessor like any other declaration.[MessageDeclaration(typeof(TValue))]states on an attribute class which value it declares, becauseIMessageDeclarationSource.DeclarationTypeis a runtime property no analyzer can execute.IModuleConfiguration.RegisterCompositionValidationruns a rule after every module has built, which the requirement needs: the messaging module is foundational and has no commands to inspect during its own build.
In-process idempotency. IIdempotencyDefinition<TMessage> declares the key a repeat is recognised by, IIdempotencyStore remembers which keys were applied, and CommandModuleBuilder.EnableIdempotency() registers the shortcuts and the completion handler that claim the key before the handler runs and settle it after: applied on success, released on anything else, so a transient failure does not turn the retry into a false repeat. IdempotencyDeclaration.ReplayResult records the first answer so a repeated result-producing command can be answered with it. InMemoryIdempotencyStore ships from LiteBus.Testing.Mediation on purpose, because a store that forgets on restart and knows only its own process cannot make a claim about the system. New litebus.idempotency.store probe. See In-Process Idempotency.
Arity-2 open generic handlers. A generic post-handler, completion handler, or error handler may take two type parameters, binding the message type and the result type the message declares through the new IProducesResult<TMessageResult> marker on ICommand<T>, IQuery<T> and IStreamQuery<T>. Generic cross-cutting code reaches the typed contract instead of falling back to an object? it can do nothing with.
Validator migration adapters. ThrowingValidator<TMessage, TException>, with ThrowingCommandValidator and ThrowingQueryValidator, adapt a validator whose body still reports failure by throwing. Adapted and converted validators mix in one mediation because the stage collects across both, so a codebase converts module by module instead of in one commit touching every validator.
An explicit audit trail lifetime. UseAuditTrail<TAuditTrail>(InstanceLifetime), still Scoped by default. The litebus.audit.trail probe now also reports trailIsSingleton, resolved by comparing the trail across two dispatch scopes.
Changed
- The completion stage orders by priority alone. Every other role runs handlers registered for the message type before handlers registered for a base type. Completion handlers observe an ending rather than wrapping the handler, so there is no onion for a specific handler to sit inside, and the split put the framework's broadly registered audit writer beyond the reach of an application's priority.
MessageModuleBuilder.UseAuditTrail(IAuditTrail)is nowUseAuditTrailInstance, so the name carries the lifetime a pre-created instance necessarily has.- An untyped open generic shortcut is skipped for a message that declares a result rather than failing registration. A closed registration of that pair is still a configuration error, because there the author named the message; an open generic says "every message I fit" and a result-producing message is not one of them.
LB1005accepts two type parameters when the handler implements a contract taking both in order. A second parameter the handler invented, aTContextor aTStore, is still rejected, because the registry closes by position and has nothing to put there.LB1018is now the preconfigured instance ofLB1020overAuditDeclaration, sharing one analysis rather than duplicating it.- Declaration values may be delegates over the message, which is now documented. That covers every scope derivable from the message rather than constants only.
Fixed
- The auditing guide and
HandlerPrioritiescontradicted each other. The guide told an application needing an atomic audit record to stage the record and let its commit flush it, which requires that commit to run after the audit writer atReservedFloor + 200.HandlerPrioritiestold the same application to stay belowReservedFloorand warned that anything above it may be reordered between releases. The documented way to get an atomic record required an application handler inside the band reserved for the framework. - The
litebus.audit.trailprobe failed on the default configuration. It resolvedIAuditTrailfrom the provider it was handed, and resolving a scoped service from a root provider throws in a container validating scopes. Since the trail is scoped by default, the probe failed on exactly the setup it exists to approve. It now resolves through a dispatch scope. IHandleContextDatashipped unwired. It was public, implemented, and referenced by nothing, with the reference guide describing it as "not wired into the pipeline". A consumer read it as the answer to passing state between stages and found nothing called it.LB1018reported messages covered by a definition on a base type. Coverage was an exact type match, so a definition written for a base command or a marker interface did not satisfy the rule for the messages beneath it. Definition coverage now walks base types and interfaces, matching how the registry resolves declarations at runtime.
Breaking Changes Since preview.1
Only relevant if you already consume 7.0.0-preview.1.
| Change | What to do |
|---|---|
IMessageDependencies.IndirectCompletionHandlers removed |
Completion handlers are one priority-ordered collection on CompletionHandlers. Custom implementations and test doubles drop the property. IMessageDescriptor still separates the two sets. |
IExecutionContext.Data added |
A custom implementation or test double returns new HandleContextData(). |
UseAuditTrail(IAuditTrail) renamed to UseAuditTrailInstance |
Rename the call. The generic overload takes an optional InstanceLifetime, still Scoped by default. |
IModuleConfiguration gained CompositionValidations and RegisterCompositionValidation |
A custom host adapter runs the collected validations after its module loop, or a rule spanning several modules never executes. Both shipped adapters already do. |
Two behavior changes with no compile break, worth checking if you rely on either:
- Completion handler order. A handler registered for a concrete message no longer automatically runs before one registered for a base type. Priority decides, with registration order breaking ties. If you depended on the old rule, give the handler an explicit priority.
LB1018false positives disappear. A message covered by a definition on its base type or a marker interface is no longer reported. If you suppressed the rule for such a message, the suppression is now redundant.
Verification
Release gates for this tag: Release build, 1123 unit tests, documentation validation across 245 Markdown files, documentation semantics against 19 analyzer IDs and 1718 discovered tests, 472 rendered documentation pages link-checked, 83 packages packed and validated, and the Docker-backed PostgreSQL, AMQP, EF Core and transport integration suites with skipped tests rejected. The merged line coverage gate is 90 percent; the source this preview changed is covered at 95.4 percent.