Back to Blog
.NET CoreMicroservicesAzureEvent-Driven

Event-Driven Microservices with .NET Core & Azure Service Bus

10 Apr 202511 min read·Venkatraman Nagarajan

The Problem with Synchronous Microservices

Direct HTTP calls between services create tight coupling — if the inventory service is down, the order service fails too. Event-driven architecture breaks this dependency. Services publish events; subscribers react asynchronously in their own time.

Azure Service Bus: Topics vs Queues

Use Queues when only one consumer needs a message (e.g., send one email). Use Topics + Subscriptions when multiple services need the same event (e.g., OrderPlaced triggers both inventory deduction and billing).

Publishing Events from .NET

// Register in Program.cs
builder.Services.AddSingleton<ServiceBusClient>(sp =>
    new ServiceBusClient(builder.Configuration["AzureServiceBus:ConnectionString"]));

// Publisher
public class OrderEventPublisher
{
    private readonly ServiceBusSender _sender;

    public OrderEventPublisher(ServiceBusClient client)
        => _sender = client.CreateSender("order-events");

    public async Task PublishOrderPlacedAsync(OrderPlacedEvent evt)
    {
        var body = JsonSerializer.Serialize(evt);
        var message = new ServiceBusMessage(body)
        {
            ContentType = "application/json",
            Subject = nameof(OrderPlacedEvent),
            MessageId = evt.OrderId.ToString(),
        };
        await _sender.SendMessageAsync(message);
    }
}

Consuming with Background Service

public class InventoryWorker : BackgroundService
{
    private readonly ServiceBusProcessor _processor;

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        _processor.ProcessMessageAsync += HandleMessageAsync;
        _processor.ProcessErrorAsync  += HandleErrorAsync;
        await _processor.StartProcessingAsync(ct);
    }

    private async Task HandleMessageAsync(ProcessMessageEventArgs args)
    {
        var evt = JsonSerializer.Deserialize<OrderPlacedEvent>(args.Message.Body);
        // deduct stock...
        await args.CompleteMessageAsync(args.Message);
    }
}

The Outbox Pattern — Avoid Lost Events

Never publish directly from within a database transaction — the DB commit might succeed but the publish might fail, leaving your system in an inconsistent state. Instead, write the event to an outbox table in the same transaction, then let a background job publish and delete it.

// Same DbContext transaction
await using var tx = await _db.Database.BeginTransactionAsync();
_db.Orders.Add(order);
_db.OutboxMessages.Add(new OutboxMessage(new OrderPlacedEvent(order)));
await _db.SaveChangesAsync();
await tx.CommitAsync();
// Background worker picks up OutboxMessages and publishes to Service Bus

Dead-Letter Queue Monitoring

Always subscribe to the dead-letter queue in staging. Messages land there after max delivery attempts. Set up an Azure Monitor alert on DLQ depth — it's your early warning for deserialization bugs or downstream service failures.

Found this useful? Share it:

© 2026 VENKATRAMAN NAGARAJAN. All rights reserved.

Senior Full Stack Engineer · Chennai, India