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)
Single bus configuration
Section titled “Single bus configuration”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.
MultiBus configuration
Section titled “MultiBus configuration”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.
Marker bus interfaces
Section titled “Marker bus interfaces”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.
Call AddMassTransit<T>
Section titled “Call AddMassTransit<T>”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); });});Using your other bus instance
Section titled “Using your other bus instance”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.
ASP.NET Controller
Section titled “ASP.NET Controller”[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 = { } }) }}Minimal API
Section titled “Minimal API”app.MapPost("/inventory", async ([FromBody] AllocateInventory request, IScopedBus<SecondBus> secondBus) =>{ await _secondBus.Publish(request);}Razor Page
Section titled “Razor Page”public class InventoryPage : PageModel{ public void OnPost([FromServices] IScopedBus<ISecondBus> secondBus) { await _secondBus.Publish(new AllocateInventory { SomeData = { } }) }}Advanced bus instances
Section titled “Advanced bus instances”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.
Container Registration Details
Section titled “Container Registration Details”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.
First Bus
Section titled “First Bus”There are several interfaces added to the container using this configuration:
| Interface | Lifestyle | Notes |
|---|---|---|
| IBusControl | Singleton | Used to start/stop the bus (not typically used) |
| IBus | Singleton | Publish/Send on this bus, starting a new conversation |
| IScopedBus | Scoped | V9: New interface for send, publish, request, etc. |
| ISendEndpointProvider | Scoped | Send messages from consumer dependencies, ASP.NET Controllers |
| IPublishEndpoint | Scoped | Publish messages from consumer dependencies, ASP.NET Controllers |
| IClientFactory | Singleton | Used to create request clients (singleton, or within scoped consumers) |
| IRequestClient<SubmitOrder> | Scoped | Used to send requests |
| ConsumeContext | Scoped | Available in any message scope, such as a consumer, saga, or activity |
Multibus
Section titled “Multibus”The registered interfaces are slightly different for additional bus instances.
| Interface | Lifestyle | Notes |
|---|---|---|
| N/A | Not registered, but automatically started/stopped by the hosted service | |
| N/A | Not registered, the new bus interface is registered instead | |
| ISecondBus | Singleton | Publish/Send on this bus, starting a new conversation |
| IScopedBus<ISecondBus> | Scoped | V9: New interface for send, publish, request, etc. |
| ISendEndpointProvider | Scoped | Send messages from consumer dependencies only |
| IPublishEndpoint | Scoped | Publish messages from consumer dependencies only |
| IClientFactory | N/A | Registered as an instance-specific client factory |
| IRequestClient<SubmitOrder> | Scoped | Created using the specific bus instance |
| ConsumeContext | Scoped | Available in any message scope, such as a consumer, saga, or activity |
| Bind<ISecondBus, ISendEndpointProvider> | Scoped | Send messages from controllers or outside of a consumer context |
| Bind<ISecondBus, IPublishEndpoint> | Scoped | Publish messages from controllers or outside of a consumer context |
| Bind<ISecondBus, IClientFactory> | Scoped | Registered as an instance-specific client factory |
| Bind<ISecondBus, IRequestClient<SubmitOrder>> | Scoped | Created using the bound bus instance |
The Bind<TKey, TValue> pattern
Section titled “The Bind<TKey, TValue> pattern”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.
-
DependencyInjectionContainerRegistrarHandles 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.