Skip to content

Best Practices

This page collects practical recommendations for using ServiceBus.Core.Rabbit in production systems.


Use Descriptors As The Source Of Truth

Topology should be described through descriptors.

Avoid scattering RabbitMQ declaration logic across application code.

graph LR
    Descriptor["Descriptor Configuration"] --> Client["Publisher / Subscriber"]
    Client --> RabbitMQ

Recommended:

var descriptor = new PublisherBoundDescriptor
{
    Exchanges =
    {
        new ExchangeBoundDescriptor
        {
            Name = "orders",
            Type = ExchangeTypes.Direct,
            Durable = true
        }
    }
};

Avoid:

channel.ExchangeDeclare(...);
channel.QueueDeclare(...);
channel.QueueBind(...);

Use Meaningful Names

Use business-oriented names.

Recommended:

orders
orders.created
orders.updated
payments.completed

Avoid:

test
queue1
event2
message

The physical RabbitMQ object name is derived from the descriptor FullName.

The naming model is documented in:

Reference / Descriptors / Topology / ServiceDescriptor

Prefer Direct Or Topic Exchanges

For most business scenarios:

Scenario Recommended Exchange
Exact routing Direct
Pattern routing Topic
Broadcast Fanout
Metadata-based routing Headers

Start with direct.

Use topic when routing keys become hierarchical.


Keep Routing Keys Business-Oriented

Recommended:

orders.created
orders.updated
payments.authorized
payments.completed

For topic exchanges:

orders.eu.created
orders.us.created
orders.eu.updated

Avoid technical or temporary values.

event1
test
debug

Choose Queue Types Deliberately

RabbitMQ queue type is configured through queue arguments.

Arguments =
{
    ["x-queue-type"] = "quorum"
}

General guidance:

Queue Type Recommended Usage
classic Compatibility and simple workloads.
quorum Durable production workloads.
stream Event streaming and replay.

For production systems, prefer quorum queues unless there is a specific reason not to.


Tune Prefetch Count

PrefetchCount controls how many unacknowledged messages RabbitMQ can deliver to a subscriber.

Start conservatively.

PrefetchCount = 10

Use lower values when:

  • processing is expensive;
  • messages are large;
  • fairness between consumers matters.

Use higher values when:

  • messages are small;
  • processing is fast;
  • throughput is more important than fairness.

Use Retry Policies For Transient Failures

Retries are useful for temporary failures.

Examples:

  • database timeout;
  • downstream service unavailable;
  • temporary network error.
RetryPolicy = new RetryPolicyDescriptor
{
    Enabled = true,
    MaxRetry = 5,
    Delayed = TimeSpan.FromSeconds(10),
    Durable = true,
    AutoDelete = false
}

Do not use retries for invalid messages or permanent business failures.


Use Dead Letter Strategies

Messages that cannot be processed should be isolated.

Arguments =
{
    ["x-dead-letter-exchange"] = "orders.dead-letter",
    ["x-dead-letter-routing-key"] = "orders.failed"
}

Dead-letter queues should be monitored.


Use Delivery Confirmation For Critical Publishers

Enable delivery confirmation when the publisher must know whether RabbitMQ accepted the message.

Confirmation = new DeliveryConfirmation
{
    Enabled = true,
    WaitFor = true,
    Timeout = TimeSpan.FromSeconds(30)
}

Use it for:

  • financial events;
  • integration events;
  • audit events;
  • critical business messages.

Observe Channel Events

Enable channel events when diagnostics and observability are required.

EnableChannelEvents = true

Then attach handlers through:

publisher.OnChannelEventFired.Add(...);
subscriber.OnChannelEventFired.Add(...);

Channel events are useful for:

  • flow control;
  • callback exceptions;
  • channel shutdown events.

Prefer Dependency Injection In Applications

Manual construction is useful for learning and tests.

For applications, prefer dependency injection.

This keeps object creation and lifetime management centralized.

See:

How To / Dependency Injection