Skip to content

Feature/messaging hardening#213

Open
Vulthil wants to merge 46 commits into
mainfrom
feature/messaging-hardening
Open

Feature/messaging hardening#213
Vulthil wants to merge 46 commits into
mainfrom
feature/messaging-hardening

Conversation

@Vulthil

@Vulthil Vulthil commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Description

Related Issue

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that changes existing behavior)
  • 📝 Documentation only
  • 🧹 Refactor / chore (no functional change)

Checklist

  • My commits follow the Conventional Commits format.
  • The solution builds without warnings (dotnet build -c Release).
  • All tests pass locally (dotnet test -c Release).
  • I added or updated tests covering my change.
  • I updated XML documentation for any changed public members.
  • I updated the relevant docs (docs/, README) and PublicAPI.*.txt files where applicable.

Additional Notes

Vulthil and others added 30 commits May 25, 2026 12:33
…on ISender

Move the CQRS pipeline from ISender-only dispatch to direct handler decoration.
Pipeline behaviors are now composed lazily at handler-resolution time via a
PipelineHandlerDecorator wrapping every registered ICommandHandler/IQueryHandler/
IDomainEventHandler, so callers can inject the typed handler interface (e.g.
ICommandHandler<TCmd, TResult>) and still receive the full pipeline (validation,
logging, transactional behavior, etc.) without going through ISender.

- Add HandlerRegistrar, HandlerInterfaceAdapters, IInnerHandler and
  PipelineHandlerDecorator under Vulthil.SharedKernel.Application/Pipeline.
- Replace Scrutor-based assembly scanning with explicit registration;
  drop the Scrutor package reference.
- Expose AddOpenPipelineHandler / AddOpenDomainEventPipelineHandler for
  open-generic behavior registration; behaviors apply regardless of when
  they are registered relative to AddHandlers.
- Update the WebApi sample to inject typed handlers in its endpoints,
  group routes under /api, and rename the route prefix to /main-entities.
- Add HttpResponseMessageExtensions.GetResponseAsync<T> in
  Vulthil.Extensions.Testing for the refreshed integration tests.
- Document the new pipeline shape in docs/articles/cqrs-pipeline.md and
  the Vulthil.SharedKernel.Application package doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bring the messaging libraries up to a production-ready baseline with structured
logging, observability, health checking, and configuration-driven topology
declaration. Consumers gain an in-context PublishAsync that auto-propagates
correlation metadata.

Public surface (Abstractions):
- IMessageContext.PublishAsync<T>(message, configure?) auto-propagates
  CorrelationId/ConversationId/InitiatorId from the incoming context.
- IMessageContext.CancellationToken exposes the delivery's cancellation.

Public surface (Vulthil.Messaging):
- MessageConfiguration.Exchange is now non-nullable; the new required
  constructor takes the exchange name. MessageConfiguration<T> defaults
  it to typeof(T).FullName.
- AddMessaging eagerly loads Messaging:Queues:* and Messaging:Messages:*
  from IConfiguration before the configurator action runs; code calls
  merge onto the loaded values and win on conflict. Services can now
  declare topology entirely in appsettings.
- IMessageConfigurationProvider.QueueDefinitions exposes the assembled
  queue set; transports pull queues from there rather than from individual
  DI singletons.
- Removed half-baked IFaultConfigurator / ConfigureFaults /
  MessagingOptions.AutoDeclareFaultStatus (still unshipped; will be
  redesigned in a follow-up).

Public surface (Vulthil.Messaging.RabbitMq):
- MessagingInstrumentation.ActivitySourceName plus the TracerProviderBuilder
  extension AddVulthilMessagingInstrumentation(). UseRabbitMq registers
  it automatically; gated on RabbitMQClientSettings.DisableTracing so the
  Aspire flag toggles the full pipeline.
- vulthil_messaging_rabbitmq_bus health check that flips to Healthy after
  RabbitMqBus.StartAsync completes; gated on DisableHealthChecks.

Internal changes:
- Producer-only services no longer initialize the reply queue eagerly;
  ResponseListener is lazy-initialized on the first IRequester call.
- RabbitMqBus declares per-message exchanges using MessageConfiguration
  via IMessageConfigurationProvider, so the bus and publisher use the
  same source of truth and never disagree on topology.
- Worker/publisher/requester gained LoggerMessage-based structured logs;
  silent catches on fault publish, poison messages, and missing execution
  plans now surface as warnings/errors.
- Fault snapshots embed JsonElement (faithful payload roundtrip) instead
  of a deserialized object.

Docs:
- Refreshed docs/articles/messaging.md with sections on in-context
  publish, message configuration, configuration-driven setup,
  observability, and health checks.
- Pointer additions in the per-package docs for Abstractions,
  Vulthil.Messaging, and Vulthil.Messaging.RabbitMq.

Chore:
- Moved documentation-guidelines.instructions.md under .github/instructions/
  where the scoped-instructions loader in CLAUDE.md expects it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ross-cutting concerns

Introduce a middleware-style consume pipeline so cross-cutting concerns
(logging, validation, scoped resource management, telemetry, etc.) can be
composed around the consumer invocation without modifying transport or
consumer code.

Public surface (Abstractions):
- ConsumeDelegate<TMessage>: delegate signature for the next pipeline stage.
- IConsumeFilter<TMessage>: middleware-style filter that wraps consumer
  invocation. Filters may short-circuit by not invoking next.

Public surface (Vulthil.Messaging):
- IMessagingConfigurator.AddConsumeFilter<TFilter>(): closed-generic
  registration; auto-discovers all IConsumeFilter<TMessage> interfaces the
  implementation declares.
- IMessagingConfigurator.AddOpenConsumeFilter(Type openFilterType):
  open-generic registration for filters that apply to every message type
  (mirrors the existing AddOpenPipelineHandler pattern in
  Vulthil.SharedKernel.Application).

Internal changes (Vulthil.Messaging.RabbitMq):
- ConsumePipelineFactory.Build<TMessage>(): composes the registered filters
  around a terminal delegate. First-registered = outermost. Returns the
  terminal unchanged when no filters are registered (no allocation on the
  cold path).
- ConsumerInvoker<TConsumer, TMessage>: terminal is the consumer call.
- RpcInvoker<TConsumer, TRequest, TResponse>: terminal captures the response
  via closure; outer try/catch guarantees a response is sent even when a
  filter throws or short-circuits (MessageResult.Failure with explanatory
  text). IMessageConfigurationProvider is now resolved at invoke-time so
  its JsonSerializerOptions can come from the scoped provider.

Tests (5 new in ConsumeFilterPipelineTests):
- No filters routes straight to the consumer.
- Multiple filters compose outer-first / inner-last around the consumer.
- Short-circuiting a filter prevents consumer invocation.
- RPC pipeline wraps the consumer call and still publishes the response.
- RPC short-circuit produces a failure response instead of a timeout.

Test-infra fixes:
- MessageTypeCacheTests nested fixture types (TestMessage, TestRequest,
  TestResponse) promoted from private to internal so Castle.DynamicProxy
  can proxy generic instantiations of IConsumeFilter<T> against the
  AutoMocker service provider.
- MessageTypeCacheTests registers an empty IEnumerable<IConsumeFilter<T>>
  for each tested message type so AutoMocker does not auto-mock the
  pipeline into a silent no-op.

Docs:
- New "Consume Filters" section in docs/articles/messaging.md covering the
  interface shape, registration (open + closed), filter ordering,
  short-circuit semantics, and RPC behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ship a default open-generic LoggingConsumeFilter<TMessage> registered as the
outermost layer of every consume pipeline. It emits structured Debug logs at
consume entry/exit and a Warning log on uncaught exceptions, with timing
information from Stopwatch.GetTimestamp.

New public surface:
- ConsumeFilterOptions class exposing per-default-filter toggles, currently
  EnableLogging (default true).
- MessagingOptions.ConsumeFilters property returning the live options object;
  bind via Messaging:Options:ConsumeFilters in appsettings or mutate via
  ConfigureMessagingOptions.

Registration:
- AddMessaging registers LoggingConsumeFilter<> via TryAddEnumerable BEFORE
  running the configurator action so user-registered filters compose INSIDE
  the defaults (logging wraps everything).
- The filter checks EnableLogging at construction time; when disabled, it
  passes through to next without logging or timing — the filter stays in DI
  for test resolution, only its behavior is suppressed.

Tests:
- DisabledFilterPassesThroughWithoutLogging
- EnabledFilterLogsConsumingAndConsumedOnSuccess
- EnabledFilterLogsWarningAndRethrowsOnException

Tests use a RecordingLogger<T> fake rather than Moq because LoggerMessage's
generated code short-circuits on ILogger.IsEnabled, which a default Moq
returns false from.

Docs:
- New "Built-in filters" subsection in docs/articles/messaging.md showing
  the logging output, the option toggle in appsettings, and the code-side
  ConfigureMessagingOptions path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vulthil and others added 16 commits June 4, 2026 22:02
…to 13.4.2

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… getters

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…actions

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t to avoid CI dev-cert TLS failures

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant