Transports¶
A transport determines how a message physically travels from sender to processor. KnightBus' defining characteristic is that the transport is chosen per message type, by the interface the message implements, and that any number of transports can be active in the same host.
services
.UseServiceBus(config => config.ConnectionString = serviceBusConnection)
.UseTransport<ServiceBusTransport>()
.UseRedis(config => config.ConnectionString = redisConnection)
.UseTransport<RedisTransport>()
.RegisterProcessors();
With that in place, an IServiceBusCommand goes to Service Bus and an IRedisCommand goes to Redis,
with no routing configuration anywhere.
Feature matrix¶
What the transport itself can do:
| Service Bus | Storage Queues | PostgreSQL | Redis | NATS | |
|---|---|---|---|---|---|
| Commands | ✅ | ✅ | ✅ | ✅ | ✅ |
| Events (pub/sub) | ✅ | — | ✅ | ✅ | ✅ |
| Request/response | — | — | — | — | ✅ |
| Streaming responses | — | — | — | — | ✅ |
| Deferred delivery | ✅ | ✅ | ✅ | — | — |
| Cancel deferred message | ✅ | — | — | — | — |
| Dead letter queue | ✅ | ✅ | ✅ | ✅ | — |
| Management API | ✅ | ✅ | ✅ | ✅ | — |
| Message lock extension | — | ✅ | — | — | — |
| Carries attachments | ✅ | ✅ | ✅ | ✅ | ✅ |
| Default serializer | Newtonsoft | Newtonsoft | System.Text.Json | Newtonsoft | Newtonsoft |
What each package ships — a different question, answered by a different table:
| Service Bus | Storage Queues | PostgreSQL | Redis | NATS | SQL Server | |
|---|---|---|---|---|---|---|
| Attachment provider | — | ✅ Blob | — | — | — | — |
| Saga store | — | ✅ Blob | ✅ | ✅ | — | ✅ |
| Singleton lock manager | — | ✅ Blob lease | — | — | — | — |
The second table is not a restriction¶
Nothing in the second table belongs to the transport it ships beside. Attachments, sagas and
singleton locks are host-wide features whose middleware lives in KnightBus.Core, and they are
configured once per host, not per transport. A UseXxxAttachments() or UseXxxSagaStore() call
names the storage backing the feature; it says nothing about which transport your messages travel
over. KnightBus.SqlServer is the case that makes this obvious — a saga store with no transport at
all.
So all of these are ordinary arrangements, not workarounds:
- messages over NATS, attachments in Blob Storage, saga state in PostgreSQL;
- messages over Service Bus with singleton processors, coordinated by Blob leases — the only lock
implementation KnightBus ships, and the reason
KnightBus.Azure.Storageappears in applications that use no Azure queue at all; - messages over Redis with attachments in Blob Storage, the only attachment provider KnightBus ships.
Pick each row of the second table on its own merits — cost, durability, what you already operate — and mix freely. The differences that matter are between the implementations, not the transports: see sagas for the important one, that only the Blob and Redis stores detect concurrent writes.
Where the transport does constrain you¶
One row in the first table is a real coupling.
Message lock extension is about the receive side. ExtendMessageLockDurationMiddleware is a core
middleware you may register on any host, but it can only renew a lock the transport lets it renew:
the message state handler has to implement IMessageLockHandler<T>, which today only Storage Queues
does. Elsewhere the middleware is inert — and on PostgreSQL, implementing IExtendMessageLockTimeout
is worse than inert, see
extending the lock.
Choosing one¶
- Azure Service Bus — the most capable option. Native dead-lettering, topics with subscriptions, deferred and cancellable messages. Choose it when you want the broker to do the work and you are already on Azure.
- Azure Storage Queues — cheap, simple and durable, and the only transport that can extend a message lock mid-processing. Good for long-running low-throughput work. Commands only.
- PostgreSQL — no extra infrastructure if you already run Postgres, and transactional with your own data. Polling-based, so latency is bounded by the polling delay.
- Redis — the highest-throughput option, using a circular-list pattern so messages are not lost in transit. Choose it for high-volume work where a few seconds of durability risk is acceptable.
- NATS — the only transport with request/response and streaming responses. Choose it when you need a reply, not just a hand-off.
Common shape¶
Every transport follows the same registration pattern — three calls, of which the first is named after the transport:
services
.UseXxx(config => config.ConnectionString = connectionString) // client + configuration
.UseTransport<XxxTransport>() // start listeners
.RegisterProcessors(); // find handlers
UseXxx registers the configuration and the bus client. UseTransport<T> starts the
listeners. Forgetting the second one is the most common setup mistake — the client works, messages are
sent, and nothing is ever consumed.
Queue and topic entities are created automatically on first use by every transport.
Adding your own¶
The transport SPI is ITransport plus ITransportChannelFactory, and for polling transports
GenericMessagePump handles the prefetch and concurrency arithmetic. See
contributing.