Skip to content

feat(rabbitmq): public API for multi-node cluster endpoints (#2659)#2664

Merged
jeremydmiller merged 1 commit intoJasperFx:mainfrom
BlackChepo:feature/2659_RMQ_cluster
May 4, 2026
Merged

feat(rabbitmq): public API for multi-node cluster endpoints (#2659)#2664
jeremydmiller merged 1 commit intoJasperFx:mainfrom
BlackChepo:feature/2659_RMQ_cluster

Conversation

@BlackChepo
Copy link
Copy Markdown
Contributor

Summary

Adds public fluent API for RabbitMQ multi-node cluster failover. Closes #2659.

Wolverine's RabbitMQ transport already had the internal plumbing — RabbitMqTransport.AmqpTcpEndpoints is forwarded to
ConnectionFactory.CreateConnectionAsync(IList<AmqpTcpEndpoint>) (the official RabbitMQ.NET client failover API) — but no public way to populate the list. This PR exposes that capability through RabbitMqTransportExpression, propagates the configuration to virtual-host tenants, and surfaces the cluster nodes in connection diagnostics.

Public API

Two repeatable overloads on RabbitMqTransportExpression:

opts.UseRabbitMq(f =>
    {
        f.UserName = "guest";
        f.Password = "guest";
        f.Ssl.Enabled = true;
        f.Ssl.ServerName = "rabbit-cluster";
    })
    .AddClusterNode("rabbit-1.local")
    .AddClusterNode("rabbit-2.local")
    .AddClusterNode("rabbit-3.local");
  • AddClusterNode(string hostName, int port = -1) — convenience overload. Copies ConnectionFactory.Ssl onto the new endpoint as a fresh SslOption so TLS configured on the factory applies to all cluster nodes by default. port = -1 defers to the AmqpTcpEndpoint constructor (5672 normal, 5671 TLS).
  • AddClusterNode(AmqpTcpEndpoint endpoint) — power-user overload. Stores the supplied endpoint as-is. Use this for per-node TLS, distinct certificates, or non-default ports.

Both overloads require UseRabbitMq(...) or UseRabbitMqUsingNamedConnection(...) to have run first; a clear InvalidOperationException names both entry points.

Multi-tenancy

Virtual-host tenants (AddTenant(tenantId, virtualHostName)) inherit the parent transport's cluster nodes automatically — they target the same broker, so they should share
the cluster topology.

URI- and Action-based tenants (AddTenant(tenantId, Uri), AddTenant(tenantId, Action<ConnectionFactory>)) do not inherit the parent cluster — those overloads are for tenants on separate brokers and bring their own connection settings. This is a documented limitation; a future overload could accept cluster nodes for those paths if requested.

RabbitMqTenant.Compile() is guarded against duplicate appends on repeated calls.

Diagnostics

RabbitMqConnectionDescription gains indexed ClusterNodes[i] entries (formatted host:port) so the cluster configuration is visible in logs, resource reports, and OptionsDescription consumers. ResourceUri and RabbitMqHealthCheck semantics are unchanged — by design, since cluster failover means "any reachable node = healthy", not "all nodes individually reachable".

Out of scope

  • Connection-string syntax for cluster nodes (ConnectionStringParser). Deferred until concrete demand and a clear format convention emerge. The fluent API is the only entry point for now.
  • Per-tenant cluster configuration for URI/Action tenants. Documented limitation.

Test plan

  • Unit tests for both overloads — argument validation, SSL inheritance (fresh instance, not shared reference), port = -1 default resolution, ordered append, AmqpTcpEndpoint overload preserves reference equality.
  • Tenant inheritance — virtual-host tenants inherit, URI tenants do not (regression guard for the documented limitation), Compile() is idempotent.
  • Diagnostics — ClusterNodes[i] entries render only when configured; empty list produces no entries.
  • Integration smoke test — declares the local broker as a "cluster of one", publishes and receives a message via host.TrackActivity().PublishMessageAndWaitAsync(...), verifies traffic flows through CreateConnectionAsync(IList<AmqpTcpEndpoint>).
  • Full RabbitMQ test suite (74 tests) — no regressions.
  • Docs build — new "Connecting to a RabbitMQ cluster" section with TLS-inheritance and per-node override examples.

…#2659)

Expose AddClusterNode(host, port) and AddClusterNode(AmqpTcpEndpoint)
on RabbitMqTransportExpression so applications can configure RabbitMQ
cluster failover via the fluent API. The underlying connection plumbing
(RabbitMqTransport.AmqpTcpEndpoints + ConnectionFactory.CreateConnectionAsync(endpoints))
already existed but had no public entry point.

- (host, port) overload copies ConnectionFactory.Ssl onto the new
  endpoint as a fresh SslOption so TLS configured on the factory
  applies to cluster nodes by default.
- (AmqpTcpEndpoint) overload stores the supplied endpoint as-is for
  full per-node control.
- Both overloads require UseRabbitMq(...) or
  UseRabbitMqUsingNamedConnection(...) to have run first; the error
  message names both entry points.
- Virtual-host tenants inherit the parent transport's cluster nodes
  via RabbitMqTenant.Compile(), guarded against duplicate appends on
  repeated Compile() calls. URI- and Action-based tenants do not
  inherit (documented limitation).
- RabbitMqConnectionDescription gains indexed ClusterNodes[i] entries
  so the cluster configuration is visible in diagnostics. ResourceUri
  and health-check semantics are unchanged.
- Docs: new "Connecting to a RabbitMQ cluster" section in the RabbitMQ
  guide with TLS-inheritance and per-node override examples plus the
  documented tenant-inheritance rule and rationale.
@jeremydmiller jeremydmiller merged commit c46e868 into JasperFx:main May 4, 2026
17 of 21 checks passed
jeremydmiller added a commit that referenced this pull request May 4, 2026
Minor release. Highlights:

- WolverineFx.Marten: durable local messages routed by the receiving handler's
  ancillary store (Uri-based gate, no IMessageStore.Id dependency). Closes #2669.
  PR #2674.
- WolverineFx.RabbitMQ: public AddClusterNode(...) API for multi-node failover.
  Closes #2659. PR #2664.
- WolverineFx.Polecat: fixed NRE in OutboxedSessionFactory when constructing the
  FlushOutgoingMessagesOnCommit listener. Closes #2668. PR #2672.
- WolverineFx core: new DocumentStores collection on ServiceCapabilities for
  CritterWatch document-side rendering.
- Dependency bumps: JasperFx 1.28.2 → 1.29.0, JasperFx.Events 1.31.1 → 1.33.1,
  Marten + Marten.AspNetCore 8.32.0 → 8.35.0.

Also backfilled a 5.36.2 entry in CHANGELOG covering the EF Core + outbox flush
rework from PR #2665 that landed without a CHANGELOG note at the time.

Co-Authored-By: Claude Opus 4.7 (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.

Add RabbitMQ multinode cluster support

2 participants