<?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.us-east-2.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>Is Your Microservice Safe from AI Agents? Rethinking Security in the Age of Agentic AI</title>
      <dc:creator>Dinesh Dunukedeniya</dc:creator>
      <pubDate>Fri, 30 Oct 2026 12:02:44 +0000</pubDate>
      <link>https://dev.to/dinesh_dunukedeniya_539a3/is-your-microservice-safe-from-ai-agents-rethinking-security-in-the-age-of-agentic-ai-3ho8</link>
      <guid>https://dev.to/dinesh_dunukedeniya_539a3/is-your-microservice-safe-from-ai-agents-rethinking-security-in-the-age-of-agentic-ai-3ho8</guid>
      <description>&lt;p&gt;For years, we've designed microservice architectures around a relatively simple assumption:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An authenticated user interacting with an application is generally the person making decisions and initiating business operations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our security models reflect this assumption. We authenticate users, issue access tokens, validate permissions, enforce business rules and allow authorised operations to proceed.&lt;/p&gt;

&lt;p&gt;But what happens when an AI agent starts interacting with our applications on behalf of users?&lt;/p&gt;

&lt;p&gt;This is not a niche concern. Agents integrated through the Model Context Protocol (MCP), agents holding delegated OAuth access, AI tool integrations embedded in platforms like Slack or GitHub, and AI assistants operating through an already authenticated browser session all expose the same architectural weakness. In each case, the agent presents valid credentials and passes every permission check and the backend has no way to tell whether the operation reflects a genuine user decision.&lt;/p&gt;

&lt;p&gt;Some of these cases at least give us something to work with. An MCP integration, for example, gives us a registered client identity and a scoped token we can inspect and constrain though MCP's authorisation model is still maturing, and enforcing those scopes at the granularity that matters remains genuinely hard in practice. A browser session gives us none of that. There, the agent acts entirely inside the user's existing authenticated context, and the requests are indistinguishable from the user's own.&lt;/p&gt;

&lt;p&gt;But the distinction matters less than it first appears. In every case, the token grants a user's permissions across a range of operations, and the backend executes each one on request. Permissions describe &lt;em&gt;what the user is allowed to do&lt;/em&gt;. They do not describe &lt;em&gt;what the user actually intended this time&lt;/em&gt;. Scoped permissions narrow the surface; they do not supply intent.&lt;/p&gt;

&lt;p&gt;From the application's perspective, everything appears normal. The requests contain valid credentials. The user is authenticated. The requested operations are permitted.&lt;/p&gt;

&lt;p&gt;Yet there is a fundamental problem. The challenge is not authentication failure. The challenge is not authorisation failure. The challenge is intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Invisible Agent Problem
&lt;/h2&gt;

&lt;p&gt;I call this the &lt;strong&gt;Invisible Agent Problem&lt;/strong&gt;: a condition where an AI agent performs actions through a user's authenticated session, making the resulting requests technically indistinguishable from actions the user initiated directly. The browser case makes the agent invisible; the architectural gap it exposes authorisation without intent is broader.&lt;/p&gt;

&lt;p&gt;Here's what it looks like in practice.&lt;/p&gt;

&lt;p&gt;Imagine a trading application. You authenticate, open your trading account, and ask an AI assistant to help manage your investments. The AI assistant has access to your authenticated browser session and can interact with the application using the same permissions and context available to you.&lt;/p&gt;

&lt;p&gt;You give it a straightforward instruction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Buy $10,000 worth of Anthropic shares."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI agent navigates to the trading page, enters the transaction details, and submits the request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /api/trades/buy
Authorization: Bearer &amp;lt;user-access-token&amp;gt;
Content-Type: application/json

{
  "instrument": "ANTHROPIC",
  "amount": 10000
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your backend performs its normal validations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the access token valid?&lt;/li&gt;
&lt;li&gt;Is the user authenticated?&lt;/li&gt;
&lt;li&gt;Does the user have permission to trade?&lt;/li&gt;
&lt;li&gt;Does the user have sufficient available funds?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything works as expected until the final validation. The account does not have sufficient funds, and the backend returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INSUFFICIENT_BALANCE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Insufficient available balance to complete this trade."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the application has done exactly what it was designed to do.&lt;/p&gt;

&lt;p&gt;But the AI agent doesn't necessarily stop. It interprets the error and decides it needs to free up funds before completing the purchase. It navigates to the sell page, reviews the user's portfolio, identifies a SpaceX holding, and decides to sell it.&lt;/p&gt;

&lt;p&gt;The user never requested this. The AI agent did.&lt;/p&gt;

&lt;p&gt;It submits another request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /api/trades/sell
Authorization: Bearer &amp;lt;user-access-token&amp;gt;
Content-Type: application/json

{
  "instrument": "SPACEX",
  "quantity": "ALL"
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend receives the request. The access token is valid. The user has permission to sell shares. The user owns the SpaceX shares. All business validations succeed. The sale is accepted.&lt;/p&gt;

&lt;p&gt;The AI agent has now sold an investment that the user never instructed it to sell. It may then retry the original purchase.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A["User Instruction&amp;lt;br/&amp;gt;'Buy $10k of Anthropic'"]
    B["AI Agent&amp;lt;br/&amp;gt;interprets goal"]
    C["Request: BUY ANTHROPIC"]
    D["Backend: INSUFFICIENT FUNDS"]
    E["AI Agent decides to free up funds&amp;lt;br/&amp;gt;(never instructed)"]
    F["Request: SELL SPACEX"]
    G["Backend: APPROVED&amp;lt;br/&amp;gt;valid token, valid permission"]

    A --&amp;gt; B --&amp;gt; C --&amp;gt; D --&amp;gt; E --&amp;gt; F --&amp;gt; G

    style E fill:#f8d7da,stroke:#dc3545
    style G fill:#d4edda,stroke:#28a745&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The browser session makes this scenario easy to picture, but nothing about it depends on the browser. The same sequence valid credentials, valid permissions, unintended outcome plays out identically for an MCP tool call or an agent holding a delegated token.&lt;/p&gt;

&lt;p&gt;From the backend's perspective, both transactions were properly authenticated and authorised. From the user's perspective, neither represented an explicitly approved investment decision. The backend validated permissions, but it never validated intent.&lt;/p&gt;

&lt;p&gt;To be clear, this example is intentionally simplified. The AI agent is not assumed to be malicious. It is simply attempting to achieve the user's goal using the permissions already available through the authenticated session. The concern is not hostile behaviour. The concern is autonomous decision-making that extends beyond the user's explicit instruction.&lt;/p&gt;

&lt;p&gt;This is where traditional microservice security assumptions start to break down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Existing Controls Don't Catch This
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Validation Check&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Is the access token valid?&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the user authenticated?&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can the user buy shares?&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can the user sell shares?&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does the user own the SpaceX shares?&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the sell transaction valid?&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every individual API request passes its security checks. So what went wrong?&lt;/p&gt;

&lt;p&gt;The backend only sees two independent business operations: sell SpaceX shares, and buy Anthropic shares. It has no knowledge that the original instruction was "Buy $10,000 worth of Anthropic shares." It also has no visibility into the AI agent's decision to sell another asset as a workaround.&lt;/p&gt;

&lt;p&gt;Traditional authorisation answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this user allowed to perform this operation?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But it doesn't necessarily answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did the user actually intend to perform this operation?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction becomes extremely important when AI agents can autonomously interpret instructions, make decisions, and execute multiple operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why More Permissions Are Not the Answer
&lt;/h2&gt;

&lt;p&gt;The obvious reflex is to introduce separate permissions for AI agents. Humans can buy and sell; AI agents can only view portfolios. But as the opening of this article argued, scoped permissions narrow the surface without supplying intent and in the browser based case, we often cannot even reliably identify when an agent is operating, because it acts through the user's existing authenticated identity.&lt;/p&gt;

&lt;p&gt;The challenge is not necessarily that the user lacks permission. The challenge is that a valid user identity and a valid permission do not prove that a specific business operation was deliberately authorised by the user. We need to rethink where and how authorisation is enforced and we need to shrink the agent's agency before we ask a human to approve what remains.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rethinking Security: Bound the Task, Then Obtain Intent
&lt;/h2&gt;

&lt;p&gt;OWASP's guidance on excessive agency is a useful starting point. It breaks the problem into three root causes excessive functionality, excessive permissions, and excessive autonomy and the recommended order of remediation matters . Before you put a human approval prompt in front of an operation, you should first ensure the agent never had the capability to perform it in the first place.&lt;/p&gt;

&lt;p&gt;This is the principle of &lt;strong&gt;least agency&lt;/strong&gt;: give the agent only the tools, permissions, and autonomy the task requires, and nothing more . In our trading example, the user's instruction was "buy Anthropic." A least agency design would bound the agent to buy-only operations, with a spending limit derived from the instruction. The agent would never have had the ability to submit a sell request at all, not because a policy check blocked it, but because the tool was never exposed and the credential never carried that scope.&lt;/p&gt;

&lt;p&gt;Least agency is prevention. Approval is the backstop for what remains. Both are necessary, but they address different failure modes. Least agency reduces the blast radius of a compromised or drifting agent; approval catches the cases where a permitted operation still exceeds what the user would have wanted.&lt;/p&gt;

&lt;p&gt;For the backstop, the principle is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A valid authenticated session should not automatically be treated as sufficient authorisation for every sensitive business operation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Consider the trading application again.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Security Requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;View portfolio&lt;/td&gt;
&lt;td&gt;Existing authentication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View transaction history&lt;/td&gt;
&lt;td&gt;Existing authentication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buy shares&lt;/td&gt;
&lt;td&gt;Risk-based authorisation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sell shares&lt;/td&gt;
&lt;td&gt;Risk-based authorisation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transfer funds&lt;/td&gt;
&lt;td&gt;Strong independent approval&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice that the application does not need to determine whether the request came from a human or an AI agent. The same policy applies to both. For sensitive operations, the backend requires an additional authorisation step before execution.&lt;/p&gt;

&lt;p&gt;That said, "additional authorisation" is a spectrum, not a single mechanism. Step-up authentication, transaction signing, policy-based re-authorisation, and out-of-band approval all sit on it. The right choice depends on how consequential the operation is and what regulatory context you operate in. For high-impact operations moving money, changing ownership, granting access the strongest option is explicit, transaction bound approval by the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Mandate to Approval: Two Layers of Intent Enforcement
&lt;/h2&gt;

&lt;p&gt;The standards community is converging on a two-layer model that maps cleanly onto the least-agency-then-approval sequence.&lt;/p&gt;

&lt;p&gt;The first layer is the &lt;strong&gt;signed mandate&lt;/strong&gt;. NIST's design tenets for agent identity management call for "cryptographically signed mandate statements that define intended outcomes and constraints for agentic actions," verifiable by downstream services . In the payments space, Google's Agent Payments Protocol (AP2) implements this as an Intent Mandate  a signed statement of what the user authorised the agent to do, and under what conditions followed by a Cart Mandate that records what the agent actually did . The FIDO Alliance is now developing standards in this area, drawing on AP2 and Mastercard's Verifiable Intent framework .&lt;/p&gt;

&lt;p&gt;The architectural implication is significant. If the agent's task carries a signed mandate saying "buy up to $10,000 of Anthropic, no sells," the backend can reject a sell request before it ever reaches an approval prompt. The mandate bounds the agent's authority at the task level; approval handles operations the mandate permits but that exceed risk thresholds.&lt;/p&gt;

&lt;p&gt;The second layer is the approval mechanism itself. This article focuses on that layer, because it is where the operational gap is widest in existing microservice architectures. But the mandate is the stronger control; it encodes intent at the moment of delegation, not at the moment of execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using CIBA for Independent User Authorisation
&lt;/h2&gt;

&lt;p&gt;One mechanism for the approval layer is Client-Initiated Backchannel Authentication (CIBA). CIBA enables a client to initiate an authentication request through a backchannel, allowing the user to authenticate and approve actions using a separate device. Unlike traditional browser-based authentication, the approval interaction does not need to happen in the browser that initiated the transaction.&lt;/p&gt;

&lt;p&gt;CIBA is not the only option, push notification with a signed challenge, step-up authentication with transaction binding, or a dedicated approval service can all serve the same purpose. What matters architecturally is that the approval happens on a channel the agent cannot reach, and that it is bound to the specific operation. CIBA is simply a well-standardised primitive for the backchannel part.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Authorising a Share Transaction
&lt;/h3&gt;

&lt;p&gt;An AI agent submits a request to sell SpaceX shares. The backend performs its normal checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate authentication.&lt;/li&gt;
&lt;li&gt;Validate permissions.&lt;/li&gt;
&lt;li&gt;Validate transaction details.&lt;/li&gt;
&lt;li&gt;Evaluate authorisation requirements.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The backend determines that additional approval is required. Instead of executing the transaction immediately, it initiates an out-of-band approval process. The user receives a notification on their registered mobile device:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Transaction Approval Required

Operation: SELL
Instrument: SpaceX
Quantity: 100 Shares
Account: Trading Account
Source: Trading Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user can review the details and explicitly approve or reject the transaction. Only after that approval is verified does the transaction proceed.&lt;/p&gt;

&lt;p&gt;The important point is that the user is approving a specific business operation, not simply authenticating again. We cannot infer intent from a token. For sensitive operations, we must obtain it explicitly.&lt;/p&gt;

&lt;p&gt;A note of caution: this shifts the trust boundary to the approval channel. If the agent can reach the notification, manipulate the approval UI, or act on a compromised device, the control is defeated. The approval channel must be independently secured — out of reach of the agent, and ideally out of reach of the same browser session that initiated the request. OWASP's agentic threat model explicitly lists "Overwhelming the Human in the Loop" as a risk category, precisely because approval prompts that fire too often degrade into rubber stamps .&lt;/p&gt;

&lt;h2&gt;
  
  
  Transaction-Specific Authorisation Is Already Emerging
&lt;/h2&gt;

&lt;p&gt;The broader identity and API security community has already recognised that some operations require more context than a generic access token can provide. Standards and approaches such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OAuth 2.0 Rich Authorization Requests (RAR)&lt;/li&gt;
&lt;li&gt;Financial-grade API (FAPI) profiles&lt;/li&gt;
&lt;li&gt;Transaction-authorisation patterns used in banking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;move towards a model where authorisation can be associated with a specific business transaction rather than simply granting broad access to an application.&lt;/p&gt;

&lt;p&gt;However, these approaches do not automatically solve the Invisible Agent Problem. If a user is authorised to buy and sell shares, an AI agent operating through the user's authenticated session can still potentially execute those actions.&lt;/p&gt;

&lt;p&gt;The challenge is not simply expressing richer authorisation requirements. The challenge is ensuring that sensitive business operations receive explicit approval that is bound to the specific transaction being executed.&lt;/p&gt;

&lt;p&gt;In other words, an access token may prove that a user can sell shares. It does not necessarily prove that the user intended to sell &lt;em&gt;these shares, in this quantity, at this point in time&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Agentic AI makes this distinction increasingly important because autonomous systems can combine multiple individually authorised actions into outcomes that the user never explicitly approved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Binding Approval to the Transaction
&lt;/h2&gt;

&lt;p&gt;Approval alone is not a complete transaction-authorisation solution. The application must ensure that approval is securely bound to the exact transaction being executed.&lt;/p&gt;

&lt;p&gt;For example, approval for "Sell 100 SpaceX shares" must not be reusable for "Sell 500 SpaceX shares" or "Transfer funds to another account."&lt;/p&gt;

&lt;p&gt;The backend should validate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transaction identifier&lt;/li&gt;
&lt;li&gt;Account identifier&lt;/li&gt;
&lt;li&gt;Operation type&lt;/li&gt;
&lt;li&gt;Instrument&lt;/li&gt;
&lt;li&gt;Amount or quantity&lt;/li&gt;
&lt;li&gt;Approval expiry&lt;/li&gt;
&lt;li&gt;Single-use status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents modification, replay, and misuse of previously approved transactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Risk-Based Authorisation
&lt;/h2&gt;

&lt;p&gt;We also need to balance security and usability. Requiring explicit approval for every API call would create unnecessary friction. A more practical approach is risk-based authorisation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Low-risk operations&lt;/strong&gt; — view balances, view portfolios, retrieve transaction history. Existing authentication may be sufficient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Medium-risk operations&lt;/strong&gt; — buy shares, sell shares, create recurring trades. Additional approval may depend on transaction value or risk indicators.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High-risk operations&lt;/strong&gt; — transfer funds, change ownership details, add payment destinations. Independent approval may always be required.&lt;/p&gt;

&lt;p&gt;The exact policies depend on the business domain and regulatory requirements. The important point is that the policies are enforced by the backend, not by the browser or the AI agent.&lt;/p&gt;

&lt;p&gt;Two practical cautions. First, &lt;strong&gt;approval fatigue is real&lt;/strong&gt;: users who are prompted too often will tap "approve" reflexively, and the control degrades into a rubber stamp. Risk tiering exists precisely to keep the prompt rate low enough that each prompt is meaningful. Second, &lt;strong&gt;the policy itself is a governed artefact&lt;/strong&gt;: who is allowed to mark an operation low-risk, and how are changes to that classification audited? An approval layer whose own configuration is unprotected simply moves the gap rather than closing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unintended Behaviour Versus Compromised Behaviour
&lt;/h2&gt;

&lt;p&gt;When discussing AI agents, it is useful to distinguish between two different risk categories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Intent drift.&lt;/strong&gt; The AI agent genuinely attempts to achieve the user's objective but independently performs actions that were never explicitly authorised. In our example, selling SpaceX shares to fund an Anthropic purchase is intent drift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent compromise.&lt;/strong&gt; The AI agent is influenced by prompt injection, malicious content, browser compromise, or another attack. Examples might include transferring funds, changing account settings, or executing unauthorised transactions.&lt;/p&gt;

&lt;p&gt;The causes differ, but both expose the same architectural weakness:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The backend validates identity and permissions but lacks independent confirmation that the specific business operation should be executed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Architectural Considerations for Existing Microservices
&lt;/h2&gt;

&lt;p&gt;Introducing this approach into an existing microservices architecture requires thoughtful design. Rather than embedding approval logic into every controller, consider creating reusable capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Policy Decision Service (PDP) — central authorization logic, determines allow/deny/require approval&lt;/li&gt;
&lt;li&gt;Risk Assessment Service — provides risk scores as decision input&lt;/li&gt;
&lt;li&gt;Approval Orchestration Service — drives CIBA/push/signing workflows, including transaction binding and single-use validation&lt;/li&gt;
&lt;li&gt;Identity Integration — OIDC/JWT validation and user context (capability, not a service)&lt;/li&gt;
&lt;li&gt;Audit and Compliance — immutable decision and execution logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One important observation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The API Gateway should not be responsible for all business-level authorisation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The gateway can enforce authentication and general access policies. However, only the business service truly understands the operation being performed. The trading service, not the gateway, understands whether a share sale requires explicit approval.&lt;/p&gt;

&lt;p&gt;This distinction becomes even more important in distributed systems where multiple services participate in a single business transaction.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A["User / AI Agent"]
    B["API Gateway"]
    C["Trading Microservice"]
    D["Authorisation Policy"]
    E{"Sensitive action?"}
    F["Execute transaction"]
    G["CIBA / OOB approval"]
    H["Mobile device&amp;lt;br/&amp;gt;approve / deny"]
    I["Execute transaction"]

    A --&amp;gt; B --&amp;gt; C --&amp;gt; D --&amp;gt; E
    E --&amp;gt;|No| F
    E --&amp;gt;|Yes| G --&amp;gt; H --&amp;gt; I&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  What About Traditional Security Controls?
&lt;/h2&gt;

&lt;p&gt;None of this replaces the controls we already rely on. We still need OAuth 2.0 and OpenID Connect, JWT validation, audience restrictions, fine-grained authorisation, API rate limiting, input validation, CSRF protection, fraud detection, audit logging, and distributed tracing.&lt;/p&gt;

&lt;p&gt;These controls remain essential. But none of them answers the question agentic AI forces us to ask: does this business operation reflect the user's intended decision? That is the architectural gap exposed by agentic AI.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sensitive operations require independent approval before execution, regardless of whether they originate from a human or an AI agent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Potential Objections
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Isn't MFA enough?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Multi-factor authentication strengthens identity assurance. It proves who the user is. It does not necessarily prove that the user reviewed and approved a specific business operation. An AI agent can still operate within a legitimately authenticated session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can't we just detect AI traffic?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not reliably. When an AI agent operates through a legitimate browser session, its actions may be indistinguishable from those of the user. Detection approaches may help, but they should not be treated as the primary control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isn't this just a fraud detection problem?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Partially, but not entirely. Fraud detection is often reactive. The issue discussed here is fundamentally an authorisation design problem. The goal is to prevent unintended transactions from being executed rather than detect them afterwards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doesn't this just move the problem to the approval channel?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It moves the trust boundary, which is different. Out-of-band approval does not solve every threat — a compromised device or a malicious notification can defeat it. But it does close the specific gap this article addresses: an agent operating entirely inside a legitimate session, with no independent checkpoint between a permitted action and an unintended outcome. Least agency, applied first, reduces how often that checkpoint is needed.&lt;/p&gt;

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

&lt;p&gt;AI agents are changing how users interact with applications.&lt;/p&gt;

&lt;p&gt;For decades, we have assumed that an authenticated user interacting with a system was also the individual making decisions. Agents operating through a browser session challenge that assumption without requiring any change to our authentication infrastructure — and the same weakness runs through MCP integrations, delegated OAuth access, and AI tool integrations. An agent can use a user's authenticated session, reach legitimate functionality, and perform multiple operations that are technically permitted but never intended.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Identity does not imply intent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In an agent-driven world, identity and permissions remain necessary — but for high-impact operations they are no longer sufficient. The next evolution of microservice security is two-layered: bound the agent's agency to the task it was given, then obtain explicit confirmation on a channel the agent cannot reach when a permitted action still requires a deliberate decision.&lt;/p&gt;

&lt;p&gt;The Invisible Agent Problem is not limited to trading platforms. The same challenge emerges in banking, healthcare, insurance, e-commerce, and enterprise systems — anywhere AI agents can perform consequential actions using an existing authenticated session.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclaimer: Anthropic and SpaceX are used in the examples solely for illustration purposes. The scenarios described in this article are hypothetical and are intended to demonstrate an architectural security challenge rather than investment advice.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>architecture</category>
      <category>api</category>
      <category>ai</category>
      <category>microservices</category>
    </item>
    <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>
  </channel>
</rss>
