Skip to main content

Per-Transport Extension Point (Keyed Senders & Receivers)

Most applications never touch a transport directly — you dispatch a message and the pipeline routes it to the configured transport for you. When you do need low-level access to one specific transport (for a custom relay, a health probe, or a bespoke publishing path), each transport registers a keyed ITransportSender and ITransportReceiver you can resolve by name.

Two abstractions, two audiences

Excalibur separates the transport surface into a framework-wiring layer and a consumer-facing seam:

AbstractionAudienceRole
ITransportAdapter, IMessageBusFramework wiringThe routing/lifecycle abstractions the dispatch pipeline uses to move messages. You configure them through AddDispatch(...) and routing rules; you rarely resolve them yourself.
ITransportSender, ITransportReceiverConsumer seamThe per-transport send/receive contract. Registered keyed by transport name, so you can resolve exactly the one you want.

Adding a transport with a name registers both keyed services:

services.AddKafkaTransport("orders", kafka =>
kafka.BootstrapServers("localhost:9092"));

AddKafkaTransport("orders", …) registers a keyed ITransportSender and a keyed ITransportReceiver under the key "orders". Every transport package follows the same convention — the registration is guaranteed family-wide by a cross-transport DI regression test.

The consumer seam

Resolve a named transport's sender with GetRequiredKeyedService<ITransportSender>(name):

using Excalibur.Dispatch.Transport;
using Microsoft.Extensions.DependencyInjection;

// The key is the name you passed to AddKafkaTransport("orders", …).
var sender = provider.GetRequiredKeyedService<ITransportSender>("orders");

var result = await sender.SendAsync(
new TransportMessage(/* … */),
cancellationToken);

ITransportSender exposes:

  • Task<SendResult> SendAsync(TransportMessage message, CancellationToken cancellationToken)
  • Task<BatchSendResult> SendBatchAsync(IReadOnlyList<TransportMessage> messages, CancellationToken cancellationToken)
  • Task FlushAsync(CancellationToken cancellationToken)

The receiver side is symmetric — resolve ITransportReceiver by the same key:

var receiver = provider.GetRequiredKeyedService<ITransportReceiver>("orders");

When to use it

Reach for the keyed seam only when you need transport-specific control that the pipeline doesn't already give you:

  • A custom relay or bridge between two named transports.
  • A liveness/health probe that flushes or pings a specific broker.
  • A one-off publishing path outside the normal routing rules.

For ordinary send/receive, prefer dispatching through the pipeline and configuring message routing — it keeps your handlers transport-agnostic and lets you change brokers without touching call sites.