<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dinesh Dunukedeniya</title>
    <description>The latest articles on DEV Community by Dinesh Dunukedeniya (@dinesh_dunukedeniya_539a3).</description>
    <link>https://dev.to/dinesh_dunukedeniya_539a3</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3195120%2F8ecbdcaf-c56f-4c3d-82e8-f587bd1dc245.png</url>
      <title>DEV Community: Dinesh Dunukedeniya</title>
      <link>https://dev.to/dinesh_dunukedeniya_539a3</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dinesh_dunukedeniya_539a3"/>
    <language>en</language>
    <item>
      <title>Event/Message Versioning in : The Complete Practical Guide</title>
      <dc:creator>Dinesh Dunukedeniya</dc:creator>
      <pubDate>Fri, 13 Feb 2026 06:56:47 +0000</pubDate>
      <link>https://dev.to/dinesh_dunukedeniya_539a3/eventmessage-versioning-in-the-complete-practical-guide-4ka0</link>
      <guid>https://dev.to/dinesh_dunukedeniya_539a3/eventmessage-versioning-in-the-complete-practical-guide-4ka0</guid>
      <description>&lt;p&gt;Event‑driven systems evolve. Your business changes, your data changes, and sooner or later your events must change too. The challenge is simple to describe but hard to get right: how do you evolve events without breaking every consumer in your system?&lt;/p&gt;

&lt;p&gt;This guide walks through the practical patterns, trade‑offs, and real‑world techniques for versioning events in Kafka so your system can grow safely.&lt;/p&gt;




&lt;p&gt;🧩 Why Event Versioning Matters&lt;/p&gt;

&lt;p&gt;In a microservice architecture, events become long‑lived contracts. Once published, they may be consumed by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple services
&lt;/li&gt;
&lt;li&gt;Different teams
&lt;/li&gt;
&lt;li&gt;Different versions of the same service
&lt;/li&gt;
&lt;li&gt;External partners
&lt;/li&gt;
&lt;li&gt;Analytics pipelines
&lt;/li&gt;
&lt;li&gt;Machine learning models
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A breaking change in an event can silently break all of them.&lt;/p&gt;

&lt;p&gt;This is why event versioning is not a technical detail — it’s a core architectural responsibility.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Golden Rule: Never Break Existing Consumers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once an event schema is in production, you must assume:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Someone depends on every field
&lt;/li&gt;
&lt;li&gt;Someone depends on the event shape
&lt;/li&gt;
&lt;li&gt;Someone depends on the semantics
&lt;/li&gt;
&lt;li&gt;Someone depends on the ordering
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why event versioning is about compatibility, not just schema changes.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Types of Compatibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are three main compatibility modes you need to understand.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Forward&lt;/td&gt;
&lt;td&gt;Old consumers can read new messages&lt;/td&gt;
&lt;td&gt;Upgrade consumers later&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backward&lt;/td&gt;
&lt;td&gt;New consumers can read old messages&lt;/td&gt;
&lt;td&gt;Upgrade producers later&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Both directions work&lt;/td&gt;
&lt;td&gt;Safest for large systems&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;strong&gt;Safe Changes (Non‑Breaking)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These changes are generally safe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding a new optional field
&lt;/li&gt;
&lt;li&gt;Adding a new event type
&lt;/li&gt;
&lt;li&gt;Adding a new enum value (if consumers ignore unknowns)
&lt;/li&gt;
&lt;li&gt;Increasing field length
&lt;/li&gt;
&lt;li&gt;Adding metadata fields
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These allow your system to evolve without breaking anyone.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Breaking Changes (Avoid These)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These changes will break consumers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Renaming a field
&lt;/li&gt;
&lt;li&gt;Removing a field
&lt;/li&gt;
&lt;li&gt;Changing field type
&lt;/li&gt;
&lt;li&gt;Changing event meaning
&lt;/li&gt;
&lt;li&gt;Changing event order
&lt;/li&gt;
&lt;li&gt;Changing required fields
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you must do these, you need a new version.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Versioning Strategies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are three practical ways to version events in Kafka.&lt;/p&gt;




&lt;p&gt;Strategy 1: Version Inside the Event &lt;/p&gt;

&lt;p&gt;Add a version field:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "eventType": "OrderPlaced",&lt;br&gt;
  "version": 2,&lt;br&gt;
  "orderId": "123",&lt;br&gt;
  "customerId": "456",&lt;br&gt;
  "items": [...]&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Consumers can switch logic based on version.&lt;/p&gt;

&lt;p&gt;Pros&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single topic
&lt;/li&gt;
&lt;li&gt;Easy to evolve
&lt;/li&gt;
&lt;li&gt;Consumers choose when to upgrade
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consumers must handle multiple versions
&lt;/li&gt;
&lt;li&gt;Logic can get messy over time
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Strategy 2: Version in the Topic Name&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;order-placed-v1&lt;/li&gt;
&lt;li&gt;order-placed-v2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pros&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean separation
&lt;/li&gt;
&lt;li&gt;Consumers subscribe to the version they support
&lt;/li&gt;
&lt;li&gt;No branching logic
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More topics to manage
&lt;/li&gt;
&lt;li&gt;Harder to migrate consumers
&lt;/li&gt;
&lt;li&gt;Analytics pipelines must merge topics
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use this when the new version is fundamentally different.&lt;/p&gt;




&lt;p&gt;Strategy 3: Schema Registry Versioning&lt;/p&gt;

&lt;p&gt;If you use Confluent Schema Registry or similar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each schema change creates a new version
&lt;/li&gt;
&lt;li&gt;Compatibility rules enforce safety
&lt;/li&gt;
&lt;li&gt;Consumers automatically get the right schema
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pros&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strong governance
&lt;/li&gt;
&lt;li&gt;Automatic compatibility checks
&lt;/li&gt;
&lt;li&gt;Great for large organisations
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires Avro/Protobuf/JSON Schema
&lt;/li&gt;
&lt;li&gt;More tooling and process overhead
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;How to Evolve Events Safely&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Step 1: Add new fields (non‑breaking)&lt;br&gt;
Make them optional.&lt;/p&gt;

&lt;p&gt;Step 2: Deploy producers with the new schema&lt;br&gt;
They start sending both old + new fields.&lt;/p&gt;

&lt;p&gt;Step 3: Wait until all consumers are upgraded&lt;br&gt;
This may take days or weeks.&lt;/p&gt;

&lt;p&gt;Step 4: Remove old fields (breaking)&lt;br&gt;
Only after confirming no one depends on them.&lt;/p&gt;

&lt;p&gt;This is the expand → migrate → contract pattern.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Handling Semantic Changes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes the shape stays the same but the meaning changes.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;&lt;br&gt;
price used to mean “base price”, now it means “final price”.&lt;/p&gt;

&lt;p&gt;This is a breaking change even if the schema is identical.&lt;/p&gt;

&lt;p&gt;In this case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new event type
&lt;/li&gt;
&lt;li&gt;Or create a new version
&lt;/li&gt;
&lt;li&gt;Or create a new topic
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Never silently change meaning.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Event Versioning Anti‑Patterns&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Avoid these at all costs.&lt;/p&gt;

&lt;p&gt;❌ Reusing event names for new meanings&lt;br&gt;
This causes silent data corruption.&lt;/p&gt;

&lt;p&gt;❌ Removing fields because “no one uses them”&lt;br&gt;
Someone always does.&lt;/p&gt;

&lt;p&gt;❌ Using timestamps as versioning&lt;br&gt;
Unclear and unmaintainable.&lt;/p&gt;

&lt;p&gt;❌ Forcing consumers to upgrade immediately&lt;br&gt;
Breaks autonomy.&lt;/p&gt;

&lt;p&gt;❌ Publishing different shapes of the same event without a version&lt;br&gt;
Impossible to debug.&lt;/p&gt;




&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;Event versioning is not just a technical detail — it’s a contract between teams, services, and the business. When done well, it allows your system to evolve safely for years. When done poorly, it creates silent failures, broken consumers, and painful migrations.&lt;/p&gt;

&lt;p&gt;The key principles are simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don’t break consumers
&lt;/li&gt;
&lt;li&gt;Version intentionally
&lt;/li&gt;
&lt;li&gt;Prefer additive changes
&lt;/li&gt;
&lt;li&gt;Use clear versioning strategies
&lt;/li&gt;
&lt;li&gt;Treat events as long‑lived contracts
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Strong event versioning is a sign of a mature, well‑designed microservice architecture.&lt;/p&gt;

</description>
      <category>kafka</category>
      <category>eventdriven</category>
      <category>microservices</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Boundaries: The Real Foundation of Any Modern Architecture (Microservices or Otherwise)</title>
      <dc:creator>Dinesh Dunukedeniya</dc:creator>
      <pubDate>Sat, 17 Jan 2026 02:02:27 +0000</pubDate>
      <link>https://dev.to/dinesh_dunukedeniya_539a3/boundaries-the-real-foundation-of-any-modern-architecture-microservices-or-otherwise-26a</link>
      <guid>https://dev.to/dinesh_dunukedeniya_539a3/boundaries-the-real-foundation-of-any-modern-architecture-microservices-or-otherwise-26a</guid>
      <description>&lt;p&gt;In 2026, the biggest lesson from the microservices era isn't about going small or fancy tooling, it's about boundaries.&lt;br&gt;
A boundary is straightforward: it's the clear line defining what a service (or module) owns and controls and what it doesn't. Get this right, and whether you're in full microservices, a modular monolith, or something hybrid, things just work better.&lt;/p&gt;

&lt;p&gt;When boundaries are weak or mismatched, you end up with a distributed mess that's harder to run than the monolith you started with.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a strong boundary really owns
&lt;/h2&gt;

&lt;p&gt;In practice, a well-drawn boundary covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A specific business capability&lt;/strong&gt; - the stable piece of the domain the business values (think "Order Fulfillment" not "Order CRUD").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exclusive data ownership&lt;/strong&gt; - full control over schema, storage, and evolution. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Autonomous decisions&lt;/strong&gt;— business rules and key logic that can change without cross team drama.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exposed contracts&lt;/strong&gt; — deliberate APIs or interfaces, nothing accidental.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Events published&lt;/strong&gt; — clear notifications of important changes, so others can react loosely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When these leak (sync calls everywhere, shared schemas, joint decisions), coupling creeps in. Suddenly your "independent" services aren't  and the pain scales with your org.&lt;/p&gt;

&lt;h2&gt;
  
  
  What good boundaries deliver day-to-day
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Independent deploys without release trains or endless coordination.&lt;/li&gt;
&lt;li&gt;Schema or rule changes in one place without rippling everywhere.&lt;/li&gt;
&lt;li&gt;Contained failures — one service slow or down? The core flows keep moving.&lt;/li&gt;
&lt;li&gt;Easier onboarding — new team members grasp "this owns X" quickly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The warning signs of weak boundaries
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Changes always need multi-team sign-off.&lt;/li&gt;
&lt;li&gt;Network calls turn simple operations into latency nightmares.&lt;/li&gt;
&lt;li&gt;Downtime in one spot cascades.&lt;/li&gt;
&lt;li&gt;Debugging feels like distributed tracing hell.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practical ways to get boundaries right
&lt;/h2&gt;

&lt;p&gt;Start simple: use &lt;strong&gt;Domain-Driven Design&lt;/strong&gt; bounded contexts as your guide, map services (or modules) to real business domains, not entities or tech layers.&lt;/p&gt;

&lt;p&gt;Then run the reality checks on any proposed boundary:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Can it handle its core decisions without constant sync calls to others?&lt;/li&gt;
&lt;li&gt;Does it own its data fully including how it evolves?&lt;/li&gt;
&lt;li&gt;Can the team change key logic or models independently?&lt;/li&gt;
&lt;li&gt;If it's unavailable for 20–30 minutes, does the business keep running mostly fine?&lt;/li&gt;
&lt;li&gt;Can someone new understand its purpose in under an hour?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Mostly yes? Solid. Mostly no? Rethink the cut.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Examples of Finding (and Refining) Boundaries
&lt;/h2&gt;

&lt;p&gt;Finding the right boundaries isn't magic it's iterative discovery, usually starting with conversations, &lt;strong&gt;Event Storming&lt;/strong&gt; sessions, or just mapping how the business actually works. &lt;br&gt;
The gold standard is &lt;strong&gt;Domain Driven Design (DDD)&lt;/strong&gt;: identify &lt;strong&gt;bounded contexts&lt;/strong&gt; (self-contained areas with their own language, rules, and models) that align to &lt;strong&gt;business capabilities&lt;/strong&gt; or &lt;strong&gt;subdomains&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here are a few practical examples from e-commerce (the classic domain) and beyond, showing how teams draw lines that stick or learn to redraw them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad start (common anti-pattern):&lt;/strong&gt;&lt;br&gt;
"User Service", "Product Service", "Order Service", these are nouns/entities, not capabilities. They end up chatty (e.g., Order Service constantly asks Product for stock, User for details), leading to tight coupling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better way:&lt;/strong&gt; &lt;br&gt;
Map to what the business does end-to-end.&lt;br&gt;
A typical online store might discover these strong boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Product Catalog &amp;amp; Discovery &lt;br&gt;
Owns product info, search, recommendations, categories, images. Independent: Can change pricing models or add AI recommendations without touching orders.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inventory Management &lt;br&gt;
Real-time stock tracking,reservations, replenishment alerts. Owns stock data; publishes "stock low" or "reserved" events.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shopping Cart &amp;amp; Checkout Experience  Temporary cart state, promo application, checkout flow. Session-based; doesn't own permanent orders.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Order Fulfillment &lt;br&gt;
Order creation, validation, status tracking, risk checks. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Payment Processing &lt;br&gt;
Transactions, gateways, refunds. Highly autonomous; changes PCI rules without rippling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shipping &amp;amp; Logistics &lt;br&gt;
Carrier integration, tracking, delivery estimates. Owns shipping rules and costs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How teams find these:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Workshop with domain experts, ask "What changes together? What language do you use here?" In practice (like Microsoft's eShopOnContainers reference or real Zalando/Amazon setups), these boundaries allow independent teams and scaling (e.g., inventory spikes during sales without crashing checkout).&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Boundaries aren't a microservices thing they're an architecture thing. Nail them, and the rest (tech, deployment, scale) falls into place much easier. Botch them, and no amount of service mesh, observability, or serverless magic fixes the underlying coupling.&lt;br&gt;
Whether you're building greenfield or refactoring legacy, start here. Everything else is secondary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Links
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For Event Storming:&lt;/strong&gt;&lt;br&gt;
The core book: Introducing EventStorming by Alberto Brandolini &lt;br&gt;
Link: &lt;a href="https://leanpub.com/introducing_eventstorming" rel="noopener noreferrer"&gt;https://leanpub.com/introducing_eventstorming&lt;/a&gt;&lt;br&gt;
Official site for quick intro/resources: &lt;a href="https://www.eventstorming.com/" rel="noopener noreferrer"&gt;https://www.eventstorming.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Domain-Driven Design:&lt;/strong&gt;&lt;br&gt;
The classic: Domain-Driven Design: Tackling Complexity in the Heart of Software by Eric Evans (the "Blue Book")&lt;br&gt;
Link:  &lt;a href="https://www.oreilly.com/library/view/domain-driven-design-tackling/0321125215/" rel="noopener noreferrer"&gt;https://www.oreilly.com/library/view/domain-driven-design-tackling/0321125215/&lt;/a&gt;&lt;br&gt;
Official site for quick intro/resources: &lt;br&gt;
&lt;a href="https://www.domainlanguage.com" rel="noopener noreferrer"&gt;https://www.domainlanguage.com&lt;/a&gt; &lt;/p&gt;

</description>
      <category>architecture</category>
      <category>microservices</category>
      <category>softwareengineering</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Why Asynchronous Messaging Beats REST in Modern Architectures</title>
      <dc:creator>Dinesh Dunukedeniya</dc:creator>
      <pubDate>Tue, 23 Sep 2025 09:25:05 +0000</pubDate>
      <link>https://dev.to/dinesh_dunukedeniya_539a3/why-asynchronous-messaging-beats-rest-in-modern-architectures-55lc</link>
      <guid>https://dev.to/dinesh_dunukedeniya_539a3/why-asynchronous-messaging-beats-rest-in-modern-architectures-55lc</guid>
      <description>&lt;p&gt;In the early days of microservices, &lt;strong&gt;REST APIs&lt;/strong&gt; became the default way for services to talk to each other. They’re simple, predictable, and well-understood. But as systems grow, REST starts showing cracks: latency, bottlenecks, and brittle dependencies.&lt;/p&gt;

&lt;p&gt;That’s where &lt;strong&gt;asynchronous messaging&lt;/strong&gt; with tools like &lt;strong&gt;Kafka, RabbitMQ, or Azure Service Bus&lt;/strong&gt; takes the lead.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔹 1. Loose Coupling vs Tight Coupling
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;REST APIs&lt;/strong&gt; force a request–response relationship. If Service A calls Service B, A must wait until B replies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Messaging&lt;/strong&gt; flips this. A just sends a message and moves on. B (and even C, D, E…) can consume it when ready.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 This decoupling makes systems more &lt;strong&gt;resilient&lt;/strong&gt;—one slow service doesn’t break the whole chain.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔹 2. Push vs Pull: Control Matters
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;With &lt;strong&gt;REST APIs&lt;/strong&gt;, the producer is in &lt;strong&gt;push mode&lt;/strong&gt;. The caller must hit the server directly, regardless of whether that server is busy, overloaded, or temporarily down. This creates pressure and dependency on the consumer’s capacity.&lt;/li&gt;
&lt;li&gt;With &lt;strong&gt;messaging&lt;/strong&gt;, consumers operate in &lt;strong&gt;pull mode&lt;/strong&gt;. They fetch and process events at their own pace. If a consumer slows down, the broker safely buffers messages until it catches up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 This difference alone makes async systems &lt;strong&gt;naturally elastic&lt;/strong&gt;. Services run as fast as they can—no more, no less.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔹 3. Scalability at Its Core
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;REST requires every request to hit the server directly, which means scaling horizontally (load balancers, replicas).&lt;/li&gt;
&lt;li&gt;Kafka can handle &lt;strong&gt;millions of events per second&lt;/strong&gt; by design. Multiple consumers can process the same topic in parallel, enabling massive scale without choking upstream services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Async shines when load spikes—like flash sales or real-time data streams.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔹 4. Built-in Reliability
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If a REST endpoint is down, requests fail unless you build retry logic.&lt;/li&gt;
&lt;li&gt;With messaging, events are &lt;strong&gt;stored durably&lt;/strong&gt; in the broker. Even if consumers go offline, they can catch up later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 That’s a huge win for mission-critical systems where &lt;strong&gt;losing events = losing money&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔹 5. Event-Driven Power
&lt;/h2&gt;

&lt;p&gt;REST is about &lt;strong&gt;asking&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What’s your status right now?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Messaging is about &lt;strong&gt;telling&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This event happened.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That small difference unlocks &lt;strong&gt;event-driven architectures&lt;/strong&gt;, where systems react automatically to changes. Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Order created → Inventory reserved → Notification sent&lt;/li&gt;
&lt;li&gt;Payment succeeded → Loyalty points awarded → Email triggered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 This flow is natural, scalable, and reduces the need for constant polling.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔹 6. Future-Proof Flexibility
&lt;/h2&gt;

&lt;p&gt;With REST, if you add a new consumer, you must update the producer to call it.&lt;br&gt;
With messaging, producers don’t care who listens. You can add new consumers (analytics, monitoring, fraud detection) without touching existing services.&lt;/p&gt;

&lt;p&gt;👉 That’s how companies evolve from &lt;strong&gt;MVP to enterprise-scale&lt;/strong&gt; without rewriting everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚖️ The Trade-offs: Why Async Isn’t Always Better
&lt;/h2&gt;

&lt;p&gt;While async messaging is powerful, it’s not a silver bullet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complexity&lt;/strong&gt;: Designing an event-driven system requires careful planning (message schemas, idempotency, ordering).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eventual Consistency&lt;/strong&gt;: You can’t always get an immediate answer. Users may see a slight delay before data fully syncs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational Overhead&lt;/strong&gt;: Brokers like Kafka require infrastructure, monitoring, and scaling strategies. REST, by contrast, can run almost anywhere with minimal setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 For &lt;strong&gt;user-facing features that need instant confirmation&lt;/strong&gt; (login, payment, search), REST is still the better choice. Async shines in the &lt;strong&gt;backend&lt;/strong&gt; where scale, resilience, and decoupling matter most.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Final Thought
&lt;/h2&gt;

&lt;p&gt;If you’re building for &lt;strong&gt;scale, resilience, and evolution&lt;/strong&gt;, REST alone won’t cut it.&lt;br&gt;
Async messaging is not just a tool it’s an &lt;strong&gt;architectural mindset&lt;/strong&gt; that embraces decoupling, reliability, and growth.&lt;/p&gt;

&lt;p&gt;The future is &lt;strong&gt;event-driven&lt;/strong&gt;. And messaging makes it possible.&lt;/p&gt;




</description>
      <category>eventdriven</category>
      <category>microservices</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How to Choose the Right Microservice Boundaries</title>
      <dc:creator>Dinesh Dunukedeniya</dc:creator>
      <pubDate>Sat, 09 Aug 2025 05:33:59 +0000</pubDate>
      <link>https://dev.to/dinesh_dunukedeniya_539a3/how-to-choose-the-right-microservice-boundaries-4414</link>
      <guid>https://dev.to/dinesh_dunukedeniya_539a3/how-to-choose-the-right-microservice-boundaries-4414</guid>
      <description>&lt;p&gt;Choosing the size of a microservice isn’t just about code. it’s about team structure, business capabilities, and how the system will evolve over time.&lt;br&gt;
Get it wrong, and you either end up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Mini-Monolith → big, complex, slow to deploy, and hard for one team to own.&lt;/li&gt;
&lt;li&gt;The Distributed Hairball → dozens of tiny services that constantly call each other, making debugging a nightmare.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Start with the Business Domain (Domain-Driven Design)
&lt;/h2&gt;

&lt;p&gt;Most experienced teams begin with DDD to find bounded contexts areas where the business language, rules, and data make sense together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example – E-commerce:&lt;/strong&gt;&lt;br&gt;
Bounded contexts that naturally map to microservices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Catalog → product data, categories, availability&lt;/li&gt;
&lt;li&gt;Ordering → order creation, updates, status tracking&lt;/li&gt;
&lt;li&gt;Payments → payment processing, refunds, compliance&lt;/li&gt;
&lt;li&gt;Shipping → delivery tracking, carriers, schedules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeps each service focused&lt;/li&gt;
&lt;li&gt;Prevents mixing unrelated rules&lt;/li&gt;
&lt;li&gt;Data ownership is clear&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Look at Change Patterns
&lt;/h2&gt;

&lt;p&gt;If two features always change together, they probably belong in the same service.&lt;br&gt;
If they change at different speeds or are handled by different teams, splitting makes sense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Catalog changes once a month → stable&lt;/li&gt;
&lt;li&gt;Checkout changes weekly → fast-moving&lt;/li&gt;
&lt;li&gt;→ Keep them separate so catalog updates don’t block checkout releases.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Align to Team Boundaries (Two-Pizza Rule 🍕)
&lt;/h2&gt;

&lt;p&gt;A service should be small enough for one small team (5–8 people) to fully own and deploy it without coordinating with other teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
If both the Marketing and Payments teams constantly commit to the same codebase, that service is probably too big.&lt;/p&gt;




&lt;h2&gt;
  
  
  Data Ownership Is Non-Negotiable
&lt;/h2&gt;

&lt;p&gt;Each microservice should own its own database.&lt;br&gt;
If two services write to the same table, they’re still coupled, no matter what the code says.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
Payments owns the Transactions table.&lt;br&gt;
If Orders needs payment info, it calls Payments or subscribes to its events. it doesn’t write directly to that table.&lt;/p&gt;




&lt;h2&gt;
  
  
  Watch for Chatty Communication
&lt;/h2&gt;

&lt;p&gt;If one user request calls more than 2–3 services synchronously, you may have gone too granular.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
Use event-driven architecture — publish an event like OrderPlaced and let other services (Payments, Notifications, Loyalty) react independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start Bigger, Split Later
&lt;/h2&gt;

&lt;p&gt;Most large companies don’t start with dozens of microservices.&lt;br&gt;
They begin with a modular monolith or a few chunky services and split when needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scaling issues&lt;/li&gt;
&lt;li&gt;Release coordination pain&lt;/li&gt;
&lt;li&gt;Regulatory/security isolation needs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
A SaaS platform starts with Users + Billing + Analytics in one service.&lt;br&gt;
When Billing needs PCI compliance, they split it into its service, with minimal disruption.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F33oc9nxraq5w5h63g89d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F33oc9nxraq5w5h63g89d.png" alt="microsevice Split check list" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;When deciding microservice boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with bounded contexts from Domain-Driven Design.&lt;/li&gt;
&lt;li&gt;Group features by change patterns — if they change together, keep them together.&lt;/li&gt;
&lt;li&gt;Align services to small, independent teams (Two-Pizza Rule).&lt;/li&gt;
&lt;li&gt;Enforce data ownership — one service, one database.&lt;/li&gt;
&lt;li&gt;Avoid excessive synchronous calls — prefer async/event-driven.&lt;/li&gt;
&lt;li&gt;Start larger, split only when needed for scaling, compliance, or deployment independence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn’t to have the smallest services.It’s to have well-defined, loosely coupled, independently deployable services that match your business needs and team structure.&lt;/p&gt;

&lt;p&gt;💬 Over to you — Have you worked on a project where microservices were way too small or too big? How did it go?&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Microservice-to-Microservice Authentication in .NET Using IdentityServer and JWT</title>
      <dc:creator>Dinesh Dunukedeniya</dc:creator>
      <pubDate>Fri, 01 Aug 2025 23:06:23 +0000</pubDate>
      <link>https://dev.to/dinesh_dunukedeniya_539a3/microservice-to-microservice-authentication-in-net-using-identityserver-and-jwt-2n0a</link>
      <guid>https://dev.to/dinesh_dunukedeniya_539a3/microservice-to-microservice-authentication-in-net-using-identityserver-and-jwt-2n0a</guid>
      <description>&lt;p&gt;In a microservices architecture, secure communication between services is just as important as securing external API calls. One of the most common and robust ways to handle this in .NET is by using IdentityServer, JWT tokens, and the Client Credentials flow.&lt;/p&gt;

&lt;p&gt;In this article, you’ll learn how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticate microservices using Client Credentials&lt;/li&gt;
&lt;li&gt;Request and validate JWT tokens&lt;/li&gt;
&lt;li&gt;Pass tokens between services securely&lt;/li&gt;
&lt;li&gt;Optimize token management with caching&lt;/li&gt;
&lt;li&gt;Apply best practices for real-world deployments.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Client Credentials Flow?
&lt;/h2&gt;

&lt;p&gt;The Client Credentials flow is designed for machine-to-machine communication. Unlike user-based authentication (e.g., Authorization Code flow), there’s no interactive login. Instead, each service identifies itself using a ClientId and ClientSecret, and IdentityServer issues an access token that can be used to call other services.&lt;/p&gt;

&lt;p&gt;When to use it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service A needs to call Service B directly.&lt;/li&gt;
&lt;li&gt;No user context is involved.&lt;/li&gt;
&lt;li&gt;Both services are trusted and run in a secure environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Trusted Services and Secure Environment
&lt;/h2&gt;

&lt;p&gt;When we say "both services are trusted and run in a secure environment", it means:&lt;br&gt;
&lt;strong&gt;Trusted Services&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Both microservices are owned and controlled by the same organization (or within a trusted partner ecosystem).&lt;/li&gt;
&lt;li&gt;Each service is registered in IdentityServer with its own ClientId and ClientSecret.&lt;/li&gt;
&lt;li&gt;Services rely on IdentityServer as the trust broker — if a service presents a valid JWT from IdentityServer, other services trust it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
A Billing API (Service A) calls a Payment Processor API (Service B) within your company's internal infrastructure. Both are part of your platform and use IdentityServer for token issuance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secure Environment&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Services communicate over a private and encrypted network (e.g., AWS VPC, Azure VNet, Kubernetes internal network).&lt;/li&gt;
&lt;li&gt;All traffic is secured with TLS (HTTPS) to prevent token interception.&lt;/li&gt;
&lt;li&gt;Secrets (e.g., ClientSecret) are stored securely in tools like Azure Key Vault or AWS Secrets Manager.&lt;/li&gt;
&lt;li&gt;IdentityServer is either private or protected with firewalls, API gateways, and access controls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
Your services are deployed in Kubernetes and communicate over an internal service mesh (like Istio), where mTLS enforces encryption and mutual authentication.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;If your services are NOT in a trusted or secure environment&lt;/strong&gt; (e.g., exposed to the public internet or involve third-party APIs), you should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate the audience in the JWT token strictly.&lt;/li&gt;
&lt;li&gt;Use an API Gateway with rate limiting, throttling, and authentication enforcement.&lt;/li&gt;
&lt;li&gt;Consider mTLS in addition to JWT for an extra security layer.&lt;/li&gt;
&lt;li&gt;Implement scopes and claims-based authorization to enforce least privilege.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Token Flow Diagram
&lt;/h2&gt;

&lt;p&gt;Here’s how authentication works between two microservices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------+           +---------------------+          +-----------------+
| Service A       |           | IdentityServer      |          | Service B       |
| (Caller)        |           | (Auth Provider)     |          | (Resource API)  |
+-----------------+           +---------------------+          +-----------------+
       |                              |                               |
       |   1. Request Token           |                               |
       |-----------------------------&amp;gt;|                               |
       |                              |                               |
       |         2. JWT Token         |                               |
       |&amp;lt;-----------------------------|                               |
       |                              |                               |
       |  3. Call API with Token      |                               |
       |-------------------------------------------------------------&amp;gt;|
       |                              |                               |
       |          4. Validate Token                                   |
       |                              |                               |
       |&amp;lt;-------------------------------------------------------------|

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  IdentityServer Setup
&lt;/h2&gt;

&lt;p&gt;First, configure a client in IdentityServer for service-to-service communication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new Client
{
    ClientId = "service-a",
    ClientSecrets = { new Secret("secret".Sha256()) },
    AllowedGrantTypes = GrantTypes.ClientCredentials,
    AllowedScopes = { "service-b-api" }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows Service A to request a token for the scope service-b-api and then use it when calling Service B.&lt;/p&gt;




&lt;h2&gt;
  
  
  Requesting a Token in .NET
&lt;/h2&gt;

&lt;p&gt;Service A will use the IdentityModel library to request a token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using IdentityModel.Client;
using System.Net.Http;

public class TokenService
{
    private readonly HttpClient _httpClient;

    public TokenService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task&amp;lt;string&amp;gt; GetTokenAsync()
    {
        var disco = await _httpClient.GetDiscoveryDocumentAsync("https://identityserver-url");
        if (disco.IsError) throw new Exception(disco.Error);

        var tokenResponse = await _httpClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
        {
            Address = disco.TokenEndpoint,
            ClientId = "service-a",
            ClientSecret = "secret",
            Scope = "service-b-api"
        });

        if (tokenResponse.IsError) throw new Exception(tokenResponse.Error);

        return tokenResponse.AccessToken;
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Calling Another Microservice with JWT
&lt;/h2&gt;

&lt;p&gt;Once we have a token, we can attach it to the Authorization header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using IdentityModel.Client;

public class ServiceBClient
{
    private readonly HttpClient _httpClient;
    private readonly TokenService _tokenService;

    public ServiceBClient(HttpClient httpClient, TokenService tokenService)
    {
        _httpClient = httpClient;
        _tokenService = tokenService;
    }

    public async Task&amp;lt;string&amp;gt; GetDataAsync()
    {
        var token = await _tokenService.GetTokenAsync();
        _httpClient.SetBearerToken(token);

        var response = await _httpClient.GetAsync("https://service-b/api/data");
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsStringAsync();
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Validating JWT in the Receiving Service
&lt;/h2&gt;

&lt;p&gt;Service B must validate the JWT token. Add JWT Bearer authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =&amp;gt;
    {
        options.Authority = "https://identityserver-url";
        options.RequireHttpsMetadata = true;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false
        };
    });

builder.Services.AddAuthorization();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply authentication and authorization middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Optimizing with Token Caching
&lt;/h2&gt;

&lt;p&gt;You should avoid requesting a token for every call. Instead, cache it until it expires:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class CachedTokenService
{
    private readonly TokenService _tokenService;
    private string _cachedToken;
    private DateTime _expiresAt;

    public CachedTokenService(TokenService tokenService)
    {
        _tokenService = tokenService;
    }

    public async Task&amp;lt;string&amp;gt; GetTokenAsync()
    {
        if (!string.IsNullOrEmpty(_cachedToken) &amp;amp;&amp;amp; DateTime.UtcNow &amp;lt; _expiresAt)
            return _cachedToken;

        var token = await _tokenService.GetTokenAsync();
        _cachedToken = token;
        _expiresAt = DateTime.UtcNow.AddMinutes(50); // assuming 1-hour expiry

        return _cachedToken;
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Automating Token Injection with DelegatingHandler
&lt;/h2&gt;

&lt;p&gt;You can also create a custom DelegatingHandler to automatically add tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class TokenHandler : DelegatingHandler
{
    private readonly CachedTokenService _tokenService;

    public TokenHandler(CachedTokenService tokenService)
    {
        _tokenService = tokenService;
    }

    protected override async Task&amp;lt;HttpResponseMessage&amp;gt; SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var token = await _tokenService.GetTokenAsync();
        request.SetBearerToken(token);
        return await base.SendAsync(request, cancellationToken);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register it in Program.cs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.Services.AddHttpClient&amp;lt;ServiceBClient&amp;gt;()
    .AddHttpMessageHandler&amp;lt;TokenHandler&amp;gt;();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, every call to Service B will automatically include a valid JWT.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best Practices for Production
&lt;/h2&gt;

&lt;p&gt;🔒 Secure Secrets: Store ClientSecret in a secret manager (e.g., Azure Key Vault or AWS Secrets Manager).&lt;/p&gt;

&lt;p&gt;📜 Logging &amp;amp; Monitoring: Log token acquisition failures and monitor IdentityServer availability.&lt;/p&gt;

&lt;p&gt;🚀 Use Delegating Handlers: Automate token injection for all service-to-service calls.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Use IdentityServer and Client Credentials flow for secure microservice-to-microservice communication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve JWT tokens using IdentityModel.&lt;/li&gt;
&lt;li&gt;Validate tokens in each service using JwtBearer.&lt;/li&gt;
&lt;li&gt;Optimize with token caching and DelegatingHandler.&lt;/li&gt;
&lt;li&gt;Follow security best practices for secret management
and logging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By implementing this approach, you ensure that your microservices communicate securely and efficiently in a production environment.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Fixing Microservice Dependencies with Self-Contained Events (aka ECST)</title>
      <dc:creator>Dinesh Dunukedeniya</dc:creator>
      <pubDate>Sun, 27 Jul 2025 04:05:31 +0000</pubDate>
      <link>https://dev.to/dinesh_dunukedeniya_539a3/fixing-microservice-dependencies-with-self-contained-events-aka-ecst-2l39</link>
      <guid>https://dev.to/dinesh_dunukedeniya_539a3/fixing-microservice-dependencies-with-self-contained-events-aka-ecst-2l39</guid>
      <description>&lt;p&gt;Microservices are supposed to be independent, autonomous, and scalable. But in reality, they often end up tightly coupled—relying on synchronous API calls, complex orchestration, and fragile dependencies.&lt;/p&gt;

&lt;p&gt;In this post, we’ll explore how to break microservice dependencies using a powerful pattern known as Event-Carried State Transfer (ECST)—or as I like to call it, Self-Contained Events. It’s a simple yet impactful idea: put all the necessary data in the event.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: API-Based Microservice Dependency
&lt;/h2&gt;

&lt;p&gt;Many microservice systems fall into a trap: they rely heavily on synchronous API calls between services to share data. This creates tight coupling, reduces resilience, and introduces latency.This API dependencies hurt reliability and scalability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario: Payment Service Needs Customer Info
&lt;/h2&gt;

&lt;p&gt;Let’s say a customer places an order. The Order Service emits an event like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "eventType": "OrderPlaced",
  "orderId": "ORD-1234",
  "customerId": "CUST-5678",
  "items": [ ... ]
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far, so good. But now the Payment Service needs to:&lt;/p&gt;

&lt;p&gt;Validate the customer’s billing status.&lt;/p&gt;

&lt;p&gt;Access their preferred payment method.&lt;/p&gt;

&lt;p&gt;Maybe even apply discounts based on customer type.&lt;/p&gt;

&lt;p&gt;To do that, it has to call the Customer Service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment Service → GET /customers/CUST-5678 → Customer Service

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s the problem:&lt;/p&gt;

&lt;p&gt;If Customer Service is slow or down, the Payment Service fails.&lt;/p&gt;

&lt;p&gt;You’ve created a runtime dependency between two services.&lt;/p&gt;

&lt;p&gt;The system becomes fragile and hard to scale.&lt;/p&gt;

&lt;p&gt;Despite using events, the services are still tightly coupled, relying on synchronous communication to function.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: ECST (Self-Contained Events)
&lt;/h2&gt;

&lt;p&gt;Instead of making an API call, the Customer Service emits events when relevant data changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "eventType": "CustomerUpdated",
  "customerId": "CUST-5678",
  "name": "Alice Smith",
  "email": "alice@example.com",
  "loyaltyTier": "gold",
  "preferredPaymentMethod": "visa"
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Payment Service subscribes to these events and stores only the fields it needs—either in a local database or an in-memory cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now it looks like this:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Customer Service emits CustomerCreated and CustomerUpdated events.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Payment Service listens and stores a local copy of needed customer data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When OrderPlaced is received, Payment uses local customer data—no API call required.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Benefits of This Approach
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No runtime dependency on Customer Service.&lt;/li&gt;
&lt;li&gt;Improved resilience—each service can function independently.&lt;/li&gt;
&lt;li&gt;Faster, async processing using pre-synced local data.&lt;/li&gt;
&lt;li&gt;Better boundaries—each service owns its domain and data shape.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bonus Tip: Design Events with Purpose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Design your events with the consumer’s use case in mind.&lt;/li&gt;
&lt;li&gt;Use versioned schemas to evolve safely.&lt;/li&gt;
&lt;li&gt;Make handlers idempotent to handle retries.&lt;/li&gt;
&lt;li&gt;Instead of emitting minimal "something happened" events:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ "eventType": "CustomerUpdated", "customerId": "CUST-5678" }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Emit rich, meaningful events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "eventType": "CustomerUpdated",
  "customerId": "CUST-5678",
  "email": "alice@example.com",
  "loyaltyTier": "gold"
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s how you enable autonomy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Microservices shine when services can operate independently. But if every service depends on synchronous calls to others, you haven’t truly decoupled.&lt;/p&gt;

&lt;p&gt;Event-Carried State Transfer (ECST) is a simple yet powerful technique to fix that.&lt;br&gt;
✅ Push state with events&lt;br&gt;
✅ Keep local, read-only data&lt;br&gt;
✅ Break the web of runtime dependencies&lt;/p&gt;

</description>
      <category>eventdriven</category>
      <category>microservices</category>
      <category>softwaredevelopment</category>
      <category>kafka</category>
    </item>
    <item>
      <title>Fixing Microservice Communication: From Fragile Calls to Resilient Systems</title>
      <dc:creator>Dinesh Dunukedeniya</dc:creator>
      <pubDate>Sun, 22 Jun 2025 10:46:42 +0000</pubDate>
      <link>https://dev.to/dinesh_dunukedeniya_539a3/fixing-microservice-communication-from-fragile-calls-to-resilient-systems-2c17</link>
      <guid>https://dev.to/dinesh_dunukedeniya_539a3/fixing-microservice-communication-from-fragile-calls-to-resilient-systems-2c17</guid>
      <description>&lt;p&gt;Microservices promise flexibility, scalability, and faster deployments. However, without proper communication strategies, they quickly become a tangled web of tightly coupled services, frequent downtime, and frustrating bugs. In this article, we’ll explore common microservice communication problems and how to fix them by adopting modern patterns and tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Direct Service Calls
&lt;/h2&gt;

&lt;p&gt;Imagine a typical e-commerce application with services like OrderService, PaymentService, and InventoryService. A direct HTTP call chain might look like this:&lt;br&gt;
&lt;code&gt;OrderService → PaymentService → InventoryService&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now, suppose InventoryService goes down. The entire chain breaks, and placing orders fails even though the issue is isolated.&lt;/p&gt;

&lt;p&gt;Problems with direct service-to-service calls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tight Coupling&lt;/strong&gt;&lt;br&gt;
Each service depends on the availability, responsiveness, and correct behavior of the services it calls. If one service goes down, all services calling it may also fail or become slow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cascading Failures&lt;/strong&gt;&lt;br&gt;
One failure propagates across multiple services. This can bring down large parts of your architecture, even if only one component fails.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Increased Latency&lt;/strong&gt;&lt;br&gt;
Each call adds network delay, making the overall order process slow and frustrating.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Retry Storms and Thundering Herds&lt;/strong&gt;&lt;br&gt;
Simultaneous retries overload the failing service even more. This creates a feedback loop that makes recovery harder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Harder to Scale and Deploy Independently&lt;/strong&gt;&lt;br&gt;
Synchronous dependencies force tight coordination between teams and services, limiting independent deployment.&lt;br&gt;
Scaling one service may require scaling others to handle the load.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Harder to Test&lt;/strong&gt;&lt;br&gt;
Unit and integration tests require other services to be available.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  How to Fix These Problems: Best Practices and Patterns
&lt;/h2&gt;

&lt;p&gt;To build resilient, scalable, and maintainable distributed systems, it’s essential to follow sound architectural principles. Below are some best practices and design patterns that can help you tackle common challenges in microservices.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Favor Loose Coupling Through Asynchronous Messaging
&lt;/h2&gt;

&lt;p&gt;One of the most effective ways to improve system resilience and scalability is by embracing loose coupling through asynchronous messaging. Direct, synchronous calls between services may seem convenient, but they can introduce tight dependencies, slow down performance, and increase the risk of cascading failures.&lt;/p&gt;

&lt;p&gt;Instead, adopt a message-based/event-driven architecture using tools like Apache Kafka, Azure Service Bus, or RabbitMQ. These platforms allow services to communicate in a decoupled fashion by publishing and subscribing to events rather than calling each other directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why It Works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decoupling: Services don’t need to know each other’s internal workings or availability.&lt;/li&gt;
&lt;li&gt;Scalability: Consumers can scale independently based on demand.&lt;/li&gt;
&lt;li&gt;Resilience: Fail
ures in one service won’t bring down the entire system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example Pattern in Action:&lt;br&gt;
Let’s say a customer places an order. Instead of the OrderService invoking other services directly, it publishes an OrderPlaced event to Kafka:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class KafkaPublisher
{
    private readonly IProducer&amp;lt;Null, string&amp;gt; _producer;

    public KafkaPublisher(string bootstrapServers)
    {
        var config = new ProducerConfig { BootstrapServers = bootstrapServers };
        _producer = new ProducerBuilder&amp;lt;Null, string&amp;gt;(config).Build();
    }

    public async Task PublishOrderPlacedAsync(OrderPlacedEvent orderPlaced)
    {
        var message = JsonSerializer.Serialize(orderPlaced);
        await _producer.ProduceAsync("order-events", new Message&amp;lt;Null, string&amp;gt; { Value = message });
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage in OrderService:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await kafkaPublisher.PublishOrderPlacedAsync(new OrderPlacedEvent {
    OrderId = "123",
    TotalAmount = 99.99
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there:&lt;/p&gt;

&lt;p&gt;PaymentService listens for OrderPlaced events and initiates payment processing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class KafkaConsumerService
{
    private readonly IConsumer&amp;lt;Ignore, string&amp;gt; _consumer;

    public KafkaConsumerService(string bootstrapServers, string topic, string groupId)
    {
        var config = new ConsumerConfig
        {
            BootstrapServers = bootstrapServers,
            GroupId = groupId,
            AutoOffsetReset = AutoOffsetReset.Earliest
        };

        _consumer = new ConsumerBuilder&amp;lt;Ignore, string&amp;gt;(config).Build();
        _consumer.Subscribe(topic);
    }

    public void StartConsuming(CancellationToken cancellationToken)
    {
        Task.Run(() =&amp;gt;
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
                    var consumeResult = _consumer.Consume(cancellationToken);
                    var orderPlaced = JsonSerializer.Deserialize&amp;lt;OrderPlacedEvent&amp;gt;(consumeResult.Message.Value);

                    if (orderPlaced != null)
                    {
                        ProcessPayment(orderPlaced);
                    }
                }
                catch (ConsumeException ex)
                {
                    // Handle consume exception (e.g. log it)
                }
            }
        }, cancellationToken);
    }

    private void ProcessPayment(OrderPlacedEvent orderPlaced)
    {
        // Simulate payment processing logic
        Console.WriteLine($"Processing payment for Order ID: {orderPlaced.OrderId}, Amount: {orderPlaced.TotalAmount}");
        // Here you could interact with a payment gateway or record payment details
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, InventoryService updates stock levels accordingly, and EmailService sends out an order confirmation email.&lt;/p&gt;

&lt;p&gt;Each service acts independently, processing the message in its own time and retrying as needed. This pattern not only improves modularity but also creates a more robust and responsive system.&lt;/p&gt;

&lt;p&gt;To fully harness the power of this architecture, it’s important to use the right message types for the right purpose. Choosing the correct communication pattern improves clarity, reduces unnecessary dependencies, and ensures services are loosely coupled by design.&lt;/p&gt;

&lt;p&gt;Different scenarios call for different message styles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event Notifications: Inform that something has occurred, such as OrderPlaced. These are fire-and-forget and don't require a response.&lt;/li&gt;
&lt;li&gt;Event-Carried State Transfer: Instead of triggering additional service calls, include relevant data in the event itself. For example, OrderPlaced might contain the full order details so downstream services don’t need to query OrderService.&lt;/li&gt;
&lt;li&gt;Command Messages: Explicitly request another service to act (used carefully to avoid tight coupling).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/dinesh_dunukedeniya_539a3/designing-e-commerce-order-processing-orchestration-vs-choreography-2k6d"&gt;Read my previous article to understand event driven architecture&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Add Resilience: Timeouts, Retries, and Circuit Breakers
&lt;/h2&gt;

&lt;p&gt;Even in a world leaning heavily on asynchronous messaging, sometimes synchronous calls are inevitable, particularly when dealing with legacy systems, external APIs, or tight coordination between services. When that’s the case, building resilience directly into your service communication can prevent temporary issues from turning into full-blown outages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timeouts&lt;/strong&gt;&lt;br&gt;
Set an upper limit on how long a service should wait for a response. This ensures your application doesn’t hang indefinitely while waiting for a slow or unresponsive dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retries with Exponential Backoff&lt;/strong&gt;&lt;br&gt;
Transient faults (like network blips or momentary service hiccups) are often self-healing. With retry policies, you can attempt the request again, waiting slightly longer each time to avoid compounding the issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Circuit Breakers&lt;/strong&gt;&lt;br&gt;
If a downstream service keeps failing, stop hammering it. A circuit breaker temporarily halts requests, giving the failing service time to recover and your system time to avoid a full meltdown.&lt;/p&gt;

&lt;p&gt;Putting It All Together with Polly and .Net,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var retryPolicy = Policy
    .Handle&amp;lt;HttpRequestException&amp;gt;()
    .WaitAndRetryAsync(
        retryCount: 3,
        sleepDurationProvider: attempt =&amp;gt; TimeSpan.FromSeconds(Math.Pow(2, attempt)),
        onRetry: (exception, timeSpan, retryCount, context) =&amp;gt;
        {
            Console.WriteLine($"Retry {retryCount} after {timeSpan.TotalSeconds}s due to: {exception.Message}");
        });

var circuitBreakerPolicy = Policy
    .Handle&amp;lt;HttpRequestException&amp;gt;()
    .CircuitBreakerAsync(
        exceptionsAllowedBeforeBreaking: 2,
        durationOfBreak: TimeSpan.FromSeconds(30),
        onBreak: (ex, breakDelay) =&amp;gt;
        {
            Console.WriteLine($"Circuit broken! Delay: {breakDelay.TotalSeconds}s");
        },
        onReset: () =&amp;gt; Console.WriteLine("Circuit closed, operations resumed."),
        onHalfOpen: () =&amp;gt; Console.WriteLine("Circuit half-open, testing service.")
    );

// Wrap policies
var policyWrap = Policy.WrapAsync(retryPolicy, circuitBreakerPolicy);

// Execute HTTP request with resilience
var response = await policyWrap.ExecuteAsync(() =&amp;gt;
    httpClient.GetAsync("https://inventory-service/api/check-stock"));


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improved fault tolerance: Graceful recovery from temporary service hiccups.&lt;/li&gt;
&lt;li&gt;Stability under stress: Prevents cascading failures when a downstream service is unhealthy.&lt;/li&gt;
&lt;li&gt;Better user experience: Avoids unnecessary timeouts and improves perceived system responsiveness.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Implement Graceful Fallbacks
&lt;/h2&gt;

&lt;p&gt;Even with retries and circuit breakers, sometimes your dependencies simply won’t respond. In those cases, your system still needs to behave gracefully, not crash or hang indefinitely. This is where fallback strategies come in.&lt;/p&gt;

&lt;p&gt;Fallbacks provide alternative logic or responses when primary dependencies fail, ensuring the user experience remains smooth and your service stays operational, even if at reduced functionality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Case: Inventory Service Fallback&lt;/strong&gt;&lt;br&gt;
Let’s say your application relies on an external inventory service to check stock availability. If that service goes down, rather than propagating an error or timing out, you could route the request to a backup source or return a cached/default response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public interface IInventoryService
{
    Task&amp;lt;string&amp;gt; CheckStockAsync(string productId);
}

public class PrimaryInventoryService : IInventoryService
{
    public async Task&amp;lt;string&amp;gt; CheckStockAsync(string productId)
    {
        // Simulate failure
        throw new HttpRequestException("Primary service unavailable");
    }
}

public class BackupInventoryService : IInventoryService
{
    public async Task&amp;lt;string&amp;gt; CheckStockAsync(string productId)
    {
        return await Task.FromResult("Stock from backup service: 10 units");
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let’s set up a fallback policy using Polly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var backupService = new BackupInventoryService();
var primaryService = new PrimaryInventoryService();

var fallbackPolicy = Policy&amp;lt;string&amp;gt;
    .Handle&amp;lt;HttpRequestException&amp;gt;()
    .FallbackAsync(
        fallbackAction: async cancellationToken =&amp;gt;
        {
            // Call the backup service when the primary fails
            Console.WriteLine("Primary service failed. Using backup...");
            return await backupService.CheckStockAsync("P123");
        });

var result = await fallbackPolicy.ExecuteAsync(async () =&amp;gt;
{
    return await primaryService.CheckStockAsync("P123");
});

Console.WriteLine(result);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User continuity: Prevents error messages or app crashes.&lt;/li&gt;
&lt;li&gt;Resilience: Keeps the service running, even if at reduced precision or reliability.&lt;/li&gt;
&lt;li&gt;Flexibility: Allows for dynamic routing, caching, or degradation strategies when under pressure.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Improve Observability
&lt;/h2&gt;

&lt;p&gt;Modern distributed systems are often sprawling, complex, and asynchronous, which makes understanding what's happening inside them a challenge. To avoid flying blind, it’s crucial to bake in observability from the very beginning.&lt;/p&gt;

&lt;p&gt;Observability isn't just about logging errors; it’s about gaining real-time, actionable insight into system behavior and performance. By using tools like OpenTelemetry, Jaeger, or Zipkin, you can trace how requests flow through your services, identify bottlenecks, and debug issues with surgical precision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Practices for Event Tracing and Monitoring&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tag Each Event with Correlation IDs: Generate and propagate a unique identifier with each request or event. This allows you to trace a single transaction across services and threads, even across messaging systems like Kafka or Azure Service Bus.&lt;/li&gt;
&lt;li&gt;Trace Message Consumption and Processing Times Measure the duration and path of every event, from the moment it's published to when it's processed. This helps pinpoint slow consumers, misconfigurations, or performance regressions.&lt;/li&gt;
&lt;li&gt;Monitor for Failing or Degraded Services: Set up alerts for failure rates, message lag, queue depth, and other signals that indicate unhealthy behavior in your system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster troubleshooting: Identify root causes without guesswork.&lt;/li&gt;
&lt;li&gt;Performance optimization: Locate bottlenecks and slow code paths.&lt;/li&gt;
&lt;li&gt;Operational confidence: Know when things go wrong—and why.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Designing resilient distributed systems isn’t just about writing more code. It’s about writing smarter code. By favoring loose coupling through asynchronous messaging, implementing robust fault-handling mechanisms, providing graceful fallbacks, and enhancing observability, you lay the groundwork for systems that don’t just function under ideal conditions; they thrive under pressure.&lt;/p&gt;

&lt;p&gt;The real world is messy. Services fail, networks lag, and dependencies go dark. But with the patterns and tools we've explored from Kafka and Polly to OpenTelemetry, you’ll be equipped to handle it all with confidence and control.&lt;/p&gt;

&lt;p&gt;Start with small improvements, measure their impact, and keep refining. Resilience is a journey, not a destination, and every thoughtful design choice gets you closer.&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>dotnet</category>
      <category>eventdriven</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Designing E-Commerce Order Processing: Orchestration vs Choreography</title>
      <dc:creator>Dinesh Dunukedeniya</dc:creator>
      <pubDate>Wed, 11 Jun 2025 11:00:32 +0000</pubDate>
      <link>https://dev.to/dinesh_dunukedeniya_539a3/designing-e-commerce-order-processing-orchestration-vs-choreography-2k6d</link>
      <guid>https://dev.to/dinesh_dunukedeniya_539a3/designing-e-commerce-order-processing-orchestration-vs-choreography-2k6d</guid>
      <description>&lt;p&gt;In the world of microservices and event-driven architecture, orchestrating business workflows is a common challenge. Two popular patterns for managing these workflows are Orchestration and Choreography, each with unique strengths and trade-offs.&lt;/p&gt;

&lt;p&gt;In this post, we’ll design a classic use case E-Commerce Order Processing using both patterns, helping you understand how they differ in real world scenarios.&lt;/p&gt;

&lt;p&gt;Use Case: E-Commerce Order Processing&lt;br&gt;
The system must handle customer orders by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Receiving the order&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Processing payment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Updating inventory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scheduling shipping&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The flow must ensure consistency; either the entire process is completed, or appropriate compensations occur.&lt;/p&gt;

&lt;p&gt;Let's examine the states involved in this process. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqbvjubxloml8ogde82q6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqbvjubxloml8ogde82q6.png" alt="Sate Machine Diagrom" width="800" height="1326"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Orchestration: The Centralized Conductor
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;br&gt;
Each microservice publishes events to Kafka. A central orchestrator subscribes to these events, determines the next step in the workflow, and then publishes a new event to Kafka to trigger the appropriate microservice to act.&lt;/p&gt;

&lt;p&gt;The diagram below illustrates this flow with a typical e-commerce order processing example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;OrderService publishes an OrderCreated event to Kafka after a customer places an order.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;OrderOrchestrator consumes the OrderCreated event and publishes a ProcessPayment event.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PaymentService subscribes to ProcessPayment, processes the payment, and publishes either:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PaymentCompleted if successful, or&lt;/li&gt;
&lt;li&gt;PaymentFailed if the transaction fails.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On PaymentCompleted, the orchestrator triggers the next step by publishing ReserveStock.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;InventoryService acts on ReserveStock and replies with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;StockReserved if inventory is available, or&lt;/li&gt;
&lt;li&gt;OutOfStock if items are unavailable.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On successful stock reservation, the orchestrator publishes ScheduleDelivery.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ShippingService then schedules delivery and publishes DeliveryScheduled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, OrderService updates the order status to success or failure based on the outcome of each step.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzwp4vhluw45jr91awrp0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzwp4vhluw45jr91awrp0.png" alt="Orchestration" width="800" height="817"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Event-Driven Choreography: The Decentralized Dance
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;br&gt;
Each microservice publishes events to Kafka and subscribes to relevant events from other services. Based on the event it receives, each service independently determines its next action and publishes a new event to Kafka, allowing the workflow to progress without a central orchestrator.&lt;/p&gt;

&lt;p&gt;OrderService handles the customer request and publishes an OrderPlaced event to Kafka.&lt;/p&gt;

&lt;p&gt;The diagram below illustrates this flow with a typical e-commerce order processing example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Customer places an order via OrderService.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;OrderService processes the request and publishes an OrderPlaced event to Kafka.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PaymentService subscribes to OrderPlaced, triggers the payment processing logic, and then publishes PaymentProcessed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;InventoryService subscribes to PaymentProcessed, updates the inventory, and publishes InventoryUpdated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ShippingService subscribes to InventoryUpdated, schedules the delivery, and then publishes ShippingScheduled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;OrderService subscribes to ShippingScheduled, updates the order status, and sends the Order Confirmation to the Customer.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F90c0q982jcbzyskbuar5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F90c0q982jcbzyskbuar5.png" alt="Choreography" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Orchestration vs Event Collaboration for E-commerce Applications: Which One Fits Your Needs?
&lt;/h2&gt;

&lt;p&gt;E-commerce platforms must handle &lt;strong&gt;huge transaction volumes&lt;/strong&gt; reliably and efficiently. Coordinating multiple microservices in such systems requires careful choice between &lt;strong&gt;orchestration&lt;/strong&gt; and &lt;strong&gt;event collaboration (choreography)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let’s compare both approaches in the context of e-commerce:&lt;/p&gt;




&lt;h2&gt;
  
  
  Orchestration: Centralized Control for Complex Workflows
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;When to choose:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your order processing involves multiple dependent steps: payment, inventory reservation, shipping, notifications.
&lt;/li&gt;
&lt;li&gt;You require &lt;strong&gt;strict control&lt;/strong&gt; over workflow execution order.
&lt;/li&gt;
&lt;li&gt;Auditing, retry logic, and failure recovery are critical (e.g., ensuring payment succeeds before shipping).
&lt;/li&gt;
&lt;li&gt;You want clear visibility into the entire order lifecycle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advantages for e-commerce:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simplifies handling complex business rules and transactional workflows.
&lt;/li&gt;
&lt;li&gt;Easier to implement consistent retry, rollback, and compensation logic.
&lt;/li&gt;
&lt;li&gt;Centralized monitoring and logging for compliance and troubleshooting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Challenges:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The orchestrator can become a performance bottleneck under very high load if not well designed.
&lt;/li&gt;
&lt;li&gt;Introduces tighter coupling between services and orchestrator.
&lt;/li&gt;
&lt;li&gt;Requires orchestrator to be scalable and highly available to avoid single points of failure.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Event Collaboration (Choreography): Loose Coupling for Scalability
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;When to choose:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your microservices operate more independently (e.g., inventory updates, user notifications, analytics).
&lt;/li&gt;
&lt;li&gt;You can tolerate &lt;strong&gt;eventual consistency&lt;/strong&gt; between services (e.g., shipping service reacts to payment success event asynchronously).
&lt;/li&gt;
&lt;li&gt;Scalability and fault tolerance are top priorities.
&lt;/li&gt;
&lt;li&gt;You want to reduce tight coupling between services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advantages for e-commerce:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Highly scalable as each service consumes and produces events independently.
&lt;/li&gt;
&lt;li&gt;Failure in one service doesn’t block the entire system.
&lt;/li&gt;
&lt;li&gt;Easier to extend system with new event consumers without changing existing services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Challenges:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complex to track end-to-end workflows and troubleshoot failures.
&lt;/li&gt;
&lt;li&gt;Potential for event storms or duplicate events that need careful handling.
&lt;/li&gt;
&lt;li&gt;Requires robust event monitoring, tracing, and alerting infrastructure.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Orchestration&lt;/th&gt;
&lt;th&gt;Event Collaboration (Choreography)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Centralized, strict workflow control&lt;/td&gt;
&lt;td&gt;Decentralized, loose coupling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consistency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Strong consistency, sequential steps&lt;/td&gt;
&lt;td&gt;Eventual consistency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Can be bottleneck if not designed well&lt;/td&gt;
&lt;td&gt;Highly scalable, services scale independently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fault tolerance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Orchestrator failure impacts flow&lt;/td&gt;
&lt;td&gt;Failure isolated to individual services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monitoring &amp;amp; Debugging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Easier due to centralized control&lt;/td&gt;
&lt;td&gt;Complex, needs strong tracing and monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;If your workflows involve multiple dependent steps like payment authorization, inventory reservation, and shipping scheduling — requiring strict control, auditing, and reliable retries — then orchestration is usually the better approach. For example, a central orchestrator managing events like OrderCreated, ProcessPayment, and ReserveStock ensures the order flow proceeds reliably. Just ensure your orchestrator is designed to be highly scalable and resilient to handle heavy transaction loads without becoming a bottleneck.&lt;/p&gt;

&lt;p&gt;If your system benefits from loose coupling and eventual consistency—such as independent services reacting asynchronously to events like OrderPlaced, PaymentProcessed, and InventoryUpdated—and you want better scalability, then Event Collaboration (Choreography) is preferable. Be prepared to invest in strong event monitoring and tracing to handle the complexity of decentralized workflows.&lt;/p&gt;

&lt;p&gt;Often, a hybrid approach combining orchestration for critical, multi-step processes and choreography for asynchronous, scalable interactions works best to balance reliability and scalability in e-commerce systems.&lt;/p&gt;

</description>
      <category>eventdriven</category>
      <category>microservices</category>
      <category>pubsub</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Orchestration vs. Choreography in EDA: Choosing the Right Approach</title>
      <dc:creator>Dinesh Dunukedeniya</dc:creator>
      <pubDate>Mon, 02 Jun 2025 11:06:08 +0000</pubDate>
      <link>https://dev.to/dinesh_dunukedeniya_539a3/orchestration-vs-choreography-in-eda-choosing-the-right-approach-e1l</link>
      <guid>https://dev.to/dinesh_dunukedeniya_539a3/orchestration-vs-choreography-in-eda-choosing-the-right-approach-e1l</guid>
      <description>&lt;p&gt;In my previous posts, I explored the fundamentals of &lt;strong&gt;orchestration&lt;/strong&gt; and &lt;strong&gt;choreography&lt;/strong&gt; within &lt;strong&gt;Event-Driven Architecture (EDA)&lt;/strong&gt;. These two patterns offer distinct strategies for coordinating microservice interactions.&lt;/p&gt;

&lt;p&gt;This post cuts through the definitions and dives straight into &lt;strong&gt;comparing the two&lt;/strong&gt;, with clear use cases and a side-by-side table to help you decide which pattern fits your needs best.&lt;/p&gt;




&lt;h2&gt;
  
  
  🕺 Choreography: The Decentralized Dance
&lt;/h2&gt;

&lt;p&gt;Also known as &lt;strong&gt;event collaboration&lt;/strong&gt; or &lt;strong&gt;broker-based topology&lt;/strong&gt;, &lt;strong&gt;choreography&lt;/strong&gt; is all about microservices reacting independently to events — no central brain calling the shots.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Benefits:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Highly scalable&lt;/strong&gt; — no central bottlenecks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loosely coupled&lt;/strong&gt; — services are independent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Truly event-driven&lt;/strong&gt; — ideal for real-time systems&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔧 When to Use Choreography
&lt;/h3&gt;

&lt;p&gt;Use it when you want services to respond autonomously to events, especially in &lt;strong&gt;reactive&lt;/strong&gt;, &lt;strong&gt;scalable&lt;/strong&gt;, and &lt;strong&gt;loosely coupled&lt;/strong&gt; systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Common Use Cases
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;🛒 E-Commerce Order Processing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;OrderPlaced&lt;/code&gt; → triggers &lt;code&gt;PaymentService&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PaymentCompleted&lt;/code&gt; → triggers &lt;code&gt;InventoryService&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;StockUpdated&lt;/code&gt; → triggers &lt;code&gt;ShippingService&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each service listens for and reacts to events — no central coordination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔍 Fraud Detection&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;TransactionInitiated&lt;/code&gt; event may trigger multiple fraud checks, logging systems, and alerting processes — all in parallel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;📡 IoT Pipelines&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sensor emits data → multiple services handle analytics, alerting, and storage asynchronously.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎼 Orchestration: The Centralized Conductor
&lt;/h2&gt;

&lt;p&gt;With &lt;strong&gt;orchestration&lt;/strong&gt;, a central service — often called a &lt;strong&gt;Saga Orchestrator&lt;/strong&gt; — directs the process flow step-by-step.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Benefits:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Easier to manage complex workflows&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Centralized error handling and rollback logic&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better observability and debugging&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔧 When to Use Orchestration
&lt;/h3&gt;

&lt;p&gt;Best suited for &lt;strong&gt;transaction-heavy&lt;/strong&gt;, &lt;strong&gt;linear&lt;/strong&gt;, or &lt;strong&gt;strictly ordered&lt;/strong&gt; workflows where the sequence of operations matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Common Use Cases
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;✈️ Travel Booking System&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Book flights → hotels → car rentals. If hotel booking fails, rollback flight reservation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🏦 Loan Approval&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CreditCheck&lt;/code&gt; → &lt;code&gt;FraudCheck&lt;/code&gt; → &lt;code&gt;FinalApproval&lt;/code&gt; — in order.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;📦 Order Fulfillment&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Centralized process: &lt;code&gt;Payment&lt;/code&gt; → &lt;code&gt;Inventory&lt;/code&gt; → &lt;code&gt;Shipping&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 Side-by-Side Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;⚖️ &lt;strong&gt;Aspect&lt;/strong&gt;
&lt;/th&gt;
&lt;th&gt;🕺 &lt;strong&gt;Choreography (Event Collaboration)&lt;/strong&gt;
&lt;/th&gt;
&lt;th&gt;🎼 &lt;strong&gt;Orchestration (Centralized Control)&lt;/strong&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Architecture Style&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Decentralized — services own logic individually&lt;/td&gt;
&lt;td&gt;Centralized controller sequences logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High — services scale independently&lt;/td&gt;
&lt;td&gt;Limited — orchestrator can become a bottleneck&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resilience&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High — one service failure doesn’t impact others&lt;/td&gt;
&lt;td&gt;Lower — unless orchestrator is highly available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Workflow Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Complex — hard to trace entire flow&lt;/td&gt;
&lt;td&gt;Simple — easy to follow, easier to reason about&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Independent — loosely coupled&lt;/td&gt;
&lt;td&gt;Tightly coupled — or even monolithic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monitoring&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires external tracking/observability tools&lt;/td&gt;
&lt;td&gt;Easier — one place to track everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Potentially higher due to async communication&lt;/td&gt;
&lt;td&gt;Typically lower latency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very flexible — services evolve independently&lt;/td&gt;
&lt;td&gt;Less flexible — orchestrator updates needed for changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best Fit For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Long-running, complex, scalable workflows&lt;/td&gt;
&lt;td&gt;Linear, short, transactional workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🧩 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;There's no one-size-fits-all. Choosing between &lt;strong&gt;choreography&lt;/strong&gt; and &lt;strong&gt;orchestration&lt;/strong&gt; depends on your system's goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Need &lt;strong&gt;scalability&lt;/strong&gt;, &lt;strong&gt;loose coupling&lt;/strong&gt;, and &lt;strong&gt;parallel event processing&lt;/strong&gt;? Go with &lt;strong&gt;choreography&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Need &lt;strong&gt;order&lt;/strong&gt;, &lt;strong&gt;simplicity&lt;/strong&gt;, and &lt;strong&gt;centralized control&lt;/strong&gt;? Choose &lt;strong&gt;orchestration&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Design smart — choose based on &lt;strong&gt;use case&lt;/strong&gt;, &lt;strong&gt;team expertise&lt;/strong&gt;, and &lt;strong&gt;operational requirements&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;✍️ &lt;em&gt;Have thoughts or examples from your own architecture? Drop a comment and let’s discuss!&lt;/em&gt;&lt;/p&gt;




</description>
      <category>microservices</category>
      <category>eventdriven</category>
      <category>dotnet</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Event Collaboration in Event-Driven Architecture (EDA)</title>
      <dc:creator>Dinesh Dunukedeniya</dc:creator>
      <pubDate>Tue, 27 May 2025 12:48:12 +0000</pubDate>
      <link>https://dev.to/dinesh_dunukedeniya_539a3/understanding-choreography-in-event-driven-architecture-eda-55pb</link>
      <guid>https://dev.to/dinesh_dunukedeniya_539a3/understanding-choreography-in-event-driven-architecture-eda-55pb</guid>
      <description>&lt;h2&gt;
  
  
  What is Event Collaboration in EDA?
&lt;/h2&gt;

&lt;p&gt;In simple terms, Event Collaboration/ Choreography is a decentralized way for services to communicate through events without a central controller. Think of it like a dance where each participant knows their steps and reacts based on cues from others there’s no single conductor telling everyone what to do.&lt;/p&gt;

&lt;p&gt;In the context of EDA:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Service emit events upon state changes, enabling others to listen and react accordingly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each service listens for relevant events and reacts accordingly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There is no central orchestrator managing the process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each microservice typically maintains its own state based on events it receives.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pattern is sometimes also called Broker Topology, a term popularized by Mark Richards in Software Architecture Patterns (O’Reilly). &lt;br&gt;
The name highlights that services communicate indirectly through a message broker, which routes events but does not control the workflow. &lt;/p&gt;
&lt;h2&gt;
  
  
  How Does Event Collaboration Work?
&lt;/h2&gt;

&lt;p&gt;Imagine an online store scenario:&lt;/p&gt;

&lt;p&gt;Order Service emits an OrderPlaced event.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Inventory Service listens to OrderPlaced, checks stock, and emits StockReserved or StockOut.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Payment Service listens to StockReserved, processes payment, and emits PaymentCompleted.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shipping Service listens to PaymentCompleted and arranges delivery.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each service acts independently, reacting to events and emitting new events for others. The flow emerges naturally from these interactions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4zn42q0xhwfox3j1xc6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4zn42q0xhwfox3j1xc6.png" alt="Choreography" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  When to Use Event Collaboration in Event-Driven Architecture
&lt;/h2&gt;

&lt;p&gt;Event coloboration is a powerful architectural pattern, but it’s not the perfect fit for every scenario. Consider using choreography when:&lt;/p&gt;

&lt;p&gt;✅ You Want Loose Coupling Between Services&lt;br&gt;
Each service operates independently, only knowing about events, not about the other services. This reduces dependencies and allows teams to work autonomously.&lt;/p&gt;

&lt;p&gt;✅ Your System Is Highly Distributed&lt;br&gt;
In microservices or cloud-native architectures where services are deployed separately, choreography supports scalability and flexibility.&lt;/p&gt;

&lt;p&gt;✅ You Need Asynchronous, Event-Driven Workflows&lt;br&gt;
If your business processes naturally fit event streams (e.g., order processing, payment events), choreography enables responsive and resilient flow&lt;/p&gt;

&lt;p&gt;✅ You Expect to Scale or Evolve Services Independently&lt;br&gt;
Since services produce and consume events without tight integration, you can add or update services with minimal impact on others.&lt;/p&gt;

&lt;p&gt;✅ You Can Invest in Observability and Monitoring&lt;br&gt;
Because choreography lacks a central controller, you need good tooling for tracing, logging, and monitoring event flows across services.&lt;/p&gt;
&lt;h2&gt;
  
  
  Avoid
&lt;/h2&gt;

&lt;p&gt;⚠️ If you require strict, centralized control over business workflows, orchestration might be a better fit.&lt;/p&gt;

&lt;p&gt;⚠️ When transactional consistency is critical across services and must be tightly managed.&lt;/p&gt;

&lt;p&gt;⚠️ If your team is new to distributed event-driven systems and you want simpler debugging and error handling initially.&lt;/p&gt;
&lt;h2&gt;
  
  
  Benefits of Event Collaboration
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Loose Coupling: Services only know about event formats, not about each other directly. This simplifies changes and scaling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scalability: Services can be developed, deployed, and scaled independently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flexibility: New services can join by simply subscribing to events.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Resilience: Failure in one service doesn’t block the whole process.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Challenges to Consider
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Visibility &amp;amp; Monitoring: Without a central controller, tracking the overall process flow requires dedicated observability tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Complexity in Debugging: Understanding event chains can be tricky when many services interact asynchronously.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consistency: Ensuring data consistency across services can be more challenging without orchestration.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Implementing the Choreography Pattern in .NET Microservices with Kafka
&lt;/h2&gt;

&lt;p&gt;In a choreography-based event-driven system, each microservice reacts to events and, if needed, emits its own events. This allows services to be highly autonomous and loosely coupled.&lt;/p&gt;

&lt;p&gt;Since all microservices are ASP.NET Core web apps, Kafka event consumers are implemented using BackgroundService (hosted services) within each app.&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Microservices Involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Order Service&lt;br&gt;
Initiates the workflow by publishing OrderPlaced events.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inventory Service&lt;br&gt;
Listens for OrderPlaced events, checks stock, and publishes either InventoryReserved or InventoryReservationFailed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Payment Service&lt;br&gt;
Listens for InventoryReserved and processes payment, publishing PaymentProcessed or PaymentFailed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shipping Service&lt;br&gt;
Listens for PaymentProcessed events and handles shipping.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Each Microservice Includes:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Kafka Consumer Background Service&lt;br&gt;
Subscribes to relevant Kafka topics and handles incoming events.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kafka Producer Service&lt;br&gt;
Publishes events to Kafka topics to trigger downstream service actions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Controller/Endpoints (Optional)&lt;br&gt;
For internal testing, admin access, or debug operations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Domain Models (Shared)&lt;br&gt;
All services reference a shared contracts library to ensure consistent event models.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class OrderPlaced
{
    public string OrderId { get; set; }
    public string ProductId { get; set; }
    public int Quantity { get; set; }
    public DateTime OrderDate { get; set; }
}

public class InventoryReserved
{
    public string OrderId { get; set; }
}

public class InventoryReservationFailed
{
    public string OrderId { get; set; }
    public string Reason { get; set; }
}

public class PaymentProcessed
{
    public string OrderId { get; set; }
}

public class PaymentFailed
{
    public string OrderId { get; set; }
    public string Reason { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Kafka Producer Service
Each service needs a way to publish events to Kafka. This reusable service wraps the Kafka producer logic.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// KafkaProducerService.cs
public interface IKafkaProducerService
{
    Task ProduceAsync&amp;lt;T&amp;gt;(string topic, T message);
}

public class KafkaProducerService : IKafkaProducerService
{
    private readonly IProducer&amp;lt;Null, string&amp;gt; _producer;

    public KafkaProducerService(IConfiguration configuration)
    {
        var config = new ProducerConfig
        {
            BootstrapServers = configuration["Kafka:BootstrapServers"]
        };
        _producer = new ProducerBuilder&amp;lt;Null, string&amp;gt;(config).Build();
    }

    public async Task ProduceAsync&amp;lt;T&amp;gt;(string topic, T message)
    {
        var json = JsonSerializer.Serialize(message);
        await _producer.ProduceAsync(topic, new Message&amp;lt;Null, string&amp;gt; { Value = json });
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Kafka Consumer BackgroundService
Each service implements a BackgroundService that listens for specific events.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example for InventoryService consuming OrderPlaced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// OrderPlacedConsumer.cs
public class OrderPlacedConsumer : BackgroundService
{
    private readonly IConfiguration _configuration;
    private readonly IKafkaProducerService _producer;

    public OrderPlacedConsumer(IConfiguration configuration, IKafkaProducerService producer)
    {
        _configuration = configuration;
        _producer = producer;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var config = new ConsumerConfig
        {
            BootstrapServers = _configuration["Kafka:BootstrapServers"],
            GroupId = "inventory-service",
            AutoOffsetReset = AutoOffsetReset.Earliest
        };

        using var consumer = new ConsumerBuilder&amp;lt;Ignore, string&amp;gt;(config).Build();
        consumer.Subscribe("order-placed");

        while (!stoppingToken.IsCancellationRequested)
        {
            var result = consumer.Consume(stoppingToken);
            var order = JsonSerializer.Deserialize&amp;lt;OrderPlaced&amp;gt;(result.Message.Value);

            // Simulated logic
            var isAvailable = true; // simulate stock check

            if (isAvailable)
            {
                await _producer.ProduceAsync("inventory-reserved", new InventoryReserved { OrderId = order.OrderId });
            }
            else
            {
                await _producer.ProduceAsync("inventory-failed", new InventoryReservationFailed
                {
                    OrderId = order.OrderId,
                    Reason = "Out of stock"
                });
            }
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Payment Service Consumer
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class InventoryReservedConsumer : BackgroundService
{
    private readonly IKafkaProducerService _producer;
    private readonly IConfiguration _configuration;

    public InventoryReservedConsumer(IKafkaProducerService producer, IConfiguration configuration)
    {
        _producer = producer;
        _configuration = configuration;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var config = new ConsumerConfig
        {
            BootstrapServers = _configuration["Kafka:BootstrapServers"],
            GroupId = "payment-service",
            AutoOffsetReset = AutoOffsetReset.Earliest
        };

        using var consumer = new ConsumerBuilder&amp;lt;Ignore, string&amp;gt;(config).Build();
        consumer.Subscribe("inventory-reserved");

        while (!stoppingToken.IsCancellationRequested)
        {
            var result = consumer.Consume(stoppingToken);
            var evt = JsonSerializer.Deserialize&amp;lt;InventoryReserved&amp;gt;(result.Message.Value);

            // Simulated payment logic
            var success = true;

            if (success)
            {
                await _producer.ProduceAsync("payment-processed", new PaymentProcessed { OrderId = evt.OrderId });
            }
            else
            {
                await _producer.ProduceAsync("payment-failed", new PaymentFailed
                {
                    OrderId = evt.OrderId,
                    Reason = "Card declined"
                });
            }
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Event Collaboration in EDA promotes a scalable, flexible, and resilient architecture by allowing services to communicate asynchronously and independently through events. While it introduces complexity in monitoring and debugging, with the right tools and practices, choreography can be a powerful pattern for modern distributed systems.&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>eventdriven</category>
      <category>kafka</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Mediator Pattern in Event-Driven Architecture (EDA)</title>
      <dc:creator>Dinesh Dunukedeniya</dc:creator>
      <pubDate>Fri, 23 May 2025 12:00:24 +0000</pubDate>
      <link>https://dev.to/dinesh_dunukedeniya_539a3/mediator-pattern-in-event-driven-architecture-eda-2l30</link>
      <guid>https://dev.to/dinesh_dunukedeniya_539a3/mediator-pattern-in-event-driven-architecture-eda-2l30</guid>
      <description>&lt;p&gt;In Event-Driven Architecture (EDA), microservices communicate by publishing and subscribing to events, enabling loose coupling and asynchronous processing. However, as systems grow in complexity, managing interactions across numerous services can become challenging.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Mediator Pattern&lt;/strong&gt; introduces an intermediary component — the &lt;em&gt;mediator&lt;/em&gt; — that manages and orchestrates this communication. Rather than having services directly consume and respond to each other’s events, a dedicated mediator service listens to incoming events, applies business rules, and then routes or transforms messages as needed before passing them on to downstream services.&lt;/p&gt;

&lt;p&gt;This pattern is sometimes referred to as the orchestration pattern or a central workflow engine, where a single component controls and coordinates the interactions between services.&lt;/p&gt;

&lt;p&gt;This pattern is particularly useful in &lt;strong&gt;complex business workflows&lt;/strong&gt; involving multiple microservices. By centralizing coordination logic within the mediator, you gain better control over the execution flow, simplify service responsibilities, reduce inter-service dependencies, and improve overall system maintainability.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3h18kvjj8md04bmw9zv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3h18kvjj8md04bmw9zv.png" alt="Mediator Pattern " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use the Mediator Pattern
&lt;/h2&gt;

&lt;p&gt;✅ Complex workflows requiring sequencing and coordination.Especially useful in implementing the Saga Pattern or multi-step business processes involving multiple services &lt;/p&gt;

&lt;p&gt;✅ Centralized logging, monitoring, and tracing. Gain visibility into the entire workflow execution from a single place, useful for debugging and observability.&lt;/p&gt;

&lt;p&gt;✅ Reducing point-to-point event subscriptions between services&lt;br&gt;
Avoid a tangled web of services subscribing to each other’s events, which becomes hard to manage and evolve.&lt;/p&gt;

&lt;p&gt;✅ Business rules that span multiple services. Keep cross-cutting concerns (e.g., validation, routing decisions) in one place rather than duplicating them.&lt;/p&gt;

&lt;p&gt;✅ Dynamic or conditional logic between steps. When the path of execution depends on business logic or the results of previous steps (e.g., retry logic, fallback strategies).&lt;/p&gt;

&lt;p&gt;✅ Orchestrated error handling and compensations.Centralize retry strategies, failure handling, and compensating actions in distributed transactions.&lt;/p&gt;

&lt;p&gt;✅ Auditing and compliance. A mediator can capture and persist event flows required for regulatory purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid
&lt;/h2&gt;

&lt;p&gt;⚠️ Avoid if your system has simple, independent events.&lt;/p&gt;

&lt;p&gt;⚠️ High-throughput, low-latency systems. Mediators can introduce bottlenecks or delays if not designed with performance in mind.&lt;/p&gt;

&lt;p&gt;⚠️ Use cases better suited to choreography. For loosely coupled systems where services can autonomously react to events, a pure event-driven approach with no central orchestrator may be more appropriate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the Mediator Pattern in .NET Microservices with Kafka
&lt;/h2&gt;

&lt;p&gt;Let’s walk through how to implement the Mediator Pattern using Kafka in a .NET-based microservices architecture. In our setup, all microservices are web apps, meaning that to consume Kafka events, we need to use background services (hosted services) within each app to listen and process messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Microservices Involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Client Web App&lt;br&gt;
Initiates the workflow by publishing OrderPlaced events.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mediator Service&lt;br&gt;
Acts as the central orchestrator. It reacts to events, applies business rules, and triggers the next step by publishing new events.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inventory Service&lt;br&gt;
Listens for ReserveInventory events and responds with either InventoryReserved or InventoryReservationFailed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Payment Service&lt;br&gt;
Listens for ProcessPayment events and responds with PaymentProcessed or PaymentFailed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shipping Service&lt;br&gt;
Listens for OrderConfirmed events and proceeds with shipping logistics.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Design Principles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Decoupling: Services do not communicate directly; all communication flows through Kafka topics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Orchestration: Only the Mediator knows the full business workflow. Each other service handles only its own concern.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Background Tasks: Since all services are ASP.NET Core web apps, Kafka consumers are implemented using BackgroundService.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Each Microservice Includes:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Kafka Consumer Background Task&lt;br&gt;
Subscribes to relevant topics and handles incoming events.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kafka Producer Service&lt;br&gt;
Sends events to Kafka topics to trigger actions in other services.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Controller/Endpoints (if needed)&lt;br&gt;
Mostly for internal APIs or admin/debug access. Not used for orchestration logic.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Domain Models
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;OrderPlaced&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PaymentInfo&lt;/span&gt; &lt;span class="n"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ReserveInventory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;InventoryReserved&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;InventoryReservationFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Reason&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ProcessPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PaymentInfo&lt;/span&gt; &lt;span class="n"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;PaymentProcessed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;PaymentFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Reason&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;OrderConfirmed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;PaymentInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;CardNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Kafka Producer Helper
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;KafkaProducer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IProducer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_producer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;KafkaProducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;bootstrapServers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ProducerConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;BootstrapServers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bootstrapServers&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="n"&gt;_producer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ProducerBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="n"&gt;ProduceAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonConvert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProduceAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Mediator Service Background Consumers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  OrderPlacedConsumer.cs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderPlacedConsumer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_consumer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;KafkaProducer&lt;/span&gt; &lt;span class="n"&gt;_producer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderPlacedConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;OrderPlacedConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KafkaProducer&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderPlacedConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_producer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ConsumerConfig&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;BootstrapServers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"localhost:9092"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;GroupId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mediator-group"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;AutoOffsetReset&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoOffsetReset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Earliest&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="n"&gt;_consumer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ConsumerBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OrderPlaced"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orderPlaced&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonConvert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeserializeObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderPlaced&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"OrderPlaced received: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;orderPlaced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="c1"&gt;// Orchestration step: send ReserveInventory event&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reserveInventory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ReserveInventory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderPlaced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderPlaced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProduceAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ReserveInventory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reserveInventory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  InventoryResultConsumer.cs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InventoryResultConsumer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_consumer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;KafkaProducer&lt;/span&gt; &lt;span class="n"&gt;_producer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InventoryResultConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;InventoryResultConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KafkaProducer&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InventoryResultConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_producer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ConsumerConfig&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;BootstrapServers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"localhost:9092"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;GroupId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mediator-group"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;AutoOffsetReset&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoOffsetReset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Earliest&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="n"&gt;_consumer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ConsumerBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"InventoryReserved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"InventoryReservationFailed"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"InventoryReservationFailed"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fail&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonConvert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeserializeObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InventoryReservationFailed&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Inventory reservation failed for Order &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="c1"&gt;// Handle failure (notify, rollback, etc.)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reserved&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonConvert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeserializeObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InventoryReserved&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Inventory reserved for Order &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;reserved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                    &lt;span class="c1"&gt;// Retrieve payment info from some store (simulate here)&lt;/span&gt;
                    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;paymentInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PaymentInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1234-5678-9012-3456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ProcessPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reserved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;paymentInfo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProduceAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ProcessPayment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  PaymentResultConsumer.cs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentResultConsumer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_consumer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;KafkaProducer&lt;/span&gt; &lt;span class="n"&gt;_producer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaymentResultConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;PaymentResultConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KafkaProducer&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaymentResultConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_producer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ConsumerConfig&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;BootstrapServers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"localhost:9092"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;GroupId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mediator-group"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;AutoOffsetReset&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoOffsetReset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Earliest&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="n"&gt;_consumer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ConsumerBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"PaymentProcessed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"PaymentFailed"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PaymentFailed"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fail&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonConvert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeserializeObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaymentFailed&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Payment failed for Order &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="c1"&gt;// Handle failure (e.g., rollback inventory)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;processed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonConvert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeserializeObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaymentProcessed&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Payment processed for Order &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orderConfirmed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderConfirmed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProduceAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OrderConfirmed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderConfirmed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Program.cs (Register services and run)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;KafkaProducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localhost:9092"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderPlacedConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InventoryResultConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaymentResultConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Mediator service&lt;/strong&gt; has three background consumers, each processing different event types&lt;/li&gt;
&lt;li&gt;Each consumer applies the &lt;strong&gt;orchestration logic&lt;/strong&gt; and triggers the next step by producing the next event&lt;/li&gt;
&lt;li&gt;This keeps your microservices &lt;strong&gt;loosely coupled&lt;/strong&gt; while having a central place for business workflow orchestration&lt;/li&gt;
&lt;li&gt;You can extend this with &lt;strong&gt;state management&lt;/strong&gt; (e.g., Redis, DB) for reliability and retries.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>eventdriven</category>
      <category>kafka</category>
      <category>dotnet</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Kafka vs Traditional Message Buses: Why Kafka Wins for Highly Scalable Systems</title>
      <dc:creator>Dinesh Dunukedeniya</dc:creator>
      <pubDate>Fri, 23 May 2025 10:20:24 +0000</pubDate>
      <link>https://dev.to/dinesh_dunukedeniya_539a3/kafka-vs-traditional-message-buses-why-kafka-wins-for-highly-scalable-systems-15mk</link>
      <guid>https://dev.to/dinesh_dunukedeniya_539a3/kafka-vs-traditional-message-buses-why-kafka-wins-for-highly-scalable-systems-15mk</guid>
      <description>&lt;p&gt;In the world of distributed systems and microservices, choosing the right messaging infrastructure is critical for achieving scalability, reliability, and performance. While traditional message buses like RabbitMQ, ActiveMQ, or MSMQ have been popular for years, Apache Kafka has emerged as the go-to solution for building highly scalable and resilient systems.&lt;/p&gt;

&lt;p&gt;In this post, we'll explore the fundamental differences between Kafka and traditional message buses, and why Kafka excels in high-scale environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Traditional Message Bus?
&lt;/h2&gt;

&lt;p&gt;Traditional message buses are messaging middleware designed to facilitate communication between different parts of a system, often using message queues and topics. These systems usually emphasize:&lt;/p&gt;

&lt;p&gt;Message delivery guarantees: at-least-once or at-most-once delivery&lt;/p&gt;

&lt;p&gt;Routing and filtering: selective delivery to subscribers&lt;/p&gt;

&lt;p&gt;In-memory or disk-based queues with limited retention times&lt;/p&gt;

&lt;p&gt;Broker-centric architecture: brokers manage message routing and storage&lt;/p&gt;

&lt;p&gt;Examples include RabbitMQ, IBM MQ, and Azure Service Bus.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Kafka?
&lt;/h2&gt;

&lt;p&gt;Kafka is a distributed event streaming platform designed for high-throughput, fault-tolerant, and scalable data pipelines. Key features include:&lt;/p&gt;

&lt;p&gt;Partitioned logs: Kafka topics are split into partitions, which can be processed in parallel.&lt;/p&gt;

&lt;p&gt;Durable storage: Messages are stored on disk for configurable retention periods, allowing consumers to re-read data.&lt;/p&gt;

&lt;p&gt;Consumer-managed offsets: Consumers control their read position, enabling replay and flexible processing.&lt;/p&gt;

&lt;p&gt;Horizontal scalability: Kafka brokers can be added to scale throughput seamlessly.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
