Skip to content

Implement message type binding for receive endpoints and enhance error handling - #9870

Merged
PascalSenn merged 3 commits into
mainfrom
pse/all-endpoint-to-receive
Jun 9, 2026
Merged

PascalSenn merged 3 commits into
mainfrom
pse/all-endpoint-to-receive

Conversation

@PascalSenn

Copy link
Copy Markdown
Member
  • Added Receives<TMessage>() and Receives(Type messageType) methods to endpoint descriptors for binding handlers by message type.
  • Updated MessagingTransport to utilize the new binding methods and throw exceptions when no handler is registered for a message type.
  • Introduced tests to validate the new binding behavior and error handling.
  • Updated documentation to reflect the new binding approach for message types.

…r handling

- Added `Receives<TMessage>()` and `Receives(Type messageType)` methods to endpoint descriptors for binding handlers by message type.
- Updated `MessagingTransport` to utilize the new binding methods and throw exceptions when no handler is registered for a message type.
- Introduced tests to validate the new binding behavior and error handling.
- Updated documentation to reflect the new binding approach for message types.
PascalSenn and others added 2 commits June 8, 2026 02:31
Address devil's-advocate review of Receives<T> binding:
- BindRouteToEndpoint now copies the source route's Condition onto the
  fan-out clone, so custom filters and saga typed-reply guards are no
  longer widened to a default condition.
- Add an idempotency guard so declaring the same message type or
  consumer across three or more endpoints does not over-clone routes.

Add regression tests: condition preservation on fan-out, no duplicate
routes across three endpoints, and behavioral delivery to both queues.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PascalSenn
PascalSenn marked this pull request as ready for review June 9, 2026 22:38
Copilot AI review requested due to automatic review settings June 9, 2026 22:38
@PascalSenn
PascalSenn merged commit c708be4 into main Jun 9, 2026
143 of 145 checks passed
@PascalSenn
PascalSenn deleted the pse/all-endpoint-to-receive branch June 9, 2026 22:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new way to explicitly bind receive endpoints by message type (in addition to binding by handler/consumer type), updates transport initialization to apply those bindings (and fail fast when a declared message type has no handlers), and documents/tests the behavior across transports.

Changes:

  • Added Receives<TMessage>() / Receives(Type) to receive endpoint descriptors and configuration (ReceivedMessageTypes).
  • Updated MessagingTransport initialization to bind inbound routes to endpoints based on declared received message types, and to throw when no matching handler/consumer exists.
  • Added/updated transport tests and docs/examples demonstrating message-type binding.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
website-next/content/docs/mocha/routing-and-endpoints.md Documents binding endpoints by message type via .Receives<T>().
src/Mocha/test/Mocha.Transport.RabbitMQ.Tests/Behaviors/ExplicitTopologyTests.cs Adds RabbitMQ explicit-topology test coverage for .Receives<T>().
src/Mocha/test/Mocha.Transport.Postgres.Tests/Behaviors/ExplicitTopologyTests.cs Adds Postgres explicit-topology test coverage for .Receives<T>().
src/Mocha/test/Mocha.Transport.InMemory.Tests/ReceivesTests.cs New InMemory-focused tests for binding/fan-out/idempotency/error behavior of .Receives<T>().
src/Mocha/src/Mocha/Transport/MessagingTransport.Lifecyle.cs Implements binding routes to endpoints by declared message types; introduces shared binding helper.
src/Mocha/src/Mocha/ThrowHelper.cs Adds a dedicated exception helper for “no handler for message type declared on endpoint”.
src/Mocha/src/Mocha/Endpoints/Descriptors/ReceiveEndpointDescriptor.cs Implements .Receives<TMessage>() / .Receives(Type) on the base descriptor.
src/Mocha/src/Mocha/Endpoints/Descriptors/IReceiveEndpointDescriptor.cs Extends the public descriptor interface with the new .Receives APIs.
src/Mocha/src/Mocha/Endpoints/Configurations/ReceiveEndpointConfiguration.cs Adds ReceivedMessageTypes to persist declared message-type bindings.
src/Mocha/src/Mocha.Transport.RabbitMQ/Descriptors/RabbitMQReceiveEndpointDescriptor.cs Adds fluent .Receives methods returning the RabbitMQ-specific descriptor type.
src/Mocha/src/Mocha.Transport.RabbitMQ/Descriptors/IRabbitMQReceiveEndpointDescriptor.cs Extends RabbitMQ receive endpoint interface with .Receives methods.
src/Mocha/src/Mocha.Transport.Postgres/Descriptors/PostgresReceiveEndpointDescriptor.cs Adds fluent .Receives methods returning the Postgres-specific descriptor type.
src/Mocha/src/Mocha.Transport.Postgres/Descriptors/IPostgresReceiveEndpointDescriptor.cs Extends Postgres receive endpoint interface with .Receives methods.
src/Mocha/src/Mocha.Transport.InMemory/Descriptors/InMemoryReceiveEndpointDescriptor.cs Adds fluent .Receives methods returning the InMemory-specific descriptor type.
src/Mocha/src/Mocha.Transport.InMemory/Descriptors/IInMemoryReceiveEndpointDescriptor.cs Extends InMemory receive endpoint interface with .Receives methods.
src/Mocha/examples/PostgresTransport/PostgresTransport.OrderService/Program.cs Updates example to demonstrate using .Receives<T>() for explicit topology.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 47 to 52
foreach (var handlerType in endpointConfiguration.ConsumerIdentities)
{
var consumer = context.Consumers.FirstOrDefault(h => h.Identity == handlerType);
var consumer = context.Consumers.FirstOrDefault(h => h.Identity == handlerType)
?? throw new InvalidOperationException(
$"Handler type {handlerType.FullName} not found for endpoint {Configuration.Name}");

Comment on lines +141 to +154
// The route is bound to another endpoint, so fan it out by adding an equivalent route to
// this endpoint. Skip when an equivalent route is already present so binding the same
// message type or consumer across three or more endpoints stays idempotent.
// Conditions are not part of the equivalence check, so two routes for the same consumer
// and message type that differ only by condition will not both be fanned out.
foreach (var existing in context.Router.GetInboundByEndpoint(endpoint))
{
if (existing.Consumer == route.Consumer
&& existing.Kind == route.Kind
&& existing.MessageType == route.MessageType)
{
return;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants