KubeMQ
IntegrationsMassTransitConcepts

MassTransit Concepts

Understand how MassTransit messaging patterns map onto native KubeMQ patterns, channel naming, header mapping, and the rider-based transport architecture.

Transport-Agnostic by Design

MassTransit.KubeMQ is a MassTransit transport: it adapts MassTransit's messaging abstractions to KubeMQ-native patterns. The application-level code you write against MassTransit does not change when KubeMQ becomes the transport. The following work identically regardless of transport:

  • Message contracts and IConsumer<T> implementations
  • Consumer and saga logic
  • The middleware pipeline (filters, observers)
  • Serialization, retry and redelivery policies, and the outbox pattern
  • OpenTelemetry integration and dependency-injection registration (AddConsumer<T>, AddSaga<T>)

What the transport does change is where messages physically travel: instead of RabbitMQ exchanges or an Azure Service Bus namespace, the same contracts are routed to KubeMQ's native Queues, Events, EventsStore, and CQ subsystems over gRPC. The sections below explain that routing.

Because the contracts and consumers are transport-agnostic, switching to KubeMQ is mainly a Program.cs change — replace UsingRabbitMq/UsingAzureServiceBus/UsingAmazonSqs with UsingKubeMQ.

Pattern Mapping

Each MassTransit messaging verb maps to a specific KubeMQ pattern. The mapping determines delivery semantics (point-to-point vs. fan-out, durable vs. volatile) and which KubeMQ channel type backs the message.

MassTransit PatternKubeMQ PatternDeliveryChannel Type
SendQueuesPoint-to-point (exactly one consumer)Queue
PublishEventsFan-out (all active subscribers)Events
Publish (durable)EventsStoreFan-out with persistenceEventsStore
Request/ResponseCommands/Queries (CQ)Native request-replyCQ
  • Send → Queues. ISendEndpointProvider.Send<T>(uri) routes to a KubeMQ Queue. Exactly one consumer receives each message, messages persist until consumed, and the transport acks on success / nacks on failure. Delayed delivery (ctx.Delay) maps to QueueMessage.DelaySeconds, and TTL maps to QueueMessage.ExpirationSeconds.
  • Publish → Events. IPublishEndpoint.Publish<T>() routes to KubeMQ Events: fire-and-forget fan-out to all active subscribers, with no persistence or acknowledgment. If no subscriber is active, the message is silently dropped. Delayed delivery is not supported on Events and throws KubeMQTransportConfigurationException.
  • Publish (durable) → EventsStore. When EventsStore is enabled (cfg.UseEventsStore() globally, or e.UseEventsStore() per endpoint), Publish<T>() uses KubeMQ EventsStore instead. Messages are persisted and can be replayed from configurable start positions, so subscribers can join later and catch up.
  • Request/Response → CQ. IRequestClient<T>.GetResponse<TResponse>() maps to KubeMQ's native CQ pattern, with built-in ReplyChannel and timeout. No temporary reply queues are created (unlike RabbitMQ or SQS, which simulate request/response). Two modes are available, covered in CQ modes below.

Rider-Based Architecture

MassTransit transports are normally created with a Using... bus factory. MassTransit.KubeMQ instead layers KubeMQ on top of an InMemory base bus plus a KubeMQ rider that owns the actual KubeMQ connections and receive transports.

There are two entry points, both defined in KubeMQBusRegistrationExtensions:

UsingKubeMQ() is the common case — KubeMQ is the transport for the whole application. Internally it calls UsingInMemory() to provide the IBusControl lifecycle, then attaches a KubeMQ rider that manages connections and receive endpoints.

Program.cs
services.AddMassTransit(x =>
{
    x.AddConsumer<OrderConsumer>();

    x.UsingKubeMQ((ctx, cfg) =>
    {
        cfg.Host("localhost", 50000, h =>
        {
            h.AuthToken = "my-token";
        });

        cfg.ReceiveEndpoint("order-consumer", e =>
        {
            e.ConfigureKubeMQ(t => { });
        });
    });
});

AddKubeMQRider() adds KubeMQ to a bus that already uses another transport (RabbitMQ, InMemory, etc.) as a supplementary transport. The rider is configured with IKubeMQRiderConfigurator.

Program.cs
services.AddMassTransit(x =>
{
    x.UsingInMemory();

    x.AddKubeMQRider((ctx, k) =>
    {
        k.Host("localhost", 50000);
        k.ReceiveEndpoint("orders", e => { });
    });
});

The IKubeMQRider Surface

The rider exposes outbound operations alongside its receive transports. After the bus is started, you can resolve IKubeMQRider and use:

MethodPurpose
GetSendEndpoint(Uri, ct)Resolves a send endpoint for kubemq://host:port/channel queue addresses (Send → Queues).
PublishEventAsync<T>(message, channelName, ct)Publishes a volatile event to a KubeMQ Events channel (fan-out).
SendRequestAsync<TRequest, TResponse>(request, channelName, timeoutSeconds, ct)Sends a request via KubeMQ Queries (or Commands) and returns the deserialized response.
Using the rider directly
var rider = KubeMQRiderAccessor.Current
    ?? throw new InvalidOperationException("KubeMQ rider not started.");

var endpoint = await rider.GetSendEndpoint(
    new Uri("kubemq://localhost:50000/competing-consumers-queue"));

await endpoint.Send(new WorkItem("WORK-001", "Process work item #1", 1));

SendRequestAsync defaults to a 30-second timeout (timeoutSeconds = 30). Whether it sends a Query or a Command is controlled by the configured CQ mode.

CQ Modes (Commands vs Queries)

Request/response can run in either of two CQ modes, selected by the CqMode enum:

ModeValueSemantics
Queries0 (default)Request carries data, response carries data.
Commands1Request carries data, response is an execution acknowledgment only (fire-and-await-ack).

The default is Queries. Override it globally with cfg.UseCommandsForRequestResponse(), or per endpoint with e.UseQueries() / e.UseCommands(). On a consumer error, the response includes Executed = false and an Error message.

Connection Lifecycle and Health

The KubeMQ SDK manages its own connection through a ConnectionState state machine. The transport monitors IKubeMQClient.StateChanged events and surfaces each state to MassTransit's health pipeline.

The KubeMQ SDK connection state machine; the transport maps each state onto a MassTransit health status.

Each KubeMQ state maps to a MassTransit health status:

KubeMQ StateMassTransit HealthMeaning
ReadyHealthyConnection is active and operational
ConnectingDegradedInitial connection in progress
ReconnectingDegradedLost connection, attempting to reconnect
ClosedUnhealthyConnection permanently closed
IdleUnhealthyNot connected

MassTransit registers these health checks automatically; expose them with app.MapHealthChecks("/health"). For queue receive transports, when a QueueDownstreamReceiver stream breaks the transport catches the connection exception, disposes the broken receiver, waits one second, and creates a new receiver on the next poll iteration. Unsent messages during a disconnection are handled by MassTransit's retry pipeline, not by the transport. Tune the timeouts on the host:

Reconnection configuration
cfg.Host("kubemq-server", 50000, h =>
{
    h.ConnectionTimeout = TimeSpan.FromSeconds(30);   // Initial connection
    h.ReconnectTimeout = TimeSpan.FromSeconds(120);   // Max wait for reconnection
});

Channel Naming Conventions

KubeMQ channel names are derived from MassTransit endpoint and message-type names. There is no exchange or topology layer — channels are created automatically on first use.

ContextPatternExample
Send endpointQueue name from URI pathqueue:order-processingorder-processing
Publish endpointMessage type full nameMyApp.Events.OrderSubmitted
Consumer endpointEndpoint name (auto or manual)order-consumer
Error channel{channel}_errororder-processing_error
Skipped channel{channel}_skippedorder-processing_skipped
Priority high{channel}_highorder-processing_high
Priority normal{channel}_normalorder-processing_normal
Priority low{channel}_loworder-processing_low
Consumer groupSame as endpoint nameorder-consumer

The : character in type names is replaced with . — for example, Namespace:Type becomes Namespace.Type. IEndpointNameFormatter conventions (PascalCase, kebab-case, snake_case) are respected. Faulted messages route to the _error channel after all configured retries are exhausted; skipped messages route to the _skipped channel.

Header and Envelope Mapping

The MassTransit message envelope is preserved across KubeMQ by mapping headers to KubeMQ Tags with the MT- prefix. Custom user headers use the MT-Header-{name} form.

MassTransit HeaderKubeMQ TagDescription
MessageIdMT-MessageIdUnique message identifier
CorrelationIdMT-CorrelationIdCorrelation for related messages
ConversationIdMT-ConversationIdConversation tracking
RequestIdMT-RequestIdRequest/response correlation
InitiatorIdMT-InitiatorIdMessage initiator
SourceAddressMT-SourceAddressSending endpoint address
DestinationAddressMT-DestinationAddressTarget endpoint address
ResponseAddressMT-ResponseAddressResponse endpoint address
FaultAddressMT-FaultAddressFault handling address
ContentTypeMT-ContentTypeSerialization content type
SentTimeMT-SentTimeISO 8601 send timestamp
ExpirationTimeMT-ExpirationTimeISO 8601 expiration (from TTL)
MessageTypeMT-MessageTypeSupported message types (;-separated)
Custom headersMT-Header-{name}User-defined headers

W3C distributed-trace context is propagated alongside these headers so OpenTelemetry traces span service boundaries:

  • MT-TraceParent — W3C traceparent header
  • MT-TraceState — W3C tracestate header

When a message is faulted, additional tags carry the failure metadata: MT-Fault-ExceptionType, MT-Fault-Message, MT-Fault-StackTrace, MT-Fault-Timestamp, and MT-Fault-RetryCount.

Consumer Groups (Competing Consumers)

Competing consumers in MassTransit map directly onto KubeMQ's consumer-group model: the consumer group is the endpoint name. When multiple consumer instances share the same endpoint name, KubeMQ load-balances messages across the group so each message is delivered to exactly one member.

The simplest demonstration declares two receive endpoints on the same channel — KubeMQ treats them as one group and distributes work between them:

Competing consumers on a shared channel
x.UsingKubeMQ((context, cfg) =>
{
    cfg.Host("localhost", 50000);

    // Two endpoints on the same channel form one consumer group.
    // KubeMQ load-balances messages across members,
    // so each message is consumed exactly once.
    cfg.ReceiveEndpoint("competing-consumers-queue", e =>
    {
        e.ConfigureKubeMQ(t => { });
    });

    cfg.ReceiveEndpoint("competing-consumers-queue", e =>
    {
        e.ConfigureKubeMQ(t => { });
    });
});

This applies to both Send → Queues and Request/Response → CQ: the same endpoint name lets multiple instances share queue load or act as competing responders. To scale horizontally, run multiple application instances that all bind the same endpoint name.

Competing consumers must use the same endpoint name to form one group. A name mismatch creates separate groups, and each group receives its own copy of every message.

Endpoint Address Format

Send endpoints use the kubemq:// URI scheme. The path segment is the target channel name:

kubemq://host:port/channel-name

The queue: shorthand resolves to the same target on the configured host and is the recommended form inside the bus:

Resolving a send endpoint
// Shorthand form — resolved against the configured KubeMQ host/port
var endpoint = await bus.GetSendEndpoint(new Uri("queue:order-processing"));

// Fully-qualified form — required when using IKubeMQRider.GetSendEndpoint directly
var direct = await rider.GetSendEndpoint(
    new Uri("kubemq://localhost:50000/order-processing"));

Bus → Rider → Broker Flow

The application talks to MassTransit abstractions as usual. The KubeMQ rider bridges those abstractions to the KubeMQ broker over gRPC on port 50000, where the broker dispatches to the four native subsystems that back the pattern mapping.

The application talks to MassTransit abstractions; the rider bridges them to the four native KubeMQ subsystems over gRPC.

Running a Broker Locally

MassTransit.KubeMQ connects to the KubeMQ broker over gRPC on port 50000. Port 9090 is the shared HTTP server that hosts the REST, CloudEvents, MCP, and A2A connectors — it is not required by the MassTransit transport, but the standard image exposes both.

docker run -d \  --name kubemq \  -p 50000:50000 \  -p 9090:9090 \  -e KUBEMQ_TOKEN=YOUR_LICENSE_KEY \  europe-docker.pkg.dev/kubemq/images/kubemq:next

Point the transport at it with cfg.Host("localhost", 50000).

Was this page helpful?

On this page