Skip to content

Message grouping rules are one global list, so a topology cannot say how its own messages are grouped #4616

Closed

Description

@erdtsieck

What happens

options.MessagePartitioning is one rule list for the whole application, and DetermineGroupId takes the first rule that matches. The fluent chain reads as if the rules belong to the topology at the end of it:

// InvoicingExtension: invoices of one tenant share a number series
opts.MessagePartitioning
    .ByTenantId()
    .PublishToPartitionedLocalMessaging("invoices", 8, t => t.Message<CreateInvoice>());

// OrdersExtension, registered after it
opts.MessagePartitioning
    .ByMessage<IOrderCommand>(x => x.OrderId)
    .PublishToPartitionedLocalMessaging("orders", 8, t => t.MessagesImplementing<IOrderCommand>());

// Every order command is grouped by tenant, not by order

They don't. Every ByTenantId() / ByMessage<T>() appends to the same list, whichever topology it is chained onto, and every IWolverineExtension that configures its own topology appends in DI-registration order. TenantGroupingRule matches every envelope with a tenant id, so in a multi-tenant app it is a catch-all: nothing declared after it is ever reached.

We found this in our own app (11 partitioned topologies over three modules): 26 ByMessage rules and both UseInferredMessageGrouping() calls never ran once, verified by resolving group ids through IMessageBus.PreviewSubscriptions on a running host. Every message, including ones on no partitioned topology at all, got the tenant as its group id. The only rule that worked was the one whose extension happened to register first. If the modules had registered in a different order, it would have been shadowed too, and nothing would have told us.

#2580 is the same trap from the other side: .UseInferredMessageGrouping().ByTenantId() read as "group by tenant", but the inferred rule matched first.

Why upstream

The API shape invites it: the rules sit in the topology's chain but aren't scoped to it, and a topology has no way to say how its messages are grouped. On our side the only defence is to hand-write an IGroupingRule that repeats the topology's message types.

Proposal

Let a partitioned topology declare its own grouping rules (GroupByTenantId(), GroupBy<T>(...), GroupBy(IGroupingRule)). They apply only to messages published through that topology, and they are authoritative for them, so the application-wide list is never consulted for those messages. The existing global API is unchanged. PR incoming.

Activity

  1. added a commit that references this issue on Sep 25, 2026
  2. added 2 commits that reference this issue on Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions