One Service, Nine Queues
Some work shouldn't happen on the request that triggers it. Deleting a camera's images, caching a panorama, redeploying an orchestration, processing an AI detection — these are fire-and-forget jobs handed to a queue for something else to pick up later. There are nine such queues, and rather than nine near-identical service classes, the platform writes one and registers it nine times.
The one idea to hold on to
QueueService is a single class that implements IQueueService plus nine marker
interfaces — one per queue — and DI binds each interface to an instance carrying that queue's
name. The queue name is a constructor argument, not a subclass:
public class QueueService(QueueServiceClient queueServiceClient, string queueName)
: IQueueService, ISingleImageMetadataDeleteQueue, ICacheSingleImageQueue, /* …9 total */
// each registration injects a different name:
services.AddScoped<ISingleImageMetadataDeleteQueue>(p =>
new QueueService(p.GetRequiredService<QueueServiceClient>(), opts.DeleteSingleImageQueue));
services.AddScoped<ICacheSingleImageQueue>(p =>
new QueueService(p.GetRequiredService<QueueServiceClient>(), opts.CacheSingleImageQueue));
Inject ICacheSingleImageQueue and you get an instance wired to the cache queue; inject
ISingleImageMetadataDeleteQueue and you get one wired to the delete queue. Same code, no
shared state, and the type you ask for is the queue you talk to — a compile-time guarantee
that you can't accidentally send a delete message to the cache queue.
Sending a message
The API is two methods: SendMessageAsync(string) and
DeleteMessageAsync(messageId, popReceipt). Sending does one transformation you have to know
about:
var bytes = Encoding.UTF8.GetBytes(message);
await _queueClient.SendMessageAsync(Convert.ToBase64String(bytes));
SendMessageAsync UTF-8 encodes your string and then Base64-encodes it before enqueuing.
The consumer must Base64-decode before it sees your JSON. If a queue trigger is receiving
gibberish, this is why — either it's not decoding, or it's decoding something that was never
encoded. The caller serializes to JSON; QueueService owns the Base64 layer.
This package only provides the sending side. The consumers — the Azure Function triggers and background services that dequeue, decode, and act — live in the individual projects (ImageAcquisition, Notification, and so on), not here.
Wrapping up
Asynchronous work flows through nine queues served by one QueueService class, bound nine
times behind marker interfaces so the type you inject is the queue you send to. Messages are
Base64-encoded on send, and the receiving half lives in the consuming apps. That's the last of
the storage backends — next, an AI data domain that spans several of them.
Next, how smoke and fire detections are stored and read back as one graph: Condor AI Detections.