Open-source project
zarusz/SlimMessageBus avatar
zarusz/SlimMessageBus

SlimMessageBus is one .NET messaging API with a plugin per broker

Lightweight message bus interface for .NET (pub/sub and request-response) with transport plugins for popular message brokers.

660 stars88 forksC#Apache-2.0

At a glance

What is it?
SlimMessageBus is a .NET messaging framework that hides the broker behind a single bus interface, so the same Publish and Send calls work against Kafka, RabbitMQ, Azure Service Bus, MQTT, Redis and a dozen other transports. The parts worth knowing about are the plugin boundaries, the transactional outbox, and the fact that it publishes migration guides from MediatR and MassTransit.
Who is it for?
Adopt SlimMessageBus if you run more than one broker, or want to change broker without touching call sites, since the transport, serialization and routing are all plugins behind one interface. Skip it if your messaging needs are a single topic on a single broker, since the abstraction will not pay for itself there, and if you need a broker the project does not support.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One interface, and a package per transport

SlimMessageBus is a lightweight, flexible, and extensible messaging framework for .NET, supporting multiple message brokers including Kafka, RabbitMQ, Azure EventHubs, MQTT and Redis Pub/Sub, and it simplifies asynchronous communication while integrating with modern .NET applications.

The shape of the design shows up immediately in the package list. The core is one package, and the transports are separate ones you add on top:

bash
dotnet add package SlimMessageBus
# Add specific transport provider, e.g. Kafka
dotnet add package SlimMessageBus.Host.Kafka
# Add serialization plugin
dotnet add package SlimMessageBus.Host.Serialization.SystemTextJson

So a project using Kafka installs the core, the Kafka host and a serialization plugin, and a project using RabbitMQ installs the core, the RabbitMQ host and the same serialization plugin. The core does not reference any broker at all, which is what makes the abstraction real rather than nominal.

That is the reason to look at this library if you have more than one broker, or if you want the option of changing one. The message publishing code does not change when the transport does, because the transport is a plugin.

The same package layout applies to serialization. The docs list Serialization as a plugin category rather than a transport, with separate packages for SystemTextJson and Json, and the configuration call is `.AddJsonSerializer()`.

Publishing and consuming, and the shape of the API

The basic usage is short, and it is the ordinary publish and consume shape with a small twist worth noticing.

Publishing takes the bus and the message:

csharp
IMessageBus bus; // injected

public record OrderCreatedEvent(int OrderId);

await bus.Publish(new OrderCreatedEvent(123));

The consumer is a class implementing a generic interface with a single handler method:

csharp
public class OrderCreatedEventConsumer : IConsumer<OrderCreatedEvent>
{
    public async Task OnHandle(OrderCreatedEvent message, CancellationToken cancellationToken)
    {
        // Handle the event
    }
}

Note the wording in the interface method, OnHandle, and the fact that the handler returns nothing. A consumer is told a message arrived and deals with it, and the framework owns the acknowledgement and retry policy. That matters for the broker comparison, because in Kafka the acknowledgement is a commit and in Azure Service Bus it is a lock completion, and neither is something an application handler should be deciding per message.

The request-response side is a separate pattern with a different shape, using a request and a result type:

csharp
public record CreateCustomerCommand(string Name) : IRequest<CreateCustomerCommandResult>;
public record CreateCustomerCommandResult(Guid CustomerId);

var result = await bus.Send(new CreateCustomerCommand("John Doe"));

and a handler implementing a request handler interface:

csharp

public class CreateCustomerCommandHandler : IRequestHandler<CreateCustomerCommand, CreateCustomerCommandResult>
{
    public async Task<CreateCustomerCommandResult> OnHandle(CreateCustomerCommand request, CancellationToken cancellationToken)
    {
        // Create customer logic
        return new(Guid.NewGuid());
    }
}

So pub/sub and request/reply are distinct concepts with distinct types, which is the correct modelling. A publish has no addressee, and a send has exactly one.

The record types are used for both the event and the command, which is worth noticing because it means the message shape is a plain data type with no framework base class and no attributes required.

The configuration is where the transport differences show

The configuration example is long, and reading it tells you most of what you need to know about how the library maps concepts onto brokers.

csharp
services.AddSlimMessageBus(mbb =>
{
    mbb.AddChildBus("Bus1", builder =>
    {
         builder
               // the pub-sub events
               .Produce<OrderCreatedEvent>(x => x.DefaultPath("orders-topic"))
               .Consume<OrderCreatedEvent>(x => x.Path("orders-topic")
                  //.WithConsumer<OrderCreatedEventConsumer>() // Optional: can be skipped as IConsumer<OrderCreatedEvent> will be resolved from DI
                  //.KafkaGroup("kafka-consumer-group") // Kafka: Consumer Group
                  //.SubscriptionName("azure-sb-topic-subscription") // Azure ServiceBus: Subscription Name
               )

               // the request-response
               .Produce<CreateCustomerCommand>(x => x.DefaultPath("customer-requests"))
               .Handle<CreateCustomerCommand, CreateCustomerCommandResult>(x => x.Path("customer-requests"))

               // Use Kafka transport provider (requires SlimMessageBus.Host.Kafka package)
               .WithProviderKafka(cfg => cfg.BrokerList = "localhost:9092");

            // Use Azure Service Bus transport provider
            //.WithProviderServiceBus(cfg => { ... }) // requires SlimMessageBus.Host.AzureServiceBus package
            // Use Azure Event Hub transport provider
            //.WithProviderEventHub(cfg => { ... }) // requires SlimMessageBus.Host.AzureEventHub package
            // Use Redis transport provider
            //.WithProviderRedis(cfg => { ... }) // requires SlimMessageBus.Host.Redis package
            // Use RabbitMQ transport provider
            //.WithProviderRabbitMQ(cfg => { ... }) // requires SlimMessageBus.Host.RabbitMQ package

            // Use in-memory transport provider
            //.WithProviderMemory(cfg => { ... }) // requires SlimMessageBus.Host.Memory package
   })
   // Add other bus transports (as child bus for in memory domain events), if needed
   //.AddChildBus("Bus2", (builder) => {  })
   .AddJsonSerializer() // requires SlimMessageBus.Host.Serialization.SystemTextJson or SlimMessageBus.Host.Serialization.Json package
   .AddServicesFromAssemblyContaining<OrderCreatedEventConsumer>();
});

Several things are visible there. The Produce and Consume calls take a path, and that path is what becomes a topic, a queue or a subscription depending on the provider. The Kafka consumer group and the Azure Service Bus subscription name are commented out on the Consume line, which tells you they are provider-specific extras rather than part of the core model.

The transport is then selected with a single WithProvider call, and the commented alternatives list what else is available: Service Bus, Event Hub, Redis, RabbitMQ and the in-memory provider. Each one names the package it needs, which is the same rule as the package list itself.

The child bus concept is the other structural idea. AddChildBus creates a named bus with its own provider and configuration, and the comment explains the intent, adding other bus transports as a child bus for in-memory domain events if needed. So a single application can have an in-memory bus for domain events that never leaves the process, and a Kafka bus for integration events that do, without the two colliding.

That is the modular monolith arrangement, and the documentation says the configuration can be modularized for that case, with the details in the introduction page.

Fourteen transports and four plugin categories

The documentation index is the clearest catalogue of the scope.

The transports are Amazon SQS and SNS, Apache Kafka, Azure EventHubs, Azure ServiceBus, Google Cloud Pub/Sub, a Hybrid transport, MQTT, an in-memory transport, NATS, PostgreSQL, RabbitMQ, Redis and SQL. Thirteen named transports, and the Hybrid one is interesting because it is a provider that composes others rather than being a broker in its own right.

The two that stand out from the usual set are the in-memory and SQL providers. The in-memory one is what you use for domain events inside a single process, and it means the same code path can run in tests without a broker at all. The SQL provider means the broker can be a database table, which is a genuinely useful option for a small deployment or for a system that already has a database and does not want another dependency.

The plugins are a separate category, and this is where the features that are not about transport live. There is Serialization, the Transactional Outbox plugin with a MongoDB variant, Validation using FluentValidation, AsyncAPI specification generation, and a Consumer Circuit Breaker listed under the health check section of the introduction.

Each of those plugins is a separate concern with its own documentation page, which tells you they are real integrations rather than bullet points. The AsyncAPI generator in particular is the sort of thing that saves a team from hand-writing a broker-neutral contract, and the FluentValidation plugin moves request validation into the pipeline rather than into each handler.

The transactional outbox is the plugin that changes correctness

The transactional outbox plugin is worth understanding on its own, because it is the one feature that addresses a failure mode the rest of the library does not.

The problem it solves is the dual write. If your service writes to its own database and then publishes a message, and the process dies between those two operations, you have a state change with no event, or an event with no state change. Neither is recoverable by retrying the publish, because the first write already committed.

The outbox pattern fixes it by making the event and the state change one atomic operation. The message is written to an outbox table in the same transaction as the business data, and a separate process reads that table and publishes to the broker. If the service dies, the outbox row is still there and gets published later.

The cost is a delay, because publishing is now asynchronous to the transaction, and a second component to run. The documentation gives the outbox plugin its own page, and a MongoDB variant its own page, which suggests the implementation is not trivial and the storage choice matters.

This is the plugin to read first if you are considering the library for a system where lost events matter, because a message bus without an outbox is a bus that will eventually lose a message, and the library is explicit about that being a separate concern you opt into.

The consumer circuit breaker belongs to the same family of operational concerns. A consumer that keeps failing and retries forever will take the broker's partition or subscription down with it, so a circuit breaker that stops consuming from a failing consumer and reports it as unhealthy is a production necessity rather than a nicety.

It is positioned as a replacement for MediatR and MassTransit

The line right under the badges is a migration guide reference, and it shapes how the library presents itself.

There is a See how to migrate from MediatR or MassTransit link, and the documentation has a Use Cases directory with a ReplaceMediatR page and a ReplaceMassTransit page. So the library is not only an alternative, it is positioned as a drop-in replacement for two established .NET libraries that people already use.

MediatR is the in-process mediator that most .NET teams added when they wanted to separate a command handler from the controller that calls it, and it turned into a bus with no broker attached. MassTransit is the full message bus with the broker built in. SlimMessageBus is the message bus with the broker pluggable, so the positioning is closest to MassTransit but with the transport decoupled.

The migration pages are the most useful thing in the repository for an evaluator, because they translate the vocabulary of the library you already know into the vocabulary of this one. A bus is a bus, a publish is a publish, and the difference is that here the transport is a package you choose rather than a dependency the library pulls in.

The quality signals are also visible in the badges. There is a SonarCloud maintainability rating, a coverage badge, a duplicated lines badge, a vulnerabilities badge and a quality gate status badge, which is an unusual amount of continuous quality tooling to advertise. The build badge points at a GitHub Actions workflow, and the top-level entries include an AGENTS.md, a CONTRIBUTING.md, a docs/ directory, a src/ directory with a Samples subdirectory, an infrastructure.ps1 and an infrastructure.sh, and a build/ directory.

The project is C#, Apache-2.0 licensed, with the last push on 2026-08-29 and releases 3.6.0 from 2026-08-14, 3.5.0 from 2026-07-09 and 3.4.0 from 2025-12-06.

Editorial conclusion

Adopt SlimMessageBus if you run more than one broker, or want to change broker without touching call sites, since the transport, serialization and routing are all plugins behind one interface. Skip it if your messaging needs are a single topic on a single broker, since the abstraction will not pay for itself there, and if you need a broker the project does not support. Verify first which transports and plugins you actually need, because each is a separate NuGet package, that you read the outbox plugin documentation before you rely on it for atomicity, and how you will handle dead letters and circuit breaking, since the consumer circuit breaker is a documented health check feature rather than something automatic.

Frequently asked questions

How do I install SlimMessageBus and a transport?

Add the core package with `dotnet add package SlimMessageBus`, then the host package for the broker you use, for example `dotnet add package SlimMessageBus.Host.Kafka`, and a serialization package such as `dotnet add package SlimMessageBus.Host.Serialization.SystemTextJson`. Each transport is a separate package, so the core carries no broker dependency.

What is the difference between publishing and sending in SlimMessageBus?

Publishing is the pub-sub pattern and has no addressee, with `bus.Publish(new OrderCreatedEvent(123))` and a consumer implementing `IConsumer<OrderCreatedEvent>` with an `OnHandle` method. Sending is request-response and has exactly one handler, with `bus.Send(new CreateCustomerCommand("John Doe"))` and a handler implementing `IRequestHandler<CreateCustomerCommand, CreateCustomerCommandResult>`.

Which transports and plugins does SlimMessageBus support?

The documented transports are Amazon SQS/SNS, Apache Kafka, Azure EventHubs, Azure ServiceBus, Google Cloud Pub/Sub, a Hybrid transport, MQTT, in-memory, NATS, PostgreSQL, RabbitMQ, Redis and SQL. The plugins are Serialization, a Transactional Outbox with a MongoDB variant, Validation using FluentValidation, AsyncAPI specification generation, and a Consumer Circuit Breaker.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. Releases
  5. zarusz/SlimMessageBus on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/zarusz-slimmessagebus.svg)](https://hysenlabs.com/projects/zarusz-slimmessagebus)

Was this useful?