# Mocha: Messaging Framework for .NET

> Mocha is a .NET messaging framework with a source-generated mediator for in-process work and a message bus for commands and events between services.

Canonical source: https://chillicream.com/products/mocha

Mocha is a .NET messaging framework that sends commands and events between your services, for example telling the shipping service that an order was placed. It also runs commands and queries inside a single service, with no broker involved. Define messages and handlers in C#, and Mocha generates the handler registration at build time.

[Publish your first message](https://chillicream.com/docs/mocha/quick-start) [Read the docs](https://chillicream.com/docs/mocha)

## Answer the caller now. Finish the rest in the background.

When a customer checks out, the order service can confirm the order right away. Billing, inventory, shipping, and search keep working in the background, each at its own pace. Mocha gives you two ways to do this: send a message you don't need to wait for, or send a request and wait for the reply.

[See how messaging works](https://chillicream.com/docs/mocha)

## Start with a message and a handler.

Messages in Mocha are represented as C# records and processed by handler classes. Define the message, implement its handler interface, and Mocha takes care of registering the handler at build time.

Its analyzers also detect invalid or duplicate handlers before the application starts.

[Open the quickstart](https://chillicream.com/docs/mocha/quick-start)

## You write the code. Mocha wires up the broker.

Mocha can automatically configure the messaging infrastructure your services need. Based on the messages they send and receive, it creates the appropriate routes and transport resources. This configuration is called the topology.

Mocha sets up the topology at startup, so naming and configuration conflicts surface early. You can also define the transport configuration yourself when you need more control.

```
builder.Services
    .AddMessageBus()
    .AddOrderService()
    .AddRabbitMQ();

// exchanges, queues, and bindings are derived
// from your handlers, validated at startup
```

[See routing and endpoints](https://chillicream.com/docs/mocha/routing-and-endpoints)

The default

```
builder.Services
    .AddMessageBus()
    .AddOrderService()
    .AddRabbitMQ();

// exchanges, queues, and bindings are derived
// from your handlers, validated at startup
```

Opt out

```
.AddRabbitMQ(transport =>
{
    transport
        .DeclareExchange("region-events")
        .Type(RabbitMQExchangeType.Topic)
        .Durable();

    transport.Queue("orders")
        .BindExplicitly()
        .MaxConcurrency(10);
});
```

## Mocha is also a mediator.

Mocha does more than move messages between services. Its mediator dispatches commands and queries inside your own process, with no broker involved. It builds the dispatch pipeline at build time and runs your middleware around each handler, so the hot path avoids reflection.

```
public record PlaceOrderCommand(
    Guid ProductId, int Quantity)
    : ICommand<PlaceOrderResult>;

var result = await sender.SendAsync(
    new PlaceOrderCommand(productId, 2), ct);
```

[Read about the mediator](https://chillicream.com/docs/mocha/mediator)

The command

```
public record PlaceOrderCommand(
    Guid ProductId, int Quantity)
    : ICommand<PlaceOrderResult>;
```

Dispatch

```
var result = await sender.SendAsync(
    new PlaceOrderCommand(productId, 2), ct);
```

The handler

```
public class PlaceOrderCommandHandler(AppDbContext db)
    : ICommandHandler<PlaceOrderCommand, PlaceOrderResult>
{
    public async ValueTask<PlaceOrderResult> HandleAsync(
        PlaceOrderCommand command, CancellationToken ct)
    {
        // create the order, return the result
    }
}
```

## Publish an event. Each subscriber handles it at its own pace.

Each subscriber gets its own durable queue, set up before you start publishing. From there, it processes events at its own pace and picks up where it left off after a restart. How reliably messages are delivered, and how long they're kept, depends on the transport (in-memory, broker-backed, or database-backed) and the topology you configure.

```
await bus.PublishAsync(orderPlaced, ct);
```

[See messaging patterns](https://chillicream.com/docs/mocha/messaging-patterns)

The event

```
public sealed record OrderPlaced(
    Guid OrderId, decimal Amount);
```

Publish

```
await bus.PublishAsync(orderPlaced, ct);
```

A subscriber

```
public class OrderPlacedHandler(AppDbContext db)
    : IEventHandler<OrderPlaced>
{
    public async ValueTask HandleAsync(
        OrderPlaced message, CancellationToken ct)
    {
        // react on this service's schedule
    }
}
```

## Hand off a command without waiting for the handler.

Use a command when one service needs to do work without making the caller wait for it to finish. Mocha routes the command to its handler. You choose the transport and reliability settings that fit the job.

```
await bus.SendAsync(
    new ReserveInventoryCommand(orderId), ct);
```

[See messaging patterns](https://chillicream.com/docs/mocha/messaging-patterns)

The command

```
public sealed record ReserveInventoryCommand(
    Guid OrderId);
```

Send

```
await bus.SendAsync(
    new ReserveInventoryCommand(orderId), ct);
```

The handler

```
public class ReserveInventoryHandler(Warehouse wh)
    : IEventRequestHandler<ReserveInventoryCommand>
{
    public async ValueTask HandleAsync(
        ReserveInventoryCommand command,
        CancellationToken ct)
    {
        // runs later, on the queue's time
    }
}
```

## Wait for a typed response from another service.

Use request/reply when a caller needs an answer from one service and can wait for it. Mocha matches the reply to the original request and returns a typed response, or times out if none arrives. If the handler fails, it surfaces the error through the same reply channel.

```
var product = await bus.RequestAsync(
    new GetProductRequest(id), ct);
```

[See messaging patterns](https://chillicream.com/docs/mocha/messaging-patterns)

The request

```
public sealed record GetProductRequest(Guid Id)
    : IEventRequest<ProductResponse>;
```

Ask

```
var product = await bus.RequestAsync(
    new GetProductRequest(id), ct);
```

The handler

```
public class GetProductHandler(Catalog catalog)
    : IEventRequestHandler<
        GetProductRequest, ProductResponse>
{
    public async ValueTask<ProductResponse> HandleAsync(
        GetProductRequest request, CancellationToken ct)
    {
        // the returned value rides back as the reply
    }
}
```

## Process messages in batches.

When several messages can be processed together, batching can reduce the amount of work your application has to do. Mocha collects messages until the batch reaches a configured size or a timeout expires, then passes them to the handler in a single call.

This is especially useful for bulk operations, such as writing many records to a database at once instead of issuing a separate request for each message.

```
.AddBatchHandler<OrderPlacedBatchHandler>(
    o => o.MaxBatchSize = 100);
```

[Read about batch handlers](https://chillicream.com/docs/mocha/handlers-and-consumers)

Registration

```
.AddBatchHandler<OrderPlacedBatchHandler>(
    o => o.MaxBatchSize = 100);
```

The handler

```
public class OrderPlacedBatchHandler(AppDbContext db)
    : IBatchEventHandler<OrderPlaced>
{
    public async ValueTask HandleAsync(
        IMessageBatch<OrderPlaced> batch,
        CancellationToken ct)
    {
        // one call, up to 100 messages
    }
}
```

## Publish a message at a time you choose.

Sometimes you don't want to process a message immediately. Mocha lets you schedule a message for delivery at a specific time or after a delay, making it useful for reminders, delayed retries, and other deferred work.

Whether a scheduled message survives restarts or can be cancelled depends on the scheduling store that holds it until it is due.

```
var result = await bus.SchedulePublishAsync(
    new SendWelcomeEmail(userId),
    DateTimeOffset.UtcNow.AddMinutes(30), ct);
```

[Read about scheduling](https://chillicream.com/docs/mocha/scheduling)

Schedule

```
var result = await bus.SchedulePublishAsync(
    new SendWelcomeEmail(userId),
    DateTimeOffset.UtcNow.AddMinutes(30), ct);
```

Cancel

```
// still cancellable until it is dispatched
await bus.CancelScheduledMessageAsync(
    result.Token!, ct);
```

## Avoid duplicate writes when a broker redelivers a message.

Message brokers may deliver the same message more than once, such as after a crash or a lost acknowledgment. Mocha prevents those redeliveries from repeating the same work by recording which messages have already been processed.

The inbox and the handler's changes are committed together, while any outgoing messages are held until that transaction succeeds. For database operations, this provides effectively exactly-once processing for as long as the inbox record is retained.

[Read about reliability](https://chillicream.com/docs/mocha/reliability)

## Keep long-running workflows in one state machine.

Some workflows cannot be completed by a single message. They unfold over several steps and may need to react to events, failures, or timeouts along the way.

A saga keeps track of that progress and determines what should happen next. Its state can be stored in memory or persisted when the workflow needs to survive restarts.

If part of the workflow fails, the saga can trigger compensating actions to undo earlier steps. Timeout handling requires a scheduling store that supports delayed messages.

[Read about sagas](https://chillicream.com/docs/mocha/sagas)

## See where a message went and what handled it.

When a message passes through several services, it can be difficult to see where time was spent or where something went wrong. Mocha integrates with OpenTelemetry to connect dispatch, receive, and handler activity into a single trace that follows the message across the system.

Those traces can be sent to Nitro, where you can inspect slow handlers, failed messages, and delays between services.

[Set up observability](https://chillicream.com/docs/mocha/observability)

a

### One message, end to end

TRACE · 7f3a·9b2e

b

### Correlated across the gap

OTEL

publish -> consume, end to end

delivery latency

Correlation propagated

## Separate application logic from infrastructure.

Your messaging infrastructure will often change as your application grows. You might start with an in-memory transport during development, move to a database-backed option for simpler deployments, or use a message broker in a distributed system.

Mocha keeps those infrastructure choices out of your handlers. The same message-handling code works across transports, while each transport provides its own delivery guarantees, durability, scheduling support, and routing model.

```
builder.Services
    .AddMessageBus()
    .AddOrderService() // source-generated
    .AddRabbitMQ(t => t.IsDefaultTransport())
    .AddEventHub(t =>
        t.Handler<DeviceTelemetryHandler>());
```

[Compare transports](https://chillicream.com/docs/mocha/transports)

Registration

```
builder.Services
    .AddMessageBus()
    .AddOrderService() // source-generated
    .AddRabbitMQ(t => t.IsDefaultTransport())
    .AddEventHub(t =>
        t.Handler<DeviceTelemetryHandler>());
```

The claimed handler

```
public class DeviceTelemetryHandler(Ingest ingest)
    : IEventHandler<DeviceTelemetry>
{
    public async ValueTask HandleAsync(
        DeviceTelemetry message, CancellationToken ct)
    {
        // same shape, different transport
    }
}
```

## Publish your first message with Mocha.

You can start with a single process using the in-memory transport, a message, and a handler. As your application grows, switching to a broker-backed or database-backed transport doesn't require changing your handlers. You simply gain the delivery, durability, and reliability features your application needs.

[Publish your first message](https://chillicream.com/docs/mocha/quick-start) [Read the docs](https://chillicream.com/docs/mocha)
