RabbitMQ Transport
RabbitMQ is BareWire's reference transport (BareWire.Transport.RabbitMQ) and the broker most
of this site is written against. It maps BareWire's core concepts directly onto AMQP 0-9-1:
exchanges, queues, and bindings declared as manual topology, plus consistent-hash and
single-active-consumer for per-key ordering, native dead-letter exchanges, and publish-style
request/response.
Because RabbitMQ is the default throughout the documentation, this page is a transport-specific reference — registration, connection and TLS, settlement, and a map to the feature guides — rather than a re-explanation of every feature. The deep guides are linked at the end of each section.
Registration
As with every BareWire transport, you register the core engine and the RabbitMQ transport together. See Configuration for the full rationale behind the layering.
1. Single call — bundle package (recommended)
The BareWire.RabbitMQ bundle depends on both the core and the transport and exposes one method,
AddBareWireWithRabbitMq. Configure the transport in the first delegate and (optionally) the bus
in the second:
builder.Services.AddBareWireWithRabbitMq(
transport =>
{
transport.Host("amqp://guest:guest@localhost:5672/");
transport.ConfigureTopology(t => { /* exchanges, queues, bindings */ });
transport.ReceiveEndpoint("orders", e => e.Consumer<OrderConsumer, OrderCreated>());
},
bus =>
{
bus.AddConsumer<OrderConsumer>();
// serializers, middleware...
});
The bus delegate is optional — omit it when transport defaults are enough:
builder.Services.AddBareWireWithRabbitMq(transport => transport.Host("amqp://localhost"));
2. Two calls — core and transport registered separately
AddBareWireWithRabbitMq is sugar over the explicit pair. Use the two-call form when you reference
the core and transport packages separately, or when an application registers more than one
transport (a bundle call registers the core internally, so two bundle calls would register it
twice):
builder.Services.AddBareWireRabbitMq(transport => transport.Host("amqp://guest:guest@localhost:5672/"));
builder.Services.AddBareWire(bus => bus.AddConsumer<OrderConsumer>());
Deprecated: configuring the transport with
cfg.UseRabbitMQ(...)inside theAddBareWiredelegate is an obsolete no-op — any host/credentials passed to it are ignored. Configure the transport throughAddBareWireRabbitMqor the bundle. See Configuration.
Connection
Point the transport at the broker with Host. The connection string is typically injected via
Aspire or configuration:
// Via Aspire (automatic) — registers the RabbitMQ client connection
builder.AddRabbitMQClient("rabbitmq");
// Via connection string
transport.Host("amqp://guest:guest@localhost:5672/");
Credentials can also be supplied (and overridden) through the host configurator rather than embedded in the URI — useful when the username/password come from a secret store:
transport.Host("amqp://broker.internal:5672/", h =>
{
h.Username(username);
h.Password(password); // never logged or echoed in diagnostics
});
TLS and mutual TLS
For an encrypted connection, use the amqps:// scheme and configure TLS through the host
configurator's UseTls block. UseTls exposes an ITlsConfigurator:
| Method | Meaning |
|---|---|
WithCertificate(path, password?) |
Client certificate file (PFX or PEM) and an optional private-key passphrase (the passphrase is never logged). |
WithMutualAuthentication() |
Enables mTLS — the client presents its certificate to the broker during the handshake. |
WithServerValidation(SslPolicyErrors) |
The set of SslPolicyErrors tolerated during server-certificate validation. Defaults to SslPolicyErrors.None (strict). In production always use None — relax it only to accept self-signed certificates in test environments. |
using System.Net.Security;
// Server-authenticated TLS
transport.Host("amqps://broker.internal:5671/", h =>
{
h.Username(username);
h.Password(password);
h.UseTls(tls => tls.WithServerValidation(SslPolicyErrors.None));
});
// Mutual TLS (client certificate)
transport.Host("amqps://broker.internal:5671/", h =>
{
h.UseTls(tls => tls
.WithCertificate("/etc/secrets/client.pfx", certPassword)
.WithMutualAuthentication()
.WithServerValidation(SslPolicyErrors.None));
});
Receive endpoints
Each receive endpoint binds a consumer (or several) to a queue and carries its own concurrency and retry settings:
transport.ReceiveEndpoint("orders", e =>
{
e.PrefetchCount = 16; // broker-level prefetch
e.ConcurrentMessageLimit = 8; // in-flight concurrency
e.RetryCount = 3; // retry attempts before DLQ
e.RetryInterval = TimeSpan.FromSeconds(1);
e.Consumer<OrderConsumer, OrderCreated>();
});
An endpoint can also host a raw consumer (e.RawConsumer<T>()), a saga state machine
(e.StateMachineSaga<T>()), multiple typed consumers, consume-time routing-key dispatch, and
per-key ordering. See Publishing and Consuming,
Consumer Routing Keys, and
Per-Key Consumer Ordering.
Settlement and dead-lettering
When a consumer succeeds the message is acknowledged; when it throws, BareWire retries up to
RetryCount and then negatively settles the delivery. With a dead-letter exchange declared on the
queue, the failed message is routed to the DLX rather than discarded — without one, a rejected
message is permanently lost (and BareWire logs a warning). Always configure a DLX on production
queues. See Retry and Dead Letter Queues.
Routing semantics
A publisher confirm tells you the broker accepted a publication — not that it routed it to a
queue. By default BareWire publishes with the AMQP mandatory flag off, so a message the broker
accepts but cannot route to any queue (topology drift, a missing binding or queue, a wrong routing
key) is silently dropped while SendResult.IsConfirmed still reports true. This is at-most-once
routing: fast, but a misconfigured topology loses messages without a signal.
Enable guaranteed routing to surface unroutable publications instead of dropping them silently:
wire.UseRabbitMq(rmq =>
{
rmq.GuaranteedRouting(); // opt-in; default is off
// ... host, topology, endpoints ...
});
With it enabled, BareWire publishes mandatory and detects an unroutable message via the broker's
return, mapping it to SendResult.IsConfirmed == false (and logging a warning). The default-off
behaviour is unchanged — routable publishes are unaffected (the channel already awaits a per-message
publisher confirm, so there is no extra round-trip), and there is no per-message allocation on the
send path.
Where it fails closed. The negative confirm only changes the outcome for code that inspects
SendResult — chiefly the transactional outbox dispatcher, which treats
IsConfirmed == false as non-delivery and retries (the row stays claimed instead of being marked
delivered). The direct IBus.PublishAsync / ISendEndpoint.SendAsync path is fire-and-forget:
the caller never receives a SendResult, and the background publisher does not redeliver on a
negative confirm. On that path guaranteed routing turns a silent drop into an observable one (the
warning log) but does not by itself make direct publishing at-least-once. For at-least-once delivery
against topology drift, publish through the outbox with this option enabled.
Feature map
RabbitMQ-specific behaviour is documented across these guides:
| Topic | Guide |
|---|---|
| Transport registration paths and layering | Configuration |
Exchanges, queues, bindings, IQueueConfigurator, DLX |
Topology |
| Publish/subscribe, request/response, raw messages, per-type send routing | Publishing and Consuming |
| Consume-time routing-key dispatch on a shared queue | Consumer Routing Keys |
| Single-active-consumer / consistent-hash per-key ordering | Per-Key Consumer Ordering |
| Retry policies and dead-letter exchanges | Retry and Dead Letter Queues |
| Publish-style competing responders | Publishing and Consuming |
| At-most-once vs opt-in guaranteed routing | Routing semantics |
See also
- Configuration
- Topology
- Transports — the transport-agnostic overview and the other adapters