<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged masstransit</title>
<link>https://coderlegion.com/tag/masstransit</link>
<description>Powered by Question2Answer</description>
<item>
<title>MassTransit in ASP.NET Core: A Practical Guide to Event-Driven .NET</title>
<link>https://coderlegion.com/5749/masstransit-in-asp-net-core-a-practical-guide-to-event-driven-net</link>
<description>&lt;p&gt;MassTransit in ASP.NET Core: A Practical Guide to Event-Driven .NET&lt;br&gt;
Intro&lt;/p&gt;
&lt;p&gt;Event-driven architectures help teams decouple services, scale independently, and handle failures gracefully. MassTransit is a mature, open-source library that makes message-based workflows in .NET straightforward. This guide shows a minimal but production-ready setup in ASP.NET Core with RabbitMQ.&lt;/p&gt;
&lt;p&gt;1) Install packages&lt;/p&gt;
&lt;p&gt;From your Web API project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet add package MassTransit
dotnet add package MassTransit.RabbitMQ
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(For Azure Service Bus use MassTransit.Azure.ServiceBus.Core instead.)&lt;/p&gt;
&lt;p&gt;2) Define a message contract&lt;/p&gt;
&lt;p&gt;Contracts should be versionable and live in a shared project.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public interface SubmitOrder
{
    Guid OrderId { get; }
    string CustomerId { get; }
    DateTime Timestamp { get; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3) Create a consumer&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using MassTransit;

public sealed class SubmitOrderConsumer : IConsumer&amp;lt;SubmitOrder&amp;gt;
{
    public async Task Consume(ConsumeContext&amp;lt;SubmitOrder&amp;gt; context)
    {
        var msg = context.Message;
        // Your domain logic here (idempotent!)
        Console.WriteLine($&quot;Received SubmitOrder {msg.OrderId} for {msg.CustomerId}&quot;);
        await Task.CompletedTask;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4) Configure MassTransit in Program.cs (.NET 9 minimal hosting)&lt;/p&gt;
&lt;p&gt;RabbitMQ example with retries and health checks.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using MassTransit;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMassTransit(cfg =&amp;gt;
{
    cfg.SetKebabCaseEndpointNameFormatter();

    cfg.AddConsumer&amp;lt;SubmitOrderConsumer&amp;gt;(c =&amp;gt;
    {
        // optional: configure consumer-level retry, etc.
    });

    cfg.UsingRabbitMq((context, bus) =&amp;gt;
    {
        bus.Host(&quot;rabbitmq&quot;, h =&amp;gt;
        {
            h.Username(&quot;guest&quot;);
            h.Password(&quot;guest&quot;);
        });

        bus.ReceiveEndpoint(&quot;submit-order-queue&quot;, e =&amp;gt;
        {
            e.ConfigureConsumeTopology = false; 
            e.ConfigureConsumer&amp;lt;SubmitOrderConsumer&amp;gt;(context);

            e.UseMessageRetry(r =&amp;gt; r.Interval(3, TimeSpan.FromSeconds(5)));
            e.PrefetchCount = 16;
            e.ConcurrentMessageLimit = 8;
        });
    });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;builder.Services.AddHealthChecks();&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var app = builder.Build();
app.MapHealthChecks(&quot;/health&quot;);
app.MapGet(&quot;/&quot;, () =&amp;gt; &quot;OK&quot;);
app.Run();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Docker tip&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you run RabbitMQ locally via Docker:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -d --hostname rabbit \
  -p 5672:5672 -p 15672:15672 \
  --name rabbitmq rabbitmq:3-management
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;UI is at &lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:15672&quot;&gt;http://localhost:15672&lt;/a&gt; (guest/guest).&lt;/p&gt;
&lt;p&gt;5) Publish a message (from a Controller or Service)&lt;/p&gt;
&lt;p&gt;Inject IPublishEndpoint for pub/sub or ISendEndpointProvider for point-to-point.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using MassTransit;

public sealed class OrderAppService
{
    private readonly IPublishEndpoint _publish;
    public OrderAppService(IPublishEndpoint publish) =&amp;gt; _publish = publish;

    public Task SubmitAsync(Guid orderId, string customerId)
        =&amp;gt; _publish.Publish&amp;lt;SubmitOrder&amp;gt;(new
        {
            OrderId = orderId,
            CustomerId = customerId,
            Timestamp = DateTime.UtcNow
        });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or via an endpoint (send to a specific queue):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public sealed class OrderSender
{
    private readonly ISendEndpointProvider _send;
    public OrderSender(ISendEndpointProvider send) =&amp;gt; _send = send;

    public async Task SendAsync(Guid orderId, string customerId)
    {
        var endpoint = await _send.GetSendEndpoint(new Uri(&quot;queue:submit-order-queue&quot;));
        await endpoint.Send&amp;lt;SubmitOrder&amp;gt;(new { OrderId = orderId, CustomerId = customerId, Timestamp = DateTime.UtcNow });
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;6) Error handling, retries, and observability&lt;/p&gt;
&lt;p&gt;Retries: use UseMessageRetry on endpoints or the bus. Prefer bounded retries with intervals or exponential backoff.&lt;/p&gt;
&lt;p&gt;Poison messages: failed messages after retries land in _error queues automatically.&lt;/p&gt;
&lt;p&gt;Health checks: expose /health and rely on container orchestration to restart unhealthy pods.&lt;/p&gt;
&lt;p&gt;Idempotency: make consumers safe to reprocess (e.g., check a processed table or use dedup keys).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7) (Optional) Outbox &amp;amp; transactions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you publish events within a DB transaction, consider an outbox pattern (MassTransit integrates with EFCore Outbox) to avoid dual-write issues and ensure at-least-once delivery without duplicates.&lt;/p&gt;
&lt;p&gt;8) Azure Service Bus variant (quick sketch)&lt;/p&gt;
&lt;p&gt;Swap the transport:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;builder.Services.AddMassTransit(cfg =&amp;gt;
{
    cfg.AddConsumer&amp;lt;SubmitOrderConsumer&amp;gt;();

    cfg.UsingAzureServiceBus((context, bus) =&amp;gt;
    {
        bus.Host(builder.Configuration[&quot;ASB_CONNECTION&quot;]!);

        bus.SubscriptionEndpoint&amp;lt;SubmitOrder&amp;gt;(&quot;submit-order-sub&quot;, e =&amp;gt;
        {
            e.ConfigureConsumer&amp;lt;SubmitOrderConsumer&amp;gt;(context);
            e.MaxConcurrentCalls = 8;
        });
    });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Conclusion&lt;/p&gt;
&lt;p&gt;MassTransit keeps the happy path simple while giving you the tools for serious systems: retries, sagas, scheduling, observability, and transport flexibility. Start minimal, add policies as you learn your failure modes, and keep consumers idempotent.&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/5749/masstransit-in-asp-net-core-a-practical-guide-to-event-driven-net</guid>
<pubDate>Mon, 29 Sep 2025 09:25:53 +0000</pubDate>
</item>
<item>
<title>Building a Pub/Sub System in .NET: MassTransit, Reactive Extensions, and BlockingCollection</title>
<link>https://coderlegion.com/5021/building-pub-sub-system-in-net-masstransit-reactive-extensions-and-blockingcollection</link>
<description>&lt;p&gt;Modern applications often need to broadcast events across multiple services.&lt;br&gt;
Think of an order being placed in an e-commerce system. The order service publishes an event, and multiple subscribers react independently:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;the billing service charges the customer,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the email service sends a receipt,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the analytics service tracks the order.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is the essence of the Publish/Subscribe (Pub/Sub) pattern.&lt;/p&gt;
&lt;p&gt;In this post we’ll explore what it is, why it matters, and three implementations in .NET:&lt;/p&gt;
&lt;p&gt;MassTransit with RabbitMQ (for distributed systems)&lt;/p&gt;
&lt;p&gt;Reactive Extensions (Rx) (for in-memory event streams)&lt;/p&gt;
&lt;p&gt;BlockingCollection (for producer/consumer pipelines)&lt;/p&gt;
&lt;p&gt; What is the Pub/Sub Pattern&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Pub/Sub pattern decouples senders from receivers.&lt;/li&gt;
&lt;li&gt;The Publisher creates a message (e.g., OrderSubmitted) and sends it&lt;br&gt;
to a broker or channel.&lt;/li&gt;
&lt;li&gt;The Broker delivers that message to all interested Subscribers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each Subscriber receives a copy of the message and processes it independently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Benefits&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Decoupling — publishers do not know subscribers&lt;/li&gt;
&lt;li&gt;Scalability — multiple subscribers can process in parallel&lt;/li&gt;
&lt;li&gt;Flexibility — add or remove subscribers without changing the&lt;br&gt;
publisher&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why it matters&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This pattern has been around for decades in middleware like IBM MQ, JMS, RabbitMQ, and Kafka.&lt;br&gt;
It is fundamental in:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Event-driven microservices&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real-time systems (chat apps, trading platforms)&lt;/li&gt;
&lt;li&gt;Notification and alerting systems&lt;/li&gt;
&lt;li&gt;Data pipelines and analytics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;IoT device communication&lt;/p&gt;
&lt;p&gt;At its core, Pub/Sub enables loose coupling and asynchronous communication.&lt;/p&gt;
&lt;p&gt;⚙️ Hosted Services in ASP.NET Core&lt;/p&gt;
&lt;p&gt;A hosted service is a background task that runs with your app host.&lt;br&gt;
In .NET you typically inherit from BackgroundService and implement ExecuteAsync.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class SampleWorker : BackgroundService
{
    private readonly ILogger&amp;lt;SampleWorker&amp;gt; _log;
    public SampleWorker(ILogger&amp;lt;SampleWorker&amp;gt; log) =&amp;gt; _log = log;

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        _log.LogInformation(&quot;Worker started&quot;);
        while (!ct.IsCancellationRequested)
        {
            // do work here
            await Task.Delay(TimeSpan.FromSeconds(5), ct);
        }
        _log.LogInformation(&quot;Worker stopping&quot;);
    }
}

// Register in Program.cs
builder.Services.AddHostedService&amp;lt;SampleWorker&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;a rel=&quot;nofollow&quot; href=&quot;https://masstransit.io/&quot;&gt;MassTransit&lt;/a&gt; demo below, a PublisherService is implemented as a hosted service to periodically publish messages.&lt;/p&gt;
&lt;p&gt;1️⃣ Pub/Sub with MassTransit and RabbitMQ&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;appsettings.json
{
  &quot;RabbitMq&quot;: { &quot;Host&quot;: &quot;localhost&quot;, &quot;Username&quot;: &quot;guest&quot;, &quot;Password&quot;: &quot;guest&quot; },
  &quot;PublishIntervalSeconds&quot;: 5
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OrderSubmitted.cs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;namespace MassTransitDemo;

public record OrderSubmitted(Guid OrderId, string CustomerEmail, decimal Total);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OrderSubmittedConsumer.cs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using MassTransit;
using Microsoft.Extensions.Logging;

namespace MassTransitDemo;

public class OrderSubmittedConsumer(ILogger&amp;lt;OrderSubmittedConsumer&amp;gt; logger) : IConsumer&amp;lt;OrderSubmitted&amp;gt;
{
    public Task Consume(ConsumeContext&amp;lt;OrderSubmitted&amp;gt; context)
    {
        var m = context.Message;
        logger.LogInformation(&quot;Consumed OrderSubmitted, OrderId {OrderId}, Email {Email}, Total {Total}&quot;,
            m.OrderId, m.CustomerEmail, m.Total);
        return Task.CompletedTask;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PublisherService.cs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using MassTransit;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace MassTransitDemo;

public class PublisherOptions
{
    public int PublishIntervalSeconds { get; set; } = 5;
}

public class PublisherService(
    ILogger&amp;lt;PublisherService&amp;gt; logger,
    IPublishEndpoint publishEndpoint,
    IOptions&amp;lt;PublisherOptions&amp;gt; options) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var delay = TimeSpan.FromSeconds(options.Value.PublishIntervalSeconds);
        logger.LogInformation(&quot;Publisher started, interval {Seconds}s&quot;, options.Value.PublishIntervalSeconds);

        while (!stoppingToken.IsCancellationRequested)
        {
            var msg = new OrderSubmitted(Guid.NewGuid(), &quot;*Emails are not allowed*&quot;, Random.Shared.Next(50, 300));
            await publishEndpoint.Publish(msg, stoppingToken);
            logger.LogInformation(&quot;Published OrderSubmitted, OrderId {OrderId}&quot;, msg.OrderId);
            await Task.Delay(delay, stoppingToken);
        }

        logger.LogInformation(&quot;Publisher stopping&quot;);
    }
}

Program.cs

    using MassTransit;
    using MassTransitDemo;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    
    var builder = Host.CreateApplicationBuilder(args);
    
    builder.Configuration.AddJsonFile(&quot;appsettings.json&quot;, optional: true, reloadOnChange: true);
    builder.Services.Configure&amp;lt;PublisherOptions&amp;gt;(builder.Configuration);
    
    builder.Services.AddMassTransit(mt =&amp;gt;
    {
        mt.AddConsumer&amp;lt;OrderSubmittedConsumer&amp;gt;();
    
        mt.UsingRabbitMq((ctx, cfg) =&amp;gt;
        {
            var section = builder.Configuration.GetSection(&quot;RabbitMq&quot;);
            cfg.Host(section[&quot;Host&quot;], h =&amp;gt;
            {
                h.Username(section[&quot;Username&quot;]);
                h.Password(section[&quot;Password&quot;]);
            });
    
            cfg.ReceiveEndpoint(&quot;masstransitdemo-order-submitted&quot;, e =&amp;gt;
            {
                e.ConfigureConsumer&amp;lt;OrderSubmittedConsumer&amp;gt;(ctx);
            });
    
            cfg.UseMessageRetry(r =&amp;gt; r.Interval(3, TimeSpan.FromSeconds(5)));
            cfg.UseInMemoryOutbox();
        });
    });
    
    builder.Services.AddHostedService&amp;lt;PublisherService&amp;gt;();
    
    builder.Logging.ClearProviders();
    builder.Logging.AddSimpleConsole(o =&amp;gt;
    {
        o.SingleLine = true;
        o.TimestampFormat = &quot;HH:mm:ss &quot;;
    });
    
    await builder.Build().RunAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run RabbitMQ&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -it --rm -p 5672:5672 -p 15672:15672 rabbitmq:3-management
# UI at http://localhost:15672  user guest  pass guest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MassTransit is a distributed application framework for message brokers like RabbitMQ and Azure Service Bus. It takes care of serialization, consumer lifecycle, retries, and endpoint setup.&lt;/p&gt;
&lt;p&gt;2️⃣ Pub/Sub with Reactive Extensions (Rx)&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/dotnet/reactive&quot;&gt;Reactive Extensions (Rx)&lt;/a&gt;&lt;br&gt;
 is a library for composing asynchronous and event based programs with observable sequences.&lt;/p&gt;
&lt;p&gt;Uses IObservable (event source) and IObserver (subscriber)&lt;/p&gt;
&lt;p&gt;Provides LINQ-style operators (Where, Select, Buffer) for event streams&lt;/p&gt;
&lt;p&gt;Perfect for in-memory event pipelines and UI scenarios&lt;/p&gt;
&lt;p&gt;⚠️ Limitation: Rx is in-memory only. Events vanish when the app stops.&lt;/p&gt;
&lt;p&gt;Program.cs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using System;
using System.Reactive.Linq;
using System.Reactive.Subjects;

public record OrderSubmitted(Guid OrderId, string CustomerEmail, decimal Total);

class Program
{
    static void Main()
    {
        var bus = new Subject&amp;lt;OrderSubmitted&amp;gt;();

        // Subscriber A
        var subA = bus.Subscribe(order =&amp;gt;
            Console.WriteLine($&quot;[Email] Send receipt to {order.CustomerEmail}&quot;));

        // Subscriber B with filter and transform
        var subB = bus
            .Where(o =&amp;gt; o.Total &amp;gt;= 100)
            .Select(o =&amp;gt; new { o.OrderId, Vat = o.Total * 0.24m })
            .Subscribe(x =&amp;gt;
                Console.WriteLine($&quot;[Analytics] Order {x.OrderId}, VAT {x.Vat:F2}&quot;));

        // Publish a few messages
        bus.OnNext(new OrderSubmitted(Guid.NewGuid(), &quot;*Emails are not allowed*&quot;, 79.90m));
        bus.OnNext(new OrderSubmitted(Guid.NewGuid(), &quot;*Emails are not allowed*&quot;, 149.00m));
        bus.OnNext(new OrderSubmitted(Guid.NewGuid(), &quot;*Emails are not allowed*&quot;, 220.00m));

        bus.OnCompleted();
        subA.Dispose();
        subB.Dispose();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3️⃣ Pub/Sub with BlockingCollection&lt;/p&gt;
&lt;p&gt;BlockingCollection is a thread safe producer and consumer collection in .NET.&lt;/p&gt;
&lt;p&gt;Supports blocking (consumers wait until data is available)&lt;/p&gt;
&lt;p&gt;Supports bounding (limit capacity to avoid flooding)&lt;/p&gt;
&lt;p&gt;Useful for background workers, pipelines, or batch processors&lt;/p&gt;
&lt;p&gt;Program.cs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using System.Collections.Concurrent;

public record OrderSubmitted(Guid OrderId, string CustomerEmail, decimal Total);

class Program
{
    static async Task Main()
    {
        using var queue = new BlockingCollection&amp;lt;OrderSubmitted&amp;gt;(boundedCapacity: 100);

        // Subscriber A
        var emailTask = Task.Run(() =&amp;gt;
        {
            foreach (var order in queue.GetConsumingEnumerable())
                Console.WriteLine($&quot;[Email] Send receipt to {order.CustomerEmail}&quot;);
        });

        // Subscriber B
        var analyticsTask = Task.Run(() =&amp;gt;
        {
            foreach (var order in queue.GetConsumingEnumerable())
                Console.WriteLine($&quot;[Analytics] Track order {order.OrderId}, total {order.Total}&quot;);
        });

        // Publisher
        foreach (var price in new[] { 49.50m, 129.00m, 250.00m, 15.00m })
        {
            queue.Add(new OrderSubmitted(Guid.NewGuid(), $&quot;user{price}@example.com&quot;, price));
        }

        queue.CompleteAdding();
        await Task.WhenAll(emailTask, analyticsTask);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;⚠️ Limitation: Only works inside a single process. No distribution.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=250057942209870061&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt; References&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://masstransit.io/&quot;&gt;MassTransit Documentation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MassTransit/MassTransit&quot;&gt;MassTransit GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rabbitmq.com/tutorials/tutorial-one-dotnet.html&quot;&gt;RabbitMQ Tutorials&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/core/extensions/workers&quot;&gt;Microsoft Docs: Worker Services&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber&quot;&gt;Microsoft Docs: Publisher-Subscriber Pattern&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/dotnet/reactive&quot;&gt;System.Reactive GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/standard/collections/thread-safe/blockingcollection-overview&quot;&gt;BlockingCollection Overview&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;✅ With these three approaches, you now have the full spectrum of Pub/Sub in .NET:&lt;/p&gt;
&lt;p&gt;Lightweight in-memory streams with Rx and BlockingCollection&lt;/p&gt;
&lt;p&gt;Robust distributed messaging with MassTransit and RabbitMQ&lt;/p&gt;
&lt;p&gt;The Pub/Sub pattern has lasted decades because it makes systems more modular, more scalable, and more resilient to change.&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/5021/building-pub-sub-system-in-net-masstransit-reactive-extensions-and-blockingcollection</guid>
<pubDate>Tue, 26 Aug 2025 13:57:29 +0000</pubDate>
</item>
<item>
<title>Building an Order Processing Saga with MassTransit</title>
<link>https://coderlegion.com/4621/building-an-order-processing-saga-with-masstransit</link>
<description>&lt;p&gt;MassTransit has been around since 2007 and has matured into one of the most stable and feature-rich open-source .NET messaging frameworks.&lt;/p&gt;
&lt;p&gt;It acts like a factory for message brokers, meaning you can switch between providers such as RabbitMQ, Azure Service Bus, ActiveMQ, Amazon SQS, and others with minimal configuration changes.&lt;/p&gt;
&lt;p&gt;This makes it ideal for enterprise-grade distributed systems where you want flexibility without vendor lock-in.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why MassTransit?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Stability: Over a decade of continuous development and production use.&lt;/p&gt;
&lt;p&gt;Multi-Broker Support: Easily swap between Azure Service Bus, RabbitMQ, Amazon SQS, etc., without rewriting business logic.&lt;/p&gt;
&lt;p&gt;Developer Friendly: Built for .NET developers with excellent documentation and integration with DI, EF Core, and ASP.NET Core.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Advanced Features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Outbox pattern&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Request/Response messaging&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Retry and circuit-breaker policies&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sagas and State Machines&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Message scheduling&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;One of the Coolest Features: The State Machine&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A State Machine allows you to model complex workflows where messages arrive in any order, but your application processes them in a controlled, state-aware sequence.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=8178334018060771430&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;For example, in this Order Processing Saga:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;An order is submitted.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The saga waits for stock confirmation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If stock is reserved, the saga waits for payment authorization.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once payment is authorized, the order is marked as completed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If any step fails, the saga can transition to a failed state and trigger compensating actions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach eliminates spaghetti code for multi-step processes and makes workflows easy to visualize and maintain.&lt;/p&gt;
&lt;p&gt;Technology Stack Used in This Project&lt;br&gt;
.NET 9&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;MassTransit (with Saga State Machine)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RabbitMQ (message broker)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PostgreSQL (saga persistence)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Entity Framework Core (database access)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker (local RabbitMQ/PostgreSQL setup)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Source Code&lt;br&gt;
Full working implementation available here:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/OutboxSagaDemo&quot;&gt; GitHub – OutboxSagaDemo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;MassTransit Documentation – &lt;a rel=&quot;nofollow&quot; href=&quot;https://masstransit.io/&quot;&gt;https://masstransit.io/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;MassTransit GitHub Repository – &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MassTransit/MassTransit&quot;&gt;https://github.com/MassTransit/MassTransit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;MassTransit Saga State Machine Guide – &lt;a rel=&quot;nofollow&quot; href=&quot;https://masstransit.io/documentation/patterns/saga&quot;&gt;https://masstransit.io/documentation/patterns/saga&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;RabbitMQ Documentation – &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rabbitmq.com/documentation.html&quot;&gt;https://www.rabbitmq.com/documentation.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;PostgreSQL Documentation – &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.postgresql.org/docs/&quot;&gt;https://www.postgresql.org/docs/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Entity Framework Core Docs – &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/&quot;&gt;https://learn.microsoft.com/en-us/ef/core/&lt;/a&gt;&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/4621/building-an-order-processing-saga-with-masstransit</guid>
<pubDate>Tue, 12 Aug 2025 09:22:53 +0000</pubDate>
</item>
</channel>
</rss>