Skip to content

MultiBus Configuration

pronounced mool-tee-buss

MassTransit is designed so that most applications only need a single bus, and that is the recommended approach. Using a single bus, with as many receive endpoints as needed, minimizes complexity and ensures efficient broker resource utilization. Consistent with this guidance, container configuration using the AddMassTransit method registers the appropriate types so that they are available to other components, as well as consumers, sagas, and activities.

However, with broader use of cloud-based platforms comes a greater variety of messaging transports, not to mention HTTP as a transfer protocol. As application sophistication increases, connecting to multiple message transports and/or brokers is becoming more common. Therefore, rather than force developers to create their own solutions, MassTransit can configure additional bus instances within the dependency injection container.

MultiBus allows applications to connect to multiple message transports (or the same transport with different configurations) simultaneously. Each bus instance is isolated and has its own:

  • Transport connection
  • Consumers, saga state machines, and routing slip activities
  • Receive endpoints
  • Riders (e.g., Kafka, Event Hub)

To review, an example configuration for a single bus instance is shown below.

services.AddMassTransit(x =>
{
x.AddConsumer<SubmitOrderConsumer>();
x.AddRequestClient<SubmitOrder>();
x.UsingRabbitMq((context, cfg) =>
{
cfg.ConfigureEndpoints(context);
});
});

This configures the container so that there is a bus, using RabbitMQ, with a single consumer SubmitOrderConsumer, using automatic endpoint configuration.

When a consumer, a saga state machine, or a routing slip activity is consuming a message, the ConsumeContext is accessible from the container scope. When the consumer is created from the container, the consumer and its dependencies (constructor injected) are created within that scope. If a dependency includes IScopedBus, ISendEndpointProvider, IPublishEndpoint, or ConsumeContext (not the first choice, but totally fine) on the constructor, all three of those interfaces are the same object reference. This ensures that messages sent and/or published by the consumer or its dependencies include the proper correlation identifiers and telemetry.

To support multiple bus instances in a single container, the interface behaviors described above had to be considered carefully. There are expectations on how these interfaces behave, and it was important to ensure consistent behavior whether an application has one, two, or a dozen bus instances (please, not a dozen – think of the children). To ensure that sent or published messages end up on the right queues or topics, a mechanism to differentiate between the bus instances is needed. The ability to configure each bus instance separately, yet leverage the power of a single shared container is also a must.

To define additional bus instances, MassTransit uses a marker interface for each additional bus instance, identified by a custom interface that extends IBus.

public interface ISecondBus :
IBus
{
}

These interfaces serve as type-safe keys for dependency injection, allowing the container to resolve the correct bus instance based on the requested interface type.

Once the market interface has been defined, use that interface when configuring the additional bus with the AddMassTransit<T> method, where T is the interface that was defined.

services.AddMassTransit<ISecondBus>(x =>
{
x.AddConsumer<AllocateInventoryConsumer>();
x.AddRequestClient<AllocateInventory>();
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host("remote-host");
cfg.ConfigureEndpoints(context);
});
});

For consumers or dependencies that need to send or publish messages to a different bus instance, the consumer should use either IScopedBus (for the default bus) or IScopedBus<ISecondBus> (replace ISecondBus with the actual bus marker interface) for an additional bus instance.

[ApiController]
[Route("/inventory")]
public class InventoryController : ControllerBase
{
readonly IScopedBus _secondBus;
public InventoryController(IScopedBus<ISecondBus> secondBus)
{
_secondBus = secondBus;
}
[HttpPost]
public async Task<IActionResult> Post()
{
// .. do stuff
await _secondBus.Publish(new AllocateInventory
{
SomeData = { }
})
}
}
app.MapPost("/inventory", async ([FromBody] AllocateInventory request, IScopedBus<SecondBus> secondBus) =>
{
await _secondBus.Publish(request);
}
public class InventoryPage : PageModel
{
public void OnPost([FromServices] IScopedBus<ISecondBus> secondBus)
{
await _secondBus.Publish(new AllocateInventory
{
SomeData = { }
})
}
}

In the example above, which should be the most common use, the ISecondBus interface is all that is required. MassTransit creates a dynamic class to delegate the IBus methods to the bus instance. However, it is possible to specify a class that implements ISecondBus instead.

To specify a class, as well as take advantage of the container to bring additional properties along with it, take a look at the following types and configuration.

public interface IThirdBus :
IBus
{
ISomeService SomeService { get; }
}
class ThirdBus :
BusInstance<IThirdBus>,
IThirdBus
{
public ThirdBus(IBusControl busControl, ISomeService someService)
: base(busControl)
{
SomeService = someService;
}
public ISomeService SomeService { get; }
}
public interface ISomeService
{
}
services.AddMassTransit<IThirdBus, ThirdBus>(x =>
{
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host("third-host");
});
});

This would add a third bus instance using the class specified. The class is created using the container and passed IBusControl (which must be passed down to the base class to ensure proper initialization.

To support a first class experience with Microsoft.Extensions.DependencyInjection MassTransit registers common interfaces for MultiBus instances using a Bind<TKey, TValue> class that associates an owner with a registered type. This allows access to various MassTransit components outside a consumer. Below are two tables that list out the various interfaces you might need to access.

There are several interfaces added to the container using this configuration:

InterfaceLifestyleNotes
IBusControlSingletonUsed to start/stop the bus (not typically used)
IBusSingletonPublish/Send on this bus, starting a new conversation
IScopedBusScopedV9: New interface for send, publish, request, etc.
ISendEndpointProviderScopedSend messages from consumer dependencies, ASP.NET Controllers
IPublishEndpointScopedPublish messages from consumer dependencies, ASP.NET Controllers
IClientFactorySingletonUsed to create request clients (singleton, or within scoped consumers)
IRequestClient<SubmitOrder>ScopedUsed to send requests
ConsumeContextScopedAvailable in any message scope, such as a consumer, saga, or activity

The registered interfaces are slightly different for additional bus instances.

InterfaceLifestyleNotes
IBusControlN/ANot registered, but automatically started/stopped by the hosted service
IBusN/ANot registered, the new bus interface is registered instead
ISecondBusSingletonPublish/Send on this bus, starting a new conversation
IScopedBus<ISecondBus>ScopedV9: New interface for send, publish, request, etc.
ISendEndpointProviderScopedSend messages from consumer dependencies only
IPublishEndpointScopedPublish messages from consumer dependencies only
IClientFactoryN/ARegistered as an instance-specific client factory
IRequestClient<SubmitOrder>ScopedCreated using the specific bus instance
ConsumeContextScopedAvailable in any message scope, such as a consumer, saga, or activity
Bind<ISecondBus, ISendEndpointProvider>ScopedSend messages from controllers or outside of a consumer context
Bind<ISecondBus, IPublishEndpoint>ScopedPublish messages from controllers or outside of a consumer context
Bind<ISecondBus, IClientFactory>ScopedRegistered as an instance-specific client factory
Bind<ISecondBus, IRequestClient<SubmitOrder>>ScopedCreated using the bound bus instance

Bind<TKey, TValue> is a wrapper class that associates a service with a specific bus:

public class Bind<TKey, TValue>
where TValue : class
{
public TValue Value { get; }
}

This pattern enables the DI container to differentiate services belonging to different bus instances. MassTransit uses another interface, IContainerRegistrar, to register consumers, saga state machines, and routing slip activities, and other services associated with the proper bus instance.

  • DependencyInjectionContainerRegistrar Handles registration of the default bus services into the container.

  • DependencyInjectionContainerRegistrar<TBus> Handles registration of the additional bus services into the container.

Registrations are then retrieved from the container using the IContainerSelector interface.