<?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: Nilesh Vishwakarma</title>
    <description>The latest articles on DEV Community by Nilesh Vishwakarma (@nilesh_vishwakarma_01e134).</description>
    <link>https://dev.to/nilesh_vishwakarma_01e134</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%2F4056327%2F32d6038a-f6e0-4afa-a576-27972264e2c3.png</url>
      <title>DEV Community: Nilesh Vishwakarma</title>
      <link>https://dev.to/nilesh_vishwakarma_01e134</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nilesh_vishwakarma_01e134"/>
    <language>en</language>
    <item>
      <title>Why MCP Matters: Understanding Model Context Protocol and Building Your First MCP Server in .NET</title>
      <dc:creator>Nilesh Vishwakarma</dc:creator>
      <pubDate>Mon, 19 Oct 2026 19:20:33 +0000</pubDate>
      <link>https://dev.to/nilesh_vishwakarma_01e134/why-mcp-matters-understanding-model-context-protocol-and-building-your-first-mcp-server-in-net-php</link>
      <guid>https://dev.to/nilesh_vishwakarma_01e134/why-mcp-matters-understanding-model-context-protocol-and-building-your-first-mcp-server-in-net-php</guid>
      <description>&lt;p&gt;&lt;em&gt;AI models can understand our questions, but connecting them to real applications is a different challenge. Here's where MCP fits in.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;AI assistants are useful when we want to explain a concept, summarize something, or work through a problem. But the moment we ask about data inside our own applications, things get more complicated.&lt;/p&gt;

&lt;p&gt;Imagine asking an AI assistant: &lt;strong&gt;“What is the current status of order 102?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The model understands the question. What it doesn't have is access to our order system. Somewhere, we need an integration that knows how to retrieve the answer.&lt;/p&gt;

&lt;p&gt;We could build that integration ourselves, using an API and a bit of custom code. That works well for one application. The challenge grows when several AI tools need to work with the same systems. This is the problem &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; is intended to address.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, what is MCP?
&lt;/h2&gt;

&lt;p&gt;MCP is an open protocol that provides a standard way for AI applications to communicate with external tools and data sources.&lt;/p&gt;

&lt;p&gt;It doesn't turn a database into an AI system. It doesn't replace an API. And it doesn't decide what the model should say.&lt;/p&gt;

&lt;p&gt;Instead, MCP defines how an application can discover available capabilities, call them, and receive their results.&lt;/p&gt;

&lt;p&gt;An MCP server can expose three common kinds of capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt; are operations a client can invoke, like looking up an order or searching an issue tracker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resources&lt;/strong&gt; provide data a client can read, such as documentation or configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompts&lt;/strong&gt; provide reusable templates for particular interactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a first project, tools are the easiest place to start.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyhgcwxgjooqcshyz2a3a.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyhgcwxgjooqcshyz2a3a.png" alt="Three main types of MCP server capabilities: tools, resources, and prompts" width="799" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. MCP server capabilities at a glance. The examples are illustrative.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use a REST API?
&lt;/h2&gt;

&lt;p&gt;You absolutely can. In fact, many useful MCP servers call existing REST APIs internally.&lt;/p&gt;

&lt;p&gt;The difference is who is consuming the interface and how they discover it. An ordinary API usually expects a developer to know its endpoints, authentication rules, and request formats. MCP provides a common way for compatible AI applications to discover the tools explicitly exposed by an MCP server and their input schemas.&lt;/p&gt;

&lt;p&gt;Think of an order service. It might already expose &lt;code&gt;GET /orders/{id}&lt;/code&gt;. We don't need to rewrite it. We can place an MCP server in front of the existing service and expose a tool such as &lt;code&gt;GetOrderStatus&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The application still owns its business rules and security checks. MCP only provides another interface to selected operations.&lt;/p&gt;

&lt;p&gt;That is why comparing MCP and REST as if one must replace the other misses the point. They often work together.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1jzr99h7qyvr5qw0nj2j.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1jzr99h7qyvr5qw0nj2j.png" alt="Comparison of repeated custom adapters and a shared MCP server over an existing API" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2. Conceptual comparison: applications can reuse one MCP server rather than implementing separate adapters. MCP clients remain within their host applications.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when an AI application uses MCP?
&lt;/h2&gt;

&lt;p&gt;There are three parts worth understanding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;host&lt;/strong&gt; is the AI application the user interacts with.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;MCP client&lt;/strong&gt; runs as part of that host and communicates with an MCP server.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;MCP server&lt;/strong&gt; exposes tools or other capabilities, and can call services behind the scenes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical request can follow this flow:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fptku3tfic416nrn7cki6.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fptku3tfic416nrn7cki6.png" alt="Conceptual MCP order-status request flow between user, AI host, MCP client, MCP server, and orders API" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3. Conceptual request flow. Host decisions and permissions depend on the application; arrows show relevant interactions, not every protocol message.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is a conceptual flow, not a complete protocol sequence. The host may choose to invoke a tool depending on the user's request, available permissions, and its configuration. The MCP server returns data; the host uses it to prepare an answer.&lt;/p&gt;

&lt;p&gt;The MCP server itself does &lt;strong&gt;not&lt;/strong&gt; need an AI model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's build a small MCP server in .NET
&lt;/h2&gt;

&lt;p&gt;Rather than starting with a database, let's expose a tiny order-status tool using fixed demo data. Once that works, the same structure can call a real service.&lt;/p&gt;

&lt;p&gt;This example uses the &lt;strong&gt;.NET 10 SDK&lt;/strong&gt; and the official MCP C# SDK.&lt;/p&gt;

&lt;p&gt;Create a console project and install the packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new console &lt;span class="nt"&gt;-n&lt;/span&gt; OrdersMcp &lt;span class="nt"&gt;-f&lt;/span&gt; net10.0
&lt;span class="nb"&gt;cd &lt;/span&gt;OrdersMcp

dotnet add package ModelContextProtocol
dotnet add package Microsoft.Extensions.Hosting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;Program.cs&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Hosting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Logging&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ModelContextProtocol.Server&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;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateApplicationBuilder&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="c1"&gt;// Standard output is used for MCP messages.&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;Logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddConsole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogToStandardErrorThreshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Trace&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;AddMcpServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithStdioServerTransport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithToolsFromAssembly&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;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="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerToolType&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;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderTools&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Gets the status of an order in the demo data."&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;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetOrderStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="m"&gt;101&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Processing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="m"&gt;102&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Shipped"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Order not found in demo data"&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;p&gt;A few lines do most of the work:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AddMcpServer()&lt;/code&gt; registers the MCP server. &lt;code&gt;WithStdioServerTransport()&lt;/code&gt; allows a local MCP client to communicate with the process through standard input and output. &lt;code&gt;WithToolsFromAssembly()&lt;/code&gt; discovers our annotated tool.&lt;/p&gt;

&lt;p&gt;The two attributes on &lt;code&gt;OrderTools&lt;/code&gt; and &lt;code&gt;GetOrderStatus&lt;/code&gt; tell the SDK which method should be exposed. The method accepts an order ID and returns a string. The values are hard-coded for learning purposes; &lt;strong&gt;this is not connected to a real order database&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Build the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet build &lt;span class="nt"&gt;-c&lt;/span&gt; Release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How can we test it?
&lt;/h2&gt;

&lt;p&gt;The MCP Inspector is a testing tool that can connect to MCP servers, list available tools, and invoke them directly. You don't need to connect an AI model just to check whether the server works.&lt;/p&gt;

&lt;p&gt;Install a supported Node.js version (the current Inspector v2 documentation specifies Node.js 22.19 or later), then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @modelcontextprotocol/inspector dotnet /absolute/path/to/OrdersMcp/bin/Release/net10.0/OrdersMcp.dll
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the path with your compiled DLL's location. Open the local address printed by Inspector, connect, and look for &lt;code&gt;GetOrderStatus&lt;/code&gt; under the available tools.&lt;/p&gt;

&lt;p&gt;Try &lt;code&gt;102&lt;/code&gt; as the order ID. The demo tool should return &lt;code&gt;Shipped&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's the important milestone: a separate client can discover and call a tool written in C# through MCP.&lt;/p&gt;

&lt;p&gt;To use it in an AI assistant, you then configure an MCP-compatible host to launch or connect to your server. The exact configuration file and approval behavior vary between hosts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes in a real application?
&lt;/h2&gt;

&lt;p&gt;Very little about the basic idea. Instead of returning a value from a &lt;code&gt;switch&lt;/code&gt; expression, the tool could call an existing order service.&lt;/p&gt;

&lt;p&gt;For example, the responsibilities might look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The MCP tool accepts an order ID and invokes the application service.&lt;/li&gt;
&lt;li&gt;The service checks permissions, applies business rules, and queries the system of record.&lt;/li&gt;
&lt;li&gt;The tool returns a limited, useful result to the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One mistake to avoid is putting all business logic directly into MCP tool methods. If you already have well-designed services, reuse them. That keeps your application usable by web clients, background jobs, and other integrations without depending on MCP.&lt;/p&gt;

&lt;p&gt;Security matters too. A tool that only reads an order status is very different from one that cancels orders or issues refunds. Validate inputs, enforce authorization in the underlying application, restrict exposed operations, and consider explicit user approval for sensitive actions. Connecting an AI application should not bypass any existing security boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you use MCP, and when shouldn't you?
&lt;/h2&gt;

&lt;p&gt;MCP is a good fit when multiple compatible AI applications need access to the same tools, or when you want a consistent way to expose carefully selected operations from an existing system.&lt;/p&gt;

&lt;p&gt;It may be unnecessary when you're writing a regular backend service, building a conventional web or mobile API, or adding one simple function to an AI application you fully control. Direct function calling or an ordinary API might be easier to build and maintain in those cases.&lt;/p&gt;

&lt;p&gt;And MCP is not a performance feature. A call still has to reach the underlying service, which has its own latency, failures, permissions, and dependencies. The protocol standardizes the integration boundary; it doesn't remove the engineering work behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The most useful way to look at MCP is as a bridge between AI applications and capabilities we already have.&lt;/p&gt;

&lt;p&gt;You don't need to replace your APIs or redesign your system to try it. Start with one small, read-only tool. Confirm that a client can discover it and call it. Then, if there's a real use case, connect that tool to an existing application service.&lt;/p&gt;

&lt;p&gt;Once you understand that flow, MCP becomes much less mysterious. It is not the intelligence behind the assistant. It is one of the ways the assistant can interact with the systems that hold the information it needs.&lt;/p&gt;




&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/docs/learn/architecture" rel="noopener noreferrer"&gt;Model Context Protocol: Architecture overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/csharp-sdk/blob/main/docs/concepts/getting-started.md" rel="noopener noreferrer"&gt;Official MCP C# SDK: Getting started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/inspector" rel="noopener noreferrer"&gt;MCP Inspector: Official documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/ai/quickstarts/build-mcp-server" rel="noopener noreferrer"&gt;Microsoft Learn: Create a minimal MCP server using C#&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Nilesh Vishwakarma</dc:creator>
      <pubDate>Fri, 09 Oct 2026 05:54:04 +0000</pubDate>
      <link>https://dev.to/nilesh_vishwakarma_01e134/-54al</link>
      <guid>https://dev.to/nilesh_vishwakarma_01e134/-54al</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nilesh_vishwakarma_01e134/microfrontends-microservices-how-the-architecture-works-why-it-matters-and-how-each-part-2jlb" class="crayons-story__hidden-navigation-link"&gt;Microfrontends + Microservices: How the Architecture Works, Why It Matters, and How Each Part Deploys on Its Own&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nilesh_vishwakarma_01e134" class="crayons-avatar  crayons-avatar--l  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4056327%2F32d6038a-f6e0-4afa-a576-27972264e2c3.png" alt="nilesh_vishwakarma_01e134 profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nilesh_vishwakarma_01e134" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nilesh Vishwakarma
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nilesh Vishwakarma
                
                
              
              &lt;div id="story-author-preview-content-4819846" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nilesh_vishwakarma_01e134" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4056327%2F32d6038a-f6e0-4afa-a576-27972264e2c3.png" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nilesh Vishwakarma&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nilesh_vishwakarma_01e134/microfrontends-microservices-how-the-architecture-works-why-it-matters-and-how-each-part-2jlb" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 8&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nilesh_vishwakarma_01e134/microfrontends-microservices-how-the-architecture-works-why-it-matters-and-how-each-part-2jlb" id="article-link-4819846"&gt;
          Microfrontends + Microservices: How the Architecture Works, Why It Matters, and How Each Part Deploys on Its Own
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/microfrontends"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;microfrontends&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/microservices"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;microservices&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nilesh_vishwakarma_01e134/microfrontends-microservices-how-the-architecture-works-why-it-matters-and-how-each-part-2jlb" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nilesh_vishwakarma_01e134/microfrontends-microservices-how-the-architecture-works-why-it-matters-and-how-each-part-2jlb#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            11 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Microfrontends + Microservices: How the Architecture Works, Why It Matters, and How Each Part Deploys on Its Own</title>
      <dc:creator>Nilesh Vishwakarma</dc:creator>
      <pubDate>Thu, 08 Oct 2026 19:57:30 +0000</pubDate>
      <link>https://dev.to/nilesh_vishwakarma_01e134/microfrontends-microservices-how-the-architecture-works-why-it-matters-and-how-each-part-2jlb</link>
      <guid>https://dev.to/nilesh_vishwakarma_01e134/microfrontends-microservices-how-the-architecture-works-why-it-matters-and-how-each-part-2jlb</guid>
      <description>&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdtkas2fz83k5d5g0nyl7.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdtkas2fz83k5d5g0nyl7.png" alt="Architecture of the Slice Platform Starter" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The whole system on one page. Keep it open; we'll walk through every box.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Picture a typical web shop. One team owns the product catalog, another owns orders, a third owns user profiles. In the beginning, everyone works in one frontend and one backend. It's simple, and that's fine.&lt;/p&gt;

&lt;p&gt;Then the company grows. Ten developers become fifty. Every release needs everyone to agree. A small bug on the profile page blocks the orders team from shipping. A routine library upgrade turns into a three-week, all-hands project.&lt;/p&gt;

&lt;p&gt;This is the problem &lt;strong&gt;microfrontends&lt;/strong&gt; and &lt;strong&gt;microservices&lt;/strong&gt; solve: each team builds, tests and &lt;strong&gt;deploys its own part&lt;/strong&gt; of the product, on its own schedule, while users still see &lt;strong&gt;one&lt;/strong&gt; seamless application.&lt;/p&gt;

&lt;p&gt;In this article we'll look at an architecture that does exactly that: how it works, why each piece exists, and above all &lt;strong&gt;how deployment works&lt;/strong&gt;. The headline is this: when one part of the UI changes, you deploy &lt;strong&gt;only that part&lt;/strong&gt;, and the main application doesn't need to be touched at all.&lt;/p&gt;

&lt;p&gt;A complete, runnable example is on GitHub: &lt;a href="https://github.com/it-nilesh/slice-platform-starter" rel="noopener noreferrer"&gt;github.com/it-nilesh/slice-platform-starter&lt;/a&gt; (React + Module Federation on the frontend, .NET 10 microservices on the backend, NGINX in front).&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/it-nilesh" rel="noopener noreferrer"&gt;
        it-nilesh
      &lt;/a&gt; / &lt;a href="https://github.com/it-nilesh/slice-platform-starter" rel="noopener noreferrer"&gt;
        slice-platform-starter
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Production-shaped scaffolding for microfrontends + .NET 10 microservices behind an NGINX gateway: runtime Module Federation, a slice CLI, and independent CI/CD per slice.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;MicroFrontend PoC&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/it-nilesh/slice-platform-starter/docs/images/architecture.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fit-nilesh%2Fslice-platform-starter%2FHEAD%2Fdocs%2Fimages%2Farchitecture.png" alt="Architecture"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A small, production-shaped proof of concept for &lt;strong&gt;microfrontends + microservices behind a single NGINX gateway&lt;/strong&gt;, packaged as a &lt;strong&gt;framework&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Manifest-driven dynamic remotes&lt;/strong&gt;: adding a microfrontend never requires rebuilding or redeploying the shell.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Vertical slices&lt;/strong&gt; (one API + one MFE + one pipeline per team), generated and managed by the &lt;code&gt;./slice&lt;/code&gt; CLI.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Independent CI/CD per slice&lt;/strong&gt;: test, scan, sign and push only what changed; release each slice with its own tags.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Frontend:&lt;/strong&gt; React 19 + Vite 8 + Module Federation (&lt;code&gt;@module-federation/vite&lt;/code&gt; + &lt;code&gt;@module-federation/runtime&lt;/code&gt;), TypeScript&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Backend:&lt;/strong&gt; .NET 10 Minimal APIs, one service per business capability&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Gateway:&lt;/strong&gt; NGINX, the only public entry point&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Architecture&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The diagram above shows the moving parts. In text form:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;                          Browser  (http://localhost:8080)
                                 │
                 ┌───────────────▼────────────────┐
                 │        gateway (NGINX)         │  allowlist (deny by default), rate limiting
                 │  /config/mfe-manifest.json     │  security headers + CSP, request-id, JSON logs
                 └──┬──────────────┬───────────┬──┘
       /  (host)    │  /mfe/&amp;lt;name&amp;gt;/│           │ /api/&amp;lt;name&amp;gt;/&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/it-nilesh/slice-platform-starter" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  When do you need this architecture?
&lt;/h2&gt;

&lt;p&gt;Let's answer the most important question first, because the honest answer is: &lt;strong&gt;most applications don't.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Microfrontends and microservices solve an &lt;strong&gt;organizational&lt;/strong&gt; problem, not a technical one. They're worth it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Several teams&lt;/strong&gt; work on the same product, and each owns a clear business area (catalog, orders, payments, profile…).&lt;/li&gt;
&lt;li&gt;Teams need to &lt;strong&gt;release independently&lt;/strong&gt;, several times a week, without coordinating a "big release day" with everyone else.&lt;/li&gt;
&lt;li&gt;One area's problems &lt;strong&gt;must not block or break&lt;/strong&gt; the others.&lt;/li&gt;
&lt;li&gt;Parts of the product &lt;strong&gt;change at very different speeds&lt;/strong&gt;. A checkout page might change daily while the profile page changes once a quarter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They're probably &lt;strong&gt;not&lt;/strong&gt; worth it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're &lt;strong&gt;one or two teams&lt;/strong&gt;. A well-structured &lt;strong&gt;modular monolith&lt;/strong&gt; (one application with clean internal boundaries) is simpler, faster to build and easier to debug.&lt;/li&gt;
&lt;li&gt;The product is &lt;strong&gt;small or early-stage&lt;/strong&gt;, and boundaries between areas are still changing.&lt;/li&gt;
&lt;li&gt;You don't yet have basic &lt;strong&gt;automation&lt;/strong&gt;: CI pipelines, containers, monitoring. This architecture multiplies the number of things to deploy, so manual processes don't scale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The costs are real: more moving parts, network calls that can fail, versions that drift apart, and debugging across several services. Choose this architecture when the cost of teams &lt;strong&gt;waiting for each other&lt;/strong&gt; is higher than the cost of that extra complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The big idea, explained with a shopping mall
&lt;/h2&gt;

&lt;p&gt;Before any technical detail, let's build a mental model. Think of the application as a &lt;strong&gt;shopping mall&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The mall building&lt;/strong&gt; is the &lt;strong&gt;shell&lt;/strong&gt; (also called the &lt;em&gt;host&lt;/em&gt;). It has the entrance, the hallways and the signs. It doesn't sell anything itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each shop&lt;/strong&gt; is a &lt;strong&gt;microfrontend&lt;/strong&gt;: the Catalog shop, the Orders shop, the Profile shop. Each is run by a different owner, who redecorates and restocks it whenever they like.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each shop's back office and stockroom&lt;/strong&gt; is a &lt;strong&gt;microservice&lt;/strong&gt;. The Orders shop has its own stockroom (the Orders API) that only it manages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The security desk at the entrance&lt;/strong&gt; is the &lt;strong&gt;gateway&lt;/strong&gt;. Every visitor passes through it, and it knows which hallway leads where.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The directory board&lt;/strong&gt; at the entrance is the &lt;strong&gt;manifest&lt;/strong&gt;. It lists which shops are open today and where to find them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the key insight, and the reason this architecture exists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a shop &lt;strong&gt;redecorates&lt;/strong&gt;, the mall building isn't touched.&lt;/li&gt;
&lt;li&gt;When a &lt;strong&gt;new shop opens&lt;/strong&gt;, nobody rebuilds the building. Someone adds a line to the directory board.&lt;/li&gt;
&lt;li&gt;When a shop &lt;strong&gt;closes for repairs&lt;/strong&gt;, the rest of the mall stays open.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's exactly how deployment works in this architecture. Keep this picture in mind; everything else follows from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven terms you need
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Microfrontend (MFE):&lt;/strong&gt; a small, independently deployed UI application that owns one part of the product, such as the Orders page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shell (host):&lt;/strong&gt; the outer application. It draws the header and navigation, and loads the right microfrontend for each page. It contains &lt;strong&gt;no business features&lt;/strong&gt; itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Microservice:&lt;/strong&gt; a small, independently deployed backend that owns one business capability and its data, such as the Orders API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gateway:&lt;/strong&gt; the single entry point for all traffic. Every browser request goes to it, and it forwards each one to the right container.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manifest:&lt;/strong&gt; a small configuration file listing which microfrontends exist, where to download them and which page they belong to. The shell reads it &lt;strong&gt;at runtime&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Module Federation:&lt;/strong&gt; the browser technology that lets the shell load a microfrontend's code &lt;strong&gt;at runtime&lt;/strong&gt;, even though the shell was never built with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vertical slice:&lt;/strong&gt; one business feature end to end (its microfrontend, its microservice and its own pipeline), owned by one team. The example has three: &lt;strong&gt;Catalog&lt;/strong&gt;, &lt;strong&gt;Orders&lt;/strong&gt; and &lt;strong&gt;Profile&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture, piece by piece
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The gateway: one front door
&lt;/h3&gt;

&lt;p&gt;Every request from the browser goes to a single address: the gateway (NGINX in the example). It routes by a simple naming convention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/&lt;/code&gt; goes to the &lt;strong&gt;shell&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/mfe/orders/&lt;/code&gt; goes to the &lt;strong&gt;Orders microfrontend&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/api/orders/&lt;/code&gt; goes to the &lt;strong&gt;Orders API&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One address for everything&lt;/strong&gt; means the browser never talks to several servers, so there's no cross-origin (CORS) configuration to maintain, and strict browser security rules are easy to apply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security in one place:&lt;/strong&gt; security headers, rate limiting and request size limits are applied once, for every team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe by default:&lt;/strong&gt; only services on an &lt;strong&gt;allowlist&lt;/strong&gt; are reachable. A new internal service can't become public by accident.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The shell: a frame that knows nothing about its content
&lt;/h3&gt;

&lt;p&gt;The shell draws the frame (header, navigation, error handling) and loads microfrontends into it. The crucial design decision: &lt;strong&gt;the shell's code contains no list of microfrontends.&lt;/strong&gt; It reads them from the manifest every time the app starts:&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;"schemaVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"remotes"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"catalog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"entry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mfe/catalog/remoteEntry.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/catalog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"nav"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Catalog"&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;span class="p"&gt;},&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;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"entry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mfe/orders/remoteEntry.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"nav"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Orders"&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;span class="p"&gt;}&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;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;From this file alone, the shell knows which pages exist, which menu items to show and where to download each microfrontend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; many microfrontend tutorials hard-code the list of microfrontends into the shell's build. Then every new microfrontend forces a shell rebuild and redeploy, which quietly brings back the very coupling we were trying to remove. Reading the list at runtime is what makes &lt;strong&gt;independent deployment&lt;/strong&gt; real.&lt;/p&gt;

&lt;p&gt;Because the manifest decides which code runs in the browser, the shell also &lt;strong&gt;validates&lt;/strong&gt; every entry. A microfrontend may only be loaded from the same website, under its own folder, so a bad or malicious entry can't point the app at someone else's script.&lt;/p&gt;

&lt;h3&gt;
  
  
  The microfrontends: independent apps that share a page
&lt;/h3&gt;

&lt;p&gt;Each microfrontend is a normal React application with its own folder, its own build, its own container and its own pipeline. It &lt;strong&gt;exposes&lt;/strong&gt; one component (its page) for the shell to load.&lt;/p&gt;

&lt;p&gt;A few rules keep them truly independent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;They never import each other's code.&lt;/strong&gt; If Catalog imported from Orders, they could no longer be deployed separately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They communicate through a small, shared contract.&lt;/strong&gt; When you add a product to the cart in Catalog, it announces a "cart changed" event. The shell (cart badge) and Orders (cart list) listen for it. Nobody knows who else is listening.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared libraries are loaded once.&lt;/strong&gt; React is marked as &lt;em&gt;shared&lt;/em&gt;, so the page loads a single copy instead of one per microfrontend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each one can run on its own,&lt;/strong&gt; without the shell, so a team can develop and test its page in isolation.&lt;/li&gt;
&lt;/ul&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpjsm2yyvsjoqvx515ag8.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpjsm2yyvsjoqvx515ag8.png" alt="The Catalog microfrontend inside the shell" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Catalog page is one application; the header with the cart badge is another (the shell). The user can't tell.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The microservices: each team owns its backend and its data
&lt;/h3&gt;

&lt;p&gt;Each microfrontend talks to &lt;strong&gt;its own&lt;/strong&gt; API: Catalog to the Catalog API, Orders to the Orders API. Each service owns its data, and no other service touches its database.&lt;/p&gt;

&lt;p&gt;When services need each other, they talk &lt;strong&gt;directly over the private network&lt;/strong&gt;, not through the browser. For example, when you place an order, the Orders API asks the Catalog API for the real prices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; this follows a rule every system should follow: &lt;strong&gt;the browser sends intent, the server decides facts.&lt;/strong&gt; The browser says &lt;em&gt;which&lt;/em&gt; products and &lt;em&gt;how many&lt;/em&gt;; the prices come from the service that owns them. Anything the browser sends can be changed by the user, so nothing important is trusted from it.&lt;/p&gt;

&lt;p&gt;All services also share a small common building block for health checks, logging and error responses, so every team gets the same production basics without rebuilding them.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works: follow one click
&lt;/h2&gt;

&lt;p&gt;Let's follow what happens when a user opens &lt;code&gt;https://shop.example/orders&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The browser asks the gateway for the page.&lt;/strong&gt; The gateway forwards it to the &lt;strong&gt;shell&lt;/strong&gt;, which returns a small HTML page and its JavaScript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The shell reads the manifest.&lt;/strong&gt; It learns that "Orders lives at &lt;code&gt;/mfe/orders/remoteEntry.js&lt;/code&gt; and belongs on &lt;code&gt;/orders&lt;/code&gt;", and builds the navigation menu.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The shell loads the Orders microfrontend.&lt;/strong&gt; It downloads Orders' entry file &lt;strong&gt;only now&lt;/strong&gt;, because the user is on the Orders page. Pages nobody visits are never downloaded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Orders microfrontend calls its API.&lt;/strong&gt; It requests &lt;code&gt;/api/orders&lt;/code&gt;, which the gateway forwards to the Orders API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When the user places an order,&lt;/strong&gt; the Orders API asks the Catalog API for the real prices, over the private network.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice what &lt;em&gt;didn't&lt;/em&gt; happen: the shell never needed to know anything about Orders in advance. It learned everything from the manifest, at runtime. &lt;strong&gt;That's the property that makes independent deployment possible.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Designed for failure
&lt;/h2&gt;

&lt;p&gt;In a system with many independent parts, something is always being deployed, restarting or briefly down. The architecture makes sure &lt;strong&gt;one broken part never takes down the whole page&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Each microfrontend is isolated.&lt;/strong&gt; If Catalog fails to load, only the Catalog area shows "unavailable". The header, the navigation and every other page keep working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loading has a timeout,&lt;/strong&gt; so a slow server never means an endless spinner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Try again" recovers&lt;/strong&gt; without reloading the whole page once the service is back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The gateway returns a clear "service unavailable"&lt;/strong&gt; for a slice that's down, instead of a confusing error.&lt;/li&gt;
&lt;/ul&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk58h4fhu9n3bede9fxha.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk58h4fhu9n3bede9fxha.png" alt="When one microfrontend is down, the rest of the app keeps working" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Catalog container is stopped. Only its area shows an error; navigation and the other pages still work.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment: ship only what changed
&lt;/h2&gt;

&lt;p&gt;This is where the architecture pays off. Let's start with the headline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Changing a microfrontend? Deploy only that microfrontend
&lt;/h3&gt;

&lt;p&gt;Say the Orders team redesigns the order history table. Here's the entire deployment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Orders team merges its change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only the Orders pipeline runs.&lt;/strong&gt; It builds, tests and publishes a new &lt;code&gt;mfe-orders&lt;/code&gt; image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Only the &lt;code&gt;mfe-orders&lt;/code&gt; container is replaced.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Users get the new Orders page &lt;strong&gt;on their next page load&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What &lt;em&gt;wasn't&lt;/em&gt; touched:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The shell:&lt;/strong&gt; not rebuilt, not redeployed, not restarted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catalog and Profile:&lt;/strong&gt; not rebuilt, not redeployed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The gateway:&lt;/strong&gt; not changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The manifest:&lt;/strong&gt; not changed. Orders still lives at the same address.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No other team had to be asked, wait, or re-test anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the shell never needs updating
&lt;/h3&gt;

&lt;p&gt;Three design choices make this work:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The shell loads microfrontends at runtime, not at build time.&lt;/strong&gt; The shell's build contains no microfrontend code at all. It downloads each one in the browser, from the address in the manifest. A new Orders version is simply &lt;em&gt;what that address returns now&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A stable entry file plus versioned content files.&lt;/strong&gt; Each microfrontend has one small entry file with a fixed name, &lt;code&gt;remoteEntry.js&lt;/code&gt;. The browser is told to &lt;strong&gt;always check it for updates&lt;/strong&gt;. That file points to the real code, whose file names include a unique version fingerprint (like &lt;code&gt;App-3f9a1c.js&lt;/code&gt;), so those can be &lt;strong&gt;cached forever&lt;/strong&gt;. After a deploy, the entry file points to new fingerprinted files, and every browser picks them up on its next load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A contract instead of shared code.&lt;/strong&gt; The shell and the microfrontends agree only on a small, stable contract: "expose a page component", "announce cart changes with this event". As long as a new version keeps that contract, the shell doesn't care what changed inside.&lt;/p&gt;

&lt;h3&gt;
  
  
  Every kind of change, and what it costs to ship
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Change a microfrontend's UI:&lt;/strong&gt; deploy that one microfrontend. Shell untouched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change a microservice:&lt;/strong&gt; deploy that one service. Shell and microfrontends untouched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a brand-new microfrontend:&lt;/strong&gt; deploy its containers, then add &lt;strong&gt;one entry&lt;/strong&gt; to the manifest and &lt;strong&gt;one line&lt;/strong&gt; to the gateway's allowlist. Shell untouched: it discovers the new page on the next load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn off a broken microfrontend:&lt;/strong&gt; set &lt;code&gt;"enabled": false&lt;/code&gt; in the manifest. It disappears from the app on the next page load, &lt;strong&gt;with no deployment at all&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change the shell itself&lt;/strong&gt; (for example the header design): deploy the shell. This is the only case that touches it, and it's rare, because the shell contains no business features.&lt;/li&gt;
&lt;/ul&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flpv42rs4nbb8vuq0l4lo.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flpv42rs4nbb8vuq0l4lo.png" alt="A new slice, added without touching the shell" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A fourth microfrontend, "Inventory", added by deploying it and adding one manifest entry. The shell was never rebuilt or restarted.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Independent pipelines
&lt;/h3&gt;

&lt;p&gt;Independent deployment only works if the &lt;strong&gt;pipelines&lt;/strong&gt; are independent too. Each slice has its own CI/CD pipeline that runs &lt;strong&gt;only when that slice's files change&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A change in the Orders microfrontend builds and publishes &lt;strong&gt;only&lt;/strong&gt; the Orders microfrontend image.&lt;/li&gt;
&lt;li&gt;A change in the Orders API builds and publishes &lt;strong&gt;only&lt;/strong&gt; the Orders API image.&lt;/li&gt;
&lt;li&gt;A release tag like &lt;code&gt;orders-v1.4.0&lt;/code&gt; releases &lt;strong&gt;only&lt;/strong&gt; the Orders slice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every pipeline produces &lt;strong&gt;versioned, immutable images&lt;/strong&gt; (for example &lt;code&gt;mfe-orders:sha-4e7fae5&lt;/code&gt; or &lt;code&gt;mfe-orders:1.4.0&lt;/code&gt;). "Immutable" means a tag always refers to exactly the same code, which makes every deployment predictable and every rollback trivial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where it runs
&lt;/h3&gt;

&lt;p&gt;The deployment unit is always &lt;strong&gt;one container&lt;/strong&gt;. That fits any modern platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A single server with Docker Compose:&lt;/strong&gt; replace one service at a time (&lt;code&gt;docker compose up -d --no-deps mfe-orders&lt;/code&gt;) while everything else keeps running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes:&lt;/strong&gt; each container becomes its own Deployment and Service, and the manifest and allowlist become configuration (ConfigMaps), so changing them never requires a new image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitOps (Argo CD or Flux):&lt;/strong&gt; a separate repository records which version runs in each environment. Deploying or promoting a slice becomes a small, reviewed pull request that changes one version number.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Deploying safely
&lt;/h3&gt;

&lt;p&gt;A few habits keep independent deployments safe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deploy the API before the UI,&lt;/strong&gt; and keep API changes &lt;strong&gt;backward compatible&lt;/strong&gt; (add fields, don't remove them). For a short time, old and new versions run side by side, so each must work with the other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat the shared contract like a public API:&lt;/strong&gt; only ever &lt;em&gt;add&lt;/em&gt; to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Roll back by redeploying the previous version tag.&lt;/strong&gt; Because tags are immutable, you know exactly what you're getting back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the kill switch&lt;/strong&gt; when you need a broken page gone &lt;em&gt;now&lt;/em&gt;: hide it in the manifest, then fix it calmly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Put together, this architecture changes how a growing organization works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Teams move at their own speed.&lt;/strong&gt; The Orders team can ship five times today without asking anyone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Releases get smaller and safer.&lt;/strong&gt; Deploying one small piece is less risky than deploying everything, and easier to roll back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failures stay contained,&lt;/strong&gt; both at runtime (one broken page) and at deploy time (one bad release).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New features don't require touching the core.&lt;/strong&gt; Adding a whole new area of the product is a deployment plus a configuration change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ownership is clear.&lt;/strong&gt; Each team owns its slice end to end, from the database to the screen to the pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The price is more moving parts. That's exactly why it pays off for &lt;strong&gt;many teams working on one product&lt;/strong&gt;, and why a single small team is usually better served by a modular monolith.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The whole architecture comes down to one idea, the shopping mall: &lt;strong&gt;the shell is the building, not a shop.&lt;/strong&gt; It provides the entrance, the hallways and the directory board, but it knows nothing about what each shop sells. Because of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A shop can redecorate (deploy a new microfrontend version) without touching the building.&lt;/li&gt;
&lt;li&gt;A new shop can open by adding a line to the directory board (the manifest).&lt;/li&gt;
&lt;li&gt;A shop can close for repairs (fail, or be switched off) while the rest of the mall stays open.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your teams keep waiting on each other to release, this is the architecture to reach for.&lt;/p&gt;

&lt;p&gt;The complete, runnable example is on GitHub: &lt;a href="https://github.com/it-nilesh/slice-platform-starter" rel="noopener noreferrer"&gt;github.com/it-nilesh/slice-platform-starter&lt;/a&gt;. Clone it, run &lt;code&gt;docker compose up -d --build&lt;/code&gt;, open the app, then change one microfrontend and redeploy just that one. Watching the shell stay untouched is the best way to make the idea click.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If this helped you understand microfrontends, leave a reaction and follow for more architecture deep dives. Questions or a different approach? Drop them in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>microfrontends</category>
      <category>microservices</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Rust Is Becoming the Engine Behind Python’s Modern Tooling</title>
      <dc:creator>Nilesh Vishwakarma</dc:creator>
      <pubDate>Mon, 05 Oct 2026 18:36:51 +0000</pubDate>
      <link>https://dev.to/nilesh_vishwakarma_01e134/rust-is-becoming-the-engine-behind-pythons-modern-tooling-533i</link>
      <guid>https://dev.to/nilesh_vishwakarma_01e134/rust-is-becoming-the-engine-behind-pythons-modern-tooling-533i</guid>
      <description>&lt;p&gt;&lt;strong&gt;Why Ruff, uv, Pydantic, Polars, and a growing class of Python tools are choosing Rust — and why the shift matters.&lt;/strong&gt;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv8656it7i8czh90r0p1b.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv8656it7i8czh90r0p1b.png" alt="Rust engine behind Python tooling" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1 — Conceptual illustration: Python remains the developer-facing layer while Rust increasingly powers performance-sensitive tooling underneath.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The most interesting Rust adoption story may not be happening inside Rust applications.&lt;/p&gt;

&lt;p&gt;It may be happening inside &lt;strong&gt;Python&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A Python developer today might run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv &lt;span class="nb"&gt;sync
&lt;/span&gt;ruff check &lt;span class="nb"&gt;.&lt;/span&gt;
ruff format &lt;span class="nb"&gt;.&lt;/span&gt;
ty check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They might validate API data with Pydantic, process analytical workloads with Polars, or tokenize text using Hugging Face Tokenizers or OpenAI's &lt;code&gt;tiktoken&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From the developer's point of view, this is still a Python workflow.&lt;/p&gt;

&lt;p&gt;Underneath it, however, something has changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rust is increasingly doing the heavy lifting.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ruff is written in Rust. uv is written in Rust. ty is written in Rust. Pydantic moved its core validation engine to Rust. Polars built its core query engine in Rust. Hugging Face Tokenizers uses Rust. OpenAI's &lt;code&gt;tiktoken&lt;/code&gt; contains Rust code exposed to Python.&lt;/p&gt;

&lt;p&gt;This is not evidence that Rust is replacing Python.&lt;/p&gt;

&lt;p&gt;It points to something more useful:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Python is remaining the interface developers want, while Rust is becoming part of the infrastructure underneath it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction explains why this trend matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Python toolchain is changing
&lt;/h2&gt;

&lt;p&gt;A traditional Python development environment might have combined tools such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip
virtualenv
pip-tools

flake8
isort
Black

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

&lt;/div&gt;



&lt;p&gt;That model still works, and those tools remain important.&lt;/p&gt;

&lt;p&gt;But a newer stack can look very different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Python project
                     │
        ┌────────────┼────────────┐
        │            │            │
        ▼            ▼            ▼
       uv           Ruff          ty
 packages/projects  lint/format  type checking
        │            │            │
        └────────────┼────────────┘
                     │
                    Rust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ruff combines linting, automatic fixes, import sorting-related functionality, and formatting in one native tool. uv covers package installation, dependency resolution, project management, virtual environments, lock files, tools, scripts, and Python installations. ty brings a Rust implementation into Python type checking and language-server work.&lt;/p&gt;

&lt;p&gt;The interesting story is not that all of these tools are written in the same language.&lt;/p&gt;

&lt;p&gt;It is that &lt;strong&gt;the workloads they perform have similar characteristics&lt;/strong&gt;: parsing, graph traversal, semantic analysis, caching, dependency resolution, and repeated processing of large codebases.&lt;/p&gt;

&lt;p&gt;Those are areas where implementation architecture can materially affect the developer experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ruff showed what a Rust-first Python tool could feel like
&lt;/h2&gt;

&lt;p&gt;Ruff was one of the projects that made many Python developers notice this shift.&lt;/p&gt;

&lt;p&gt;A linter looks simple from the outside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ruff check &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally, however, it needs to discover files, parse Python syntax, construct internal representations, evaluate rules, produce diagnostics, and sometimes rewrite source code.&lt;/p&gt;

&lt;p&gt;Historically, a repository could run several tools independently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python files
    │
    ├──► Flake8
    ├──► isort
    ├──► pyupgrade
    ├──► autoflake
    └──► Black
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ruff approaches much of that work through one implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python source
      │
      ▼
 ┌───────────┐
 │   Ruff    │
 │           │
 │ Parsing   │
 │ Linting   │
 │ Fixes     │
 │ Formatting│
 └───────────┘
      │
      ▼
Checked source
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Astral publishes benchmarks showing large performance advantages for Ruff in the workloads it measures. Those numbers are useful, but they should be read as &lt;strong&gt;project benchmarks&lt;/strong&gt;, not as a guarantee that every repository will see the same improvement.&lt;/p&gt;

&lt;p&gt;More importantly, Ruff illustrates a broader engineering lesson.&lt;/p&gt;

&lt;p&gt;Rust alone is not the architecture.&lt;/p&gt;

&lt;p&gt;The performance story also involves consolidation, shared parsing infrastructure, caching, data structures, and implementation choices.&lt;/p&gt;

&lt;p&gt;A badly designed Rust tool can still be slow. A well-designed Python tool can still be fast enough.&lt;/p&gt;

&lt;p&gt;The language matters, but it is only one part of the system.&lt;/p&gt;

&lt;p&gt;Ruff also has tradeoffs. Its behavior is not identical to every tool it can replace, and it does not reproduce every extension model from older ecosystems such as Flake8's plugin system.&lt;/p&gt;

&lt;p&gt;So migration should follow project requirements, not benchmark headlines.&lt;/p&gt;




&lt;h2&gt;
  
  
  uv moves Rust into the center of Python package management
&lt;/h2&gt;

&lt;p&gt;Package management makes the trend even more visible.&lt;/p&gt;

&lt;p&gt;A conventional workflow might involve several separate concepts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python installation
       │
       ├── venv / virtualenv
       ├── pip
       ├── pip-tools
       ├── pipx
       └── project-specific tooling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With uv, the workflow can become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv init
uv add fastapi
uv run main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, for pip-style workflows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv venv
uv pip &lt;span class="nb"&gt;install &lt;/span&gt;fastapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important detail is that &lt;code&gt;uv pip&lt;/code&gt; is not simply a thin wrapper that invokes pip underneath. uv implements its own package-management machinery while providing a familiar interface for many pip-style operations.&lt;/p&gt;

&lt;p&gt;That means a Rust program is now handling work such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             uv
              │
   ┌──────────┼──────────┐
   │          │          │
   ▼          ▼          ▼
Dependency  Python     Virtual
resolution  versions   environments
   │          │          │
   └──────────┼──────────┘
              ▼
            Project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, the attraction is not simply that Rust can execute native code.&lt;/p&gt;

&lt;p&gt;uv also consolidates responsibilities that Python developers previously handled through several tools.&lt;/p&gt;

&lt;p&gt;That can simplify workflows, but it does not mean uv is an exact behavioral clone of pip. Astral documents compatibility differences, which matter for projects depending on unusual packaging behavior or established enterprise build systems.&lt;/p&gt;

&lt;p&gt;A faster tool is valuable only if it is also compatible with the workflow you need.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pydantic shows the deeper architectural shift
&lt;/h2&gt;

&lt;p&gt;Ruff and uv are standalone tools.&lt;/p&gt;

&lt;p&gt;Pydantic demonstrates something more significant for application developers.&lt;/p&gt;

&lt;p&gt;Python code still looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing about that API looks like Rust.&lt;/p&gt;

&lt;p&gt;That is exactly the point.&lt;/p&gt;

&lt;p&gt;Pydantic V2 moved core validation and serialization work into &lt;code&gt;pydantic-core&lt;/code&gt;, implemented in Rust and exposed to Python.&lt;/p&gt;

&lt;p&gt;The conceptual architecture looks like this:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F57v62o0yc1uxdwkx2tm5.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F57v62o0yc1uxdwkx2tm5.png" alt="Python to Rust native boundary" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2 — Conceptual architecture: Python provides the developer experience while Rust handles selected native workloads through a boundary such as PyO3.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The developer keeps the Python interface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python classes&lt;/li&gt;
&lt;li&gt;type annotations&lt;/li&gt;
&lt;li&gt;framework integration&lt;/li&gt;
&lt;li&gt;IDE support&lt;/li&gt;
&lt;li&gt;the wider Python ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the implementation of selected performance-sensitive operations lives in Rust.&lt;/p&gt;

&lt;p&gt;This is a much more interesting model than simply "rewriting Python in Rust."&lt;/p&gt;

&lt;p&gt;It lets a project change the engine &lt;strong&gt;without changing the driver's seat&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Polars applies the same idea to data processing
&lt;/h2&gt;

&lt;p&gt;Polars makes the pattern even clearer.&lt;/p&gt;

&lt;p&gt;A Python developer can write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders.parquet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&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;p&gt;The interface is Python, but the query engine is implemented in Rust.&lt;/p&gt;

&lt;p&gt;Polars also has an architecture that differs meaningfully from pandas. Its lazy API can build a query plan, optimize it, and then execute the resulting work using its engine.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python expression
       │
       ▼
Logical query
       │
       ▼
Query optimizer
       │
       ▼
Execution plan
       │
       ▼
Rust engine
       │
       ▼
Data / CPU cores
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That distinction matters because it prevents an oversimplified conclusion.&lt;/p&gt;

&lt;p&gt;Polars is not merely "pandas rewritten in Rust."&lt;/p&gt;

&lt;p&gt;The implementation language changed, but so did important parts of the execution model.&lt;/p&gt;

&lt;p&gt;Rust contributes to the design. It does not explain the entire design.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI developers may already be using Rust without noticing
&lt;/h2&gt;

&lt;p&gt;The same pattern appears in AI infrastructure.&lt;/p&gt;

&lt;p&gt;Hugging Face Tokenizers uses a Rust implementation underneath its Python interface. OpenAI's &lt;code&gt;tiktoken&lt;/code&gt; also includes Rust code exposed to Python.&lt;/p&gt;

&lt;p&gt;From Python, usage remains ordinary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;

&lt;span class="n"&gt;encoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o200k_base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rust underneath Python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Rust boundary is almost invisible.&lt;/p&gt;

&lt;p&gt;That invisibility is important.&lt;/p&gt;

&lt;p&gt;A successful native layer should not force every Python user to become a systems programmer.&lt;/p&gt;

&lt;p&gt;If the package is distributed correctly, the user imports a Python module and works with a Python API. The implementation language becomes an internal engineering decision.&lt;/p&gt;




&lt;h2&gt;
  
  
  PyO3 and maturin made the hybrid model practical
&lt;/h2&gt;

&lt;p&gt;Python has depended on native code for decades.&lt;/p&gt;

&lt;p&gt;NumPy, SciPy, cryptographic libraries, database drivers, ML frameworks, and CPython itself have long depended on C, C++, Fortran, or other compiled languages.&lt;/p&gt;

&lt;p&gt;So the new idea is not "Python can call native code."&lt;/p&gt;

&lt;p&gt;The newer story is that the &lt;strong&gt;Rust-to-Python development path has become much more practical&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;PyO3 provides Rust bindings for Python and allows Rust functions and types to be exposed as Python modules.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python code
    │
    ▼
   PyO3
    │
    ▼
Rust implementation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then maturin helps build and package Rust-backed Python projects into Python wheels.&lt;/p&gt;

&lt;p&gt;That gives maintainers a workflow resembling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rust source
    │
    ▼
   PyO3
    │
    ▼
 maturin
    │
    ▼
Python wheel
    │
    ▼
pip / uv install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a compatible prebuilt wheel exists, the end user often does not need Rust installed at all.&lt;/p&gt;

&lt;p&gt;That changes the economics of using Rust inside Python libraries.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Rust-powered Python ecosystem is broader than one vendor
&lt;/h2&gt;

&lt;p&gt;The trend is easy to associate with Astral because Ruff, uv, and ty are highly visible.&lt;/p&gt;

&lt;p&gt;But the broader ecosystem shows that the pattern is not limited to one company or one product category.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpvnn0jus86lm13hkxjbt.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpvnn0jus86lm13hkxjbt.png" alt="Rust-powered projects across the Python ecosystem" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3 — Selected examples of Rust-powered developer tools, performance libraries, and Python/Rust integration infrastructure. This is illustrative, not exhaustive.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A useful way to group the projects is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;th&gt;Rust's role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Developer tooling&lt;/td&gt;
&lt;td&gt;Ruff, uv, ty&lt;/td&gt;
&lt;td&gt;Native CLI, parsing, resolution, analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;td&gt;Pydantic Core&lt;/td&gt;
&lt;td&gt;Validation and serialization engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data processing&lt;/td&gt;
&lt;td&gt;Polars&lt;/td&gt;
&lt;td&gt;Query and execution engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serialization&lt;/td&gt;
&lt;td&gt;orjson&lt;/td&gt;
&lt;td&gt;Native JSON implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NLP / AI&lt;/td&gt;
&lt;td&gt;Tokenizers, tiktoken&lt;/td&gt;
&lt;td&gt;Tokenization and text processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration&lt;/td&gt;
&lt;td&gt;PyO3&lt;/td&gt;
&lt;td&gt;Python/Rust bindings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Packaging&lt;/td&gt;
&lt;td&gt;maturin&lt;/td&gt;
&lt;td&gt;Building and distributing Rust-backed Python packages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is not every Rust-backed project in Python, and it should not be presented as a complete inventory.&lt;/p&gt;

&lt;p&gt;The important point is the &lt;strong&gt;pattern across categories&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Rust keeps appearing
&lt;/h2&gt;

&lt;p&gt;There are many fast languages, and C already underpins enormous parts of Python.&lt;/p&gt;

&lt;p&gt;Why Rust?&lt;/p&gt;

&lt;p&gt;Several characteristics line up well with tooling and infrastructure workloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Native execution
&lt;/h3&gt;

&lt;p&gt;Rust compiles to native code.&lt;/p&gt;

&lt;p&gt;For workloads dominated by parsing, serialization, graph resolution, static analysis, or query execution, avoiding interpreter overhead can be valuable.&lt;/p&gt;

&lt;p&gt;But native code does not automatically mean fast software.&lt;/p&gt;

&lt;p&gt;Algorithms, memory access patterns, caching, I/O, data structures, allocation behavior, and architecture can matter as much as the language.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory safety without a garbage-collected runtime
&lt;/h3&gt;

&lt;p&gt;Compilers, linters, type checkers, and query engines often create large graphs and trees in memory.&lt;/p&gt;

&lt;p&gt;Rust provides low-level control together with compile-time memory-safety guarantees, without requiring a garbage-collected runtime.&lt;/p&gt;

&lt;p&gt;That can be attractive for systems that need predictable control over large internal representations.&lt;/p&gt;

&lt;p&gt;The tradeoff is complexity. Rust's ownership and lifetime model asks more from the programmer than Python does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrency and parallelism
&lt;/h3&gt;

&lt;p&gt;Some workloads can be divided across CPU cores.&lt;/p&gt;

&lt;p&gt;Rust provides strong tooling for concurrent systems while catching many unsafe memory-access patterns at compile time.&lt;/p&gt;

&lt;p&gt;That does not make every algorithm parallel, but it can provide a strong foundation when parallelism fits the workload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Standalone distribution
&lt;/h3&gt;

&lt;p&gt;Rust-based CLI tools can be distributed as native executables or packaged through wheels for supported platforms.&lt;/p&gt;

&lt;p&gt;For end users, that can make startup and installation feel simple even though the implementation underneath is native code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The wrong conclusion: "rewrite everything in Rust"
&lt;/h2&gt;

&lt;p&gt;This is where language discussions often become unhelpful.&lt;/p&gt;

&lt;p&gt;Imagine an API request that spends its time like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
   │
   ▼
Python logic       2 ms
   │
   ▼
Database query    80 ms
   │
   ▼
External API     200 ms
   │
   ▼
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose you rewrite the 2 ms of Python logic and reduce it to 0.5 ms.&lt;/p&gt;

&lt;p&gt;The total request barely changes.&lt;/p&gt;

&lt;p&gt;The correct question is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can Rust run this function faster?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The useful question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Is this code actually the bottleneck that matters?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rust makes the most sense where the workload justifies the additional implementation complexity.&lt;/p&gt;

&lt;p&gt;That is why its adoption is so visible in areas such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parsing
Static analysis
Dependency resolution
Validation
Serialization
Tokenization
Query optimization
Data processing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those are workloads where CPU cost, memory layout, latency, and repeated execution can become visible to users.&lt;/p&gt;

&lt;p&gt;For ordinary application code dominated by network calls, databases, or external services, a rewrite may offer little practical benefit.&lt;/p&gt;

&lt;p&gt;Measure first.&lt;/p&gt;

&lt;p&gt;Change second.&lt;/p&gt;




&lt;h2&gt;
  
  
  Python and Rust are becoming complementary
&lt;/h2&gt;

&lt;p&gt;The most useful lesson is not "Rust versus Python."&lt;/p&gt;

&lt;p&gt;It is that a system does not need to use one language everywhere.&lt;/p&gt;

&lt;p&gt;A practical architecture can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────┐
│             Python              │
│                                 │
│  Application logic              │
│  APIs                           │
│  Automation                     │
│  Data workflows                 │
│  Developer-facing interfaces    │
└────────────────┬────────────────┘
                 │
                 │ Native boundary
                 │
┌────────────────▼────────────────┐
│              Rust               │
│                                 │
│  Parsing                        │
│  Validation                     │
│  Dependency resolution          │
│  Static analysis                │
│  Query execution                │
│  Tokenization                   │
│  Performance-sensitive engines  │
└─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python stays where its readability, ecosystem, flexibility, and developer experience are valuable.&lt;/p&gt;

&lt;p&gt;Rust is introduced where native execution, memory control, or systems-level implementation characteristics provide a measurable benefit.&lt;/p&gt;

&lt;p&gt;The boundary between them is what makes the combination powerful.&lt;/p&gt;




&lt;h2&gt;
  
  
  Should a Python developer learn Rust?
&lt;/h2&gt;

&lt;p&gt;Not simply because Rust is fashionable.&lt;/p&gt;

&lt;p&gt;You do not need Rust to use Ruff.&lt;/p&gt;

&lt;p&gt;You do not need Rust to use uv.&lt;/p&gt;

&lt;p&gt;You do not need Rust to write Pydantic models or use Polars from Python.&lt;/p&gt;

&lt;p&gt;That is one of the strengths of the architecture.&lt;/p&gt;

&lt;p&gt;But Rust becomes increasingly relevant if you build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;developer tools,&lt;/li&gt;
&lt;li&gt;parsers or compilers,&lt;/li&gt;
&lt;li&gt;static analyzers,&lt;/li&gt;
&lt;li&gt;database or query engines,&lt;/li&gt;
&lt;li&gt;serialization libraries,&lt;/li&gt;
&lt;li&gt;performance-sensitive Python extensions,&lt;/li&gt;
&lt;li&gt;infrastructure that spends substantial CPU time in repeatable native workloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then Rust gives you another option:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Keep the Python interface. Replace only the part that actually needs a different implementation model.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is much more sensible than rewriting an entire application because one component is slow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rust is not replacing Python. It is disappearing underneath it.
&lt;/h2&gt;

&lt;p&gt;That may be the most important part of this trend.&lt;/p&gt;

&lt;p&gt;A developer runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ruff check &lt;span class="nb"&gt;.&lt;/span&gt;
uv &lt;span class="nb"&gt;sync
&lt;/span&gt;ty check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;polars&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They are still Python developers.&lt;/p&gt;

&lt;p&gt;They may not think about Rust at all.&lt;/p&gt;

&lt;p&gt;And that may be the strongest sign that the model works.&lt;/p&gt;

&lt;p&gt;Rust does not need Python developers to stop writing Python.&lt;/p&gt;

&lt;p&gt;It only needs to be useful in the layers where Python benefits from a different implementation model underneath.&lt;/p&gt;

&lt;p&gt;That is what Ruff, uv, Pydantic Core, Polars, Tokenizers, PyO3, maturin, and similar projects demonstrate.&lt;/p&gt;

&lt;p&gt;So the interesting future is probably not &lt;strong&gt;Python or Rust&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is increasingly &lt;strong&gt;Python on top, Rust where it matters underneath&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And for modern Python tooling, that combination may be more important than choosing a winner.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Primary or official project documentation used to verify the technical claims in this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ruff documentation: &lt;a href="https://docs.astral.sh/ruff/" rel="noopener noreferrer"&gt;https://docs.astral.sh/ruff/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ruff formatter: &lt;a href="https://docs.astral.sh/ruff/formatter/" rel="noopener noreferrer"&gt;https://docs.astral.sh/ruff/formatter/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ruff FAQ: &lt;a href="https://docs.astral.sh/ruff/faq/" rel="noopener noreferrer"&gt;https://docs.astral.sh/ruff/faq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;uv documentation: &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;https://docs.astral.sh/uv/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;uv pip interface: &lt;a href="https://docs.astral.sh/uv/pip/" rel="noopener noreferrer"&gt;https://docs.astral.sh/uv/pip/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;uv pip compatibility: &lt;a href="https://docs.astral.sh/uv/pip/compatibility/" rel="noopener noreferrer"&gt;https://docs.astral.sh/uv/pip/compatibility/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ty documentation: &lt;a href="https://docs.astral.sh/ty/" rel="noopener noreferrer"&gt;https://docs.astral.sh/ty/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pydantic V2 architecture/background: &lt;a href="https://pydantic.dev/articles/pydantic-v2" rel="noopener noreferrer"&gt;https://pydantic.dev/articles/pydantic-v2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Polars user guide: &lt;a href="https://docs.pola.rs/user-guide/" rel="noopener noreferrer"&gt;https://docs.pola.rs/user-guide/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hugging Face Tokenizers: &lt;a href="https://huggingface.co/docs/tokenizers/" rel="noopener noreferrer"&gt;https://huggingface.co/docs/tokenizers/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenAI tiktoken repository: &lt;a href="https://github.com/openai/tiktoken" rel="noopener noreferrer"&gt;https://github.com/openai/tiktoken&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PyO3 guide: &lt;a href="https://pyo3.rs/" rel="noopener noreferrer"&gt;https://pyo3.rs/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;maturin documentation: &lt;a href="https://www.maturin.rs/" rel="noopener noreferrer"&gt;https://www.maturin.rs/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rust</category>
      <category>python</category>
      <category>programming</category>
      <category>tooling</category>
    </item>
    <item>
      <title>MCP vs A2A: How AI Agents Connect to Tools — and to Each Other</title>
      <dc:creator>Nilesh Vishwakarma</dc:creator>
      <pubDate>Sun, 04 Oct 2026 13:29:24 +0000</pubDate>
      <link>https://dev.to/nilesh_vishwakarma_01e134/mcp-vs-a2a-how-ai-agents-connect-to-tools-and-to-each-other-4kbo</link>
      <guid>https://dev.to/nilesh_vishwakarma_01e134/mcp-vs-a2a-how-ai-agents-connect-to-tools-and-to-each-other-4kbo</guid>
      <description>&lt;p&gt;&lt;strong&gt;The short version:&lt;/strong&gt; MCP connects an AI app to tools and data. A2A connects one AI agent to another AI agent. They solve different problems, and many systems use both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why agents need more than a language model
&lt;/h2&gt;

&lt;p&gt;A chatbot only has to produce text. An agent has to get things done.&lt;/p&gt;

&lt;p&gt;To do that, it may need to search a knowledge base, read a file, query a database, create a ticket, or hand part of the job to another agent. A language model cannot do any of that by itself. Something has to connect it to the outside world.&lt;/p&gt;

&lt;p&gt;We already have APIs for connecting software, and they are not going away. The trouble is that every API is different. If each AI app writes its own custom integration for GitHub, for the database, and for the ticketing system, the work gets repeated again and again.&lt;/p&gt;

&lt;p&gt;MCP and A2A are two open standards that reduce that repeated work. Each one covers a different connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP  →  AI app    ↔  tools, data, and context
A2A  →  AI agent  ↔  another AI agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; is an open standard that connects AI applications to the systems where data and tools live.&lt;/p&gt;

&lt;p&gt;A helpful way to picture it is a USB port. Before USB, every device needed its own cable and its own driver. MCP gives tools one standard plug, so a tool built once can work with any AI app that speaks MCP.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6qgmi0ln90uc27lew1s9.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6qgmi0ln90uc27lew1s9.png" alt="How MCP works: user, host, client, server, then tools, resources and prompts" width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How MCP works. Conceptual diagram; implementations can vary.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are three parts to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Host:&lt;/strong&gt; the app the user works in, such as an IDE, a desktop AI app, or your own agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client:&lt;/strong&gt; the piece inside the host that speaks MCP. One host can connect to many servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server:&lt;/strong&gt; the piece that offers capabilities to the AI app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An MCP server can offer three kinds of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt; are actions the model can run, such as &lt;code&gt;search_issues&lt;/code&gt; or &lt;code&gt;create_ticket&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resources&lt;/strong&gt; are content the app can read, such as a file, a document, or a database record.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompts&lt;/strong&gt; are reusable templates, such as "Review this pull request for security problems."&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A quick example
&lt;/h3&gt;

&lt;p&gt;A user asks: &lt;em&gt;"Summarize our release status and create an issue for any blocking database problem."&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The app reads the project roadmap through an MCP &lt;strong&gt;resource&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The model spots a blocking problem.&lt;/li&gt;
&lt;li&gt;The app calls the &lt;code&gt;create_issue&lt;/code&gt; &lt;strong&gt;tool&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The MCP server talks to the real project-management API.&lt;/li&gt;
&lt;li&gt;The result goes back to the user.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice step 4. The MCP server still calls a normal API behind the scenes. MCP does not replace APIs. It sits in front of them and presents them in a form that AI apps can discover and use.&lt;/p&gt;

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

&lt;p&gt;Now picture a company with several specialist agents: a support agent, a billing agent, and a travel agent.&lt;/p&gt;

&lt;p&gt;A customer writes: &lt;em&gt;"Refund my ticket and find me a replacement flight."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The support agent knows the customer and the refund policy. The travel agent owns rebooking. So the support agent needs to hand over part of the job.&lt;/p&gt;

&lt;p&gt;That is different from calling a tool. The other agent does its own reasoning, has its own tools, may ask a question back, and may need minutes or hours to finish.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Agent2Agent (A2A) Protocol&lt;/strong&gt; is an open standard for exactly this. If MCP is a USB port, A2A is closer to how colleagues work together: you find out who does what, hand over a job, check on progress, and get the finished work back.&lt;/p&gt;

&lt;p&gt;Google started A2A in 2025 and donated it to the Linux Foundation. Version 1.0, the first stable release, shipped in March 2026. Since August 2026 it has been hosted by the Agentic AI Foundation, the Linux Foundation body that also hosts MCP.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frpb0yzw6wuws9cnrv6br.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frpb0yzw6wuws9cnrv6br.png" alt="How A2A works: a client agent and a remote agent exchange messages, tasks and artifacts" width="800" height="566"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;How A2A works. Conceptual diagram; implementations can vary.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A2A has four building blocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent Card:&lt;/strong&gt; a small JSON file where an agent describes itself, including its name, skills, address, and security requirements. Think of it as a business card that other agents can read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Message:&lt;/strong&gt; one turn of conversation between agents. It can carry text, files, or structured data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task:&lt;/strong&gt; a unit of work with its own ID and status. It can be working, waiting for input, completed, or failed, so the client can follow long jobs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Artifact:&lt;/strong&gt; the finished output of a task, such as a report, an image, or structured data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One point matters here. The remote agent keeps its inner workings private. Other agents see what it can do, not how it does it.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP vs A2A at a glance
&lt;/h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxn1ygdoknnknslv8uqg6.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxn1ygdoknnknslv8uqg6.png" alt="MCP vs A2A at a glance" width="800" height="566"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;MCP and A2A solve different interoperability problems.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;MCP&lt;/th&gt;
&lt;th&gt;A2A&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Connects&lt;/td&gt;
&lt;td&gt;AI app ↔ tools and data&lt;/td&gt;
&lt;td&gt;Agent ↔ agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main building blocks&lt;/td&gt;
&lt;td&gt;Tools, resources, prompts&lt;/td&gt;
&lt;td&gt;Agent Card, messages, tasks, artifacts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other side is&lt;/td&gt;
&lt;td&gt;An MCP server&lt;/td&gt;
&lt;td&gt;An independent agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical request&lt;/td&gt;
&lt;td&gt;"Search GitHub issues"&lt;/td&gt;
&lt;td&gt;"Ask the security agent to investigate this incident"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-running work&lt;/td&gt;
&lt;td&gt;Possible&lt;/td&gt;
&lt;td&gt;Built in through tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replaces REST APIs?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither one is "better." They describe different relationships.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using both together
&lt;/h2&gt;

&lt;p&gt;Most real systems with more than one agent end up using both.&lt;/p&gt;

&lt;p&gt;A user asks a support agent: &lt;em&gt;"Find out why our customer's deployment failed, check whether it affects their SLA, and draft a reply."&lt;/em&gt;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc1eqxlublmaxlzmau6za.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc1eqxlublmaxlzmau6za.png" alt="MCP and A2A together: a support agent and a contract agent, each with its own MCP server" width="800" height="566"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A2A handles agent-to-agent collaboration. MCP handles access to tools and context.&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The support agent uses &lt;strong&gt;MCP&lt;/strong&gt; to read logs, tickets, and customer data.&lt;/li&gt;
&lt;li&gt;It sees that the incident may affect the customer's contract.&lt;/li&gt;
&lt;li&gt;It hands that question to a contract agent through &lt;strong&gt;A2A&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The contract agent uses its own &lt;strong&gt;MCP&lt;/strong&gt; server to look up the contract.&lt;/li&gt;
&lt;li&gt;The contract agent returns its findings as an artifact.&lt;/li&gt;
&lt;li&gt;The support agent combines everything and drafts the reply.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The support agent never needs to learn the contract system's API. The contract agent never has to expose its internal tools. Each side has one clear job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one do you need?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your AI app needs tools or data.&lt;/strong&gt; Use MCP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your agent needs help from another independent agent.&lt;/strong&gt; Use A2A.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your agents work together and also need tools.&lt;/strong&gt; Use both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You have one simple integration that already works.&lt;/strong&gt; Use neither. A plain REST API or function call is fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is easy to forget. If your app calls a single weather API, adding a protocol only adds complexity. Use MCP or A2A when they save you integration work, not because they are new.&lt;/p&gt;

&lt;p&gt;A simple test: ask what sits on the other side. If it mainly offers tools or data, MCP fits. If it is an independent agent that accepts work, A2A fits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things a protocol will not fix
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Security.&lt;/strong&gt; A standard way to call &lt;code&gt;delete_file&lt;/code&gt; does not make it safe to call. You still need authentication, permissions, input validation, audit logs, and human approval for risky actions. The MCP team says plainly that tool annotations are hints and not enforcement. A tool that describes itself as read-only may not be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quality.&lt;/strong&gt; MCP standardizes how a tool is called. It cannot promise the tool works correctly. A2A standardizes how agents talk. It cannot promise the other agent is right. An agent that speaks your protocol is not automatically an agent you should trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  One 2026 change worth knowing
&lt;/h2&gt;

&lt;p&gt;If you read older MCP tutorials, watch out for one thing. The MCP specification released on July 28, 2026 made the protocol stateless.&lt;/p&gt;

&lt;p&gt;Older versions began every connection with an &lt;code&gt;initialize&lt;/code&gt; handshake and tracked it with a session ID. Both are gone. Every request now carries everything it needs, so any server instance behind a load balancer can answer it.&lt;/p&gt;

&lt;p&gt;Your application can still keep state. It just no longer lives inside the protocol session. Many articles from 2025 describe the old behavior, so check which version your SDK supports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP&lt;/strong&gt; is how an AI app reaches tools, data, and prompts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A2A&lt;/strong&gt; is how one agent hands work to another agent.&lt;/li&gt;
&lt;li&gt;They complement each other, and both sit on top of the APIs you already have.&lt;/li&gt;
&lt;li&gt;Sometimes the right answer is neither.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pick the protocol that matches the relationship between your components, not the one getting the most attention this month.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article reflects the MCP 2026-07-28 specification and A2A 1.0, as of October 2026. Both projects are still evolving, so check the version your tools support.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/" rel="noopener noreferrer"&gt;MCP 2026-07-28 specification release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/server/index" rel="noopener noreferrer"&gt;MCP specification: tools, resources, and prompts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ts.sdk.modelcontextprotocol.io/v2/" rel="noopener noreferrer"&gt;MCP TypeScript SDK v2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/" rel="noopener noreferrer"&gt;MCP blog: tool annotations are hints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://a2a-protocol.org/latest/specification/" rel="noopener noreferrer"&gt;A2A specification v1.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://a2a-protocol.org/latest/topics/key-concepts/" rel="noopener noreferrer"&gt;A2A core concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://a2a-protocol.org/latest/whats-new-v1/" rel="noopener noreferrer"&gt;What's new in A2A v1.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://a2a-protocol.org/dev/blog/2026/03/12/a2a-protocol-ships-v10-production-ready-standard-for-agent-to-agent-communication/" rel="noopener noreferrer"&gt;A2A v1.0 release announcement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>agents</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Why HTTP/3 Exists: The Limits of HTTP/2 and the Rise of QUIC</title>
      <dc:creator>Nilesh Vishwakarma</dc:creator>
      <pubDate>Mon, 28 Sep 2026 19:21:07 +0000</pubDate>
      <link>https://dev.to/nilesh_vishwakarma_01e134/why-http3-exists-the-limits-of-http2-and-the-rise-of-quic-46d0</link>
      <guid>https://dev.to/nilesh_vishwakarma_01e134/why-http3-exists-the-limits-of-http2-and-the-rise-of-quic-46d0</guid>
      <description>&lt;p&gt;HTTP/2 already gave us multiplexing, binary framing, and better use of a single TCP connection.&lt;/p&gt;

&lt;p&gt;So why did we need HTTP/3?&lt;/p&gt;

&lt;p&gt;The answer is not really HTTP itself.&lt;/p&gt;

&lt;p&gt;The answer is &lt;strong&gt;TCP&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;HTTP/3 keeps familiar HTTP semantics, but it changes the transport underneath by using &lt;strong&gt;QUIC over UDP&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That change affects packet loss, stream independence, connection setup, and network migration.&lt;/p&gt;

&lt;p&gt;A simple way to remember the evolution is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 → more connections

HTTP/2 → more streams over one TCP connection

HTTP/3 → more streams over QUIC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  HTTP and QUIC Are Not the Same Thing
&lt;/h2&gt;

&lt;p&gt;HTTP defines what the message means.&lt;/p&gt;

&lt;p&gt;For example:&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;GET /products
Host: example.com
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTTP also defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;methods such as &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;status codes such as &lt;code&gt;200&lt;/code&gt;, &lt;code&gt;404&lt;/code&gt;, and &lt;code&gt;500&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;headers&lt;/li&gt;
&lt;li&gt;caching rules&lt;/li&gt;
&lt;li&gt;request and response behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple mental model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP = what the message means
TCP / QUIC = how the message travels
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1
   ↓
TCP
   ↓
IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/2
   ↓
TCP
   ↓
IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTTP/3 changes the transport:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/3
   ↓
QUIC
   ↓
UDP
   ↓
IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;HTTP/3 still uses normal HTTP ideas such as &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, status codes, headers, and caching. The main change is how the data moves across the network.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  HTTP/1.1: More Connections for More Work
&lt;/h2&gt;

&lt;p&gt;HTTP/1.1 has been used for a long time and is still important today.&lt;/p&gt;

&lt;p&gt;A request might look like this:&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="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/index.html&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt;
&lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text/html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modern websites need more than one file.&lt;/p&gt;

&lt;p&gt;A page may need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTML
CSS
JavaScript
images
fonts
API responses
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser wants to download many of these quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  A simple analogy
&lt;/h3&gt;

&lt;p&gt;Imagine a supermarket with one checkout line.&lt;/p&gt;

&lt;p&gt;If one person has a very large cart, everyone behind that person has to wait.&lt;/p&gt;

&lt;p&gt;HTTP/1.1 can reuse connections, and it also defined pipelining, but pipelined responses still have to come back in order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request A ───────────────►
Request B ───────────────►
Request C ───────────────►

Response A ◄──────── slow
Response B ◄──────── waiting
Response C ◄──────── waiting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if response C is ready, it cannot simply jump ahead of response A in that pipelined sequence.&lt;/p&gt;

&lt;p&gt;This waiting problem is one example of &lt;strong&gt;head-of-line blocking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because of this and other practical limits, browsers have historically used multiple TCP connections to get more concurrency.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HTTP/1.1: use more connections so more work can happen at the same time.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  HTTP/2: Many Streams on One TCP Connection
&lt;/h2&gt;

&lt;p&gt;HTTP/2 changed how HTTP data is represented and sent.&lt;/p&gt;

&lt;p&gt;Instead of relying mainly on several separate TCP connections, HTTP/2 can place many logical streams inside one TCP connection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TCP connection
│
├── Stream 1 → HTML
├── Stream 3 → CSS
├── Stream 5 → JavaScript
├── Stream 7 → Image
└── Stream 9 → API response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think of HTTP/1.1 as using several roads.&lt;/p&gt;

&lt;p&gt;HTTP/2 is more like using &lt;strong&gt;one large highway with several lanes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Several requests can move at the same time without opening a new TCP connection for every piece of work.&lt;/p&gt;

&lt;p&gt;HTTP/2 also uses binary frames such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HEADERS
DATA
SETTINGS
WINDOW_UPDATE
RST_STREAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These frames belong to different streams and can be mixed together on the same connection.&lt;/p&gt;

&lt;p&gt;That was a major improvement.&lt;/p&gt;

&lt;p&gt;But one important problem remained.&lt;/p&gt;

&lt;p&gt;All those HTTP/2 streams still share &lt;strong&gt;one TCP connection&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The TCP Problem in HTTP/2
&lt;/h2&gt;

&lt;p&gt;TCP gives applications reliable and ordered data.&lt;/p&gt;

&lt;p&gt;That is useful.&lt;/p&gt;

&lt;p&gt;But ordered delivery also means missing data may need to be recovered before later bytes are passed to the application.&lt;/p&gt;

&lt;p&gt;Imagine this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Packet 1  ✓
Packet 2  ✗ lost
Packet 3  ✓
Packet 4  ✓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TCP may need to recover the missing part before later ordered data can be delivered to HTTP/2.&lt;/p&gt;

&lt;p&gt;Now remember that several HTTP/2 streams share the same TCP connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream A ─┐
Stream B ─┼──► TCP
Stream C ─┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If TCP has to wait for missing data, several HTTP/2 streams can be delayed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Train analogy
&lt;/h3&gt;

&lt;p&gt;Imagine HTTP/2 streams as passengers sitting in different train compartments.&lt;/p&gt;

&lt;p&gt;The passengers are separate, but they are all on the same train.&lt;/p&gt;

&lt;p&gt;If the train stops because the track is blocked, every compartment stops too.&lt;/p&gt;

&lt;p&gt;That is why HTTP/2 multiplexing does not completely remove TCP-level head-of-line blocking.&lt;/p&gt;




&lt;h2&gt;
  
  
  HTTP/3: Change the Transport
&lt;/h2&gt;

&lt;p&gt;HTTP/3 keeps the idea of multiple streams, but moves them from TCP to &lt;strong&gt;QUIC&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/3
   ↓
QUIC
   ↓
UDP
   ↓
IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;QUIC is a transport protocol built on UDP.&lt;/p&gt;

&lt;p&gt;That does &lt;strong&gt;not&lt;/strong&gt; mean HTTP/3 simply sends unreliable UDP packets and hopes for the best.&lt;/p&gt;

&lt;p&gt;QUIC adds transport features HTTP needs, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reliable delivery&lt;/li&gt;
&lt;li&gt;congestion control&lt;/li&gt;
&lt;li&gt;flow control&lt;/li&gt;
&lt;li&gt;loss recovery&lt;/li&gt;
&lt;li&gt;encryption&lt;/li&gt;
&lt;li&gt;multiple streams&lt;/li&gt;
&lt;li&gt;connection migration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this explanation is too simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/3 = HTTP over UDP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A better explanation is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HTTP/3 runs over QUIC, and QUIC uses UDP as its foundation.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why QUIC Streams Matter
&lt;/h2&gt;

&lt;p&gt;QUIC allows several streams inside one connection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QUIC connection

├── Stream A → HTML
├── Stream B → CSS
├── Stream C → JavaScript
└── Stream D → Image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine Stream B loses some data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream A ─── ✓ ─── ✓ ─── ✓ ───►

Stream B ─── ✓ ─── X ──────────►
                  lost

Stream C ─── ✓ ─── ✓ ─── ✓ ───►
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stream B may need to wait for its missing data.&lt;/p&gt;

&lt;p&gt;But Stream A and Stream C do not automatically have to stop and wait for Stream B.&lt;/p&gt;

&lt;p&gt;That is one of the most important differences between HTTP/2 over TCP and HTTP/3 over QUIC.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does HTTP/3 remove all head-of-line blocking?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;If data is missing inside one QUIC stream, later data in that same stream may still need to wait.&lt;/p&gt;

&lt;p&gt;The important improvement is that &lt;strong&gt;one blocked stream does not inherently block unrelated streams in the same way TCP's ordered delivery can affect HTTP/2 streams&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  HTTP/2 vs HTTP/3: The Simple Version
&lt;/h2&gt;

&lt;p&gt;HTTP/2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream A ─┐
Stream B ─┼──► One TCP connection
Stream C ─┘

Packet loss in TCP
        ↓
Several streams may wait
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTTP/3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream A ───► QUIC Stream A
Stream B ───► QUIC Stream B
Stream C ───► QUIC Stream C

Loss in Stream B
        ↓
Stream B waits

A and C can still make progress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a conceptual explanation. Real packet handling is more complex.&lt;/p&gt;




&lt;h2&gt;
  
  
  QUIC Also Integrates Security
&lt;/h2&gt;

&lt;p&gt;HTTP/2 normally uses TLS over TCP for secure web traffic.&lt;/p&gt;

&lt;p&gt;A simplified view is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/2
   ↓
TLS
   ↓
TCP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;QUIC was designed together with TLS 1.3.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/3
   ↓
QUIC + TLS 1.3
   ↓
UDP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can reduce some connection setup delay, depending on the network and whether the connection is new or resumed.&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; mean HTTP/3 is always faster.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is 0-RTT?
&lt;/h2&gt;

&lt;p&gt;QUIC can support &lt;strong&gt;0-RTT&lt;/strong&gt; data in some resumed connections.&lt;/p&gt;

&lt;p&gt;Very simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First visit:
Client ── handshake ── Server

Later visit:
Client ── early data ──► Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can save time in some situations.&lt;/p&gt;

&lt;p&gt;But 0-RTT data has replay risks, so applications have to be careful about which operations are allowed to use it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;0-RTT can help some repeat connections start sooner, but it is not used for everything.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Connection Migration: Useful on Mobile Networks
&lt;/h2&gt;

&lt;p&gt;Imagine you are using your phone at home on Wi-Fi.&lt;/p&gt;

&lt;p&gt;Then you walk outside and your phone switches to mobile data.&lt;/p&gt;

&lt;p&gt;Your network path may change.&lt;/p&gt;

&lt;p&gt;QUIC supports connection migration using Connection IDs and path validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wi-Fi
  ↓
QUIC connection
  ↓
Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mobile network
  ↓
Same logical QUIC connection can continue
  ↓
Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real process includes security checks and path validation, but the basic idea is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;QUIC is designed to handle some network changes without always throwing away the whole connection.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  HTTP/1.1 vs HTTP/2 vs HTTP/3
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;HTTP/1.1&lt;/th&gt;
&lt;th&gt;HTTP/2&lt;/th&gt;
&lt;th&gt;HTTP/3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Main transport&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;QUIC over UDP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiplexing&lt;/td&gt;
&lt;td&gt;No HTTP/2-style multiplexing&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical concurrency&lt;/td&gt;
&lt;td&gt;Multiple TCP connections&lt;/td&gt;
&lt;td&gt;Multiple streams on one TCP connection&lt;/td&gt;
&lt;td&gt;Multiple streams on one QUIC connection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Field compression&lt;/td&gt;
&lt;td&gt;No HPACK/QPACK&lt;/td&gt;
&lt;td&gt;HPACK&lt;/td&gt;
&lt;td&gt;QPACK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secure web traffic&lt;/td&gt;
&lt;td&gt;TLS over TCP&lt;/td&gt;
&lt;td&gt;TLS over TCP&lt;/td&gt;
&lt;td&gt;TLS integrated with QUIC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection migration&lt;/td&gt;
&lt;td&gt;No QUIC-like native feature&lt;/td&gt;
&lt;td&gt;No QUIC-like native feature&lt;/td&gt;
&lt;td&gt;Supported by QUIC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main strength&lt;/td&gt;
&lt;td&gt;Compatibility&lt;/td&gt;
&lt;td&gt;Mature multiplexing&lt;/td&gt;
&lt;td&gt;Better stream independence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main tradeoff&lt;/td&gt;
&lt;td&gt;Limited per-connection concurrency&lt;/td&gt;
&lt;td&gt;TCP-level blocking can still matter&lt;/td&gt;
&lt;td&gt;More deployment and implementation complexity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Is HTTP/3 Always Faster?
&lt;/h2&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;HTTP/3 can have advantages when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;latency is high&lt;/li&gt;
&lt;li&gt;packet loss happens&lt;/li&gt;
&lt;li&gt;many resources are transferred at once&lt;/li&gt;
&lt;li&gt;users move between networks&lt;/li&gt;
&lt;li&gt;connection setup time matters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But on a fast and stable network with an already-open HTTP/2 connection, the difference may be small.&lt;/p&gt;

&lt;p&gt;Performance also depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser implementation&lt;/li&gt;
&lt;li&gt;server implementation&lt;/li&gt;
&lt;li&gt;CDN&lt;/li&gt;
&lt;li&gt;congestion control&lt;/li&gt;
&lt;li&gt;packet loss&lt;/li&gt;
&lt;li&gt;round-trip time&lt;/li&gt;
&lt;li&gt;bandwidth&lt;/li&gt;
&lt;li&gt;resource size&lt;/li&gt;
&lt;li&gt;connection reuse&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So it is better to say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HTTP/3 can perform better in some network conditions, but it is not automatically faster everywhere.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  HTTP/3 Has Tradeoffs Too
&lt;/h2&gt;

&lt;p&gt;HTTP/3 solves some problems, but it also adds new ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  UDP may be blocked
&lt;/h3&gt;

&lt;p&gt;Some networks, firewalls, or middleboxes may block or interfere with UDP.&lt;/p&gt;

&lt;p&gt;That means a client may need to fall back to HTTP/2 or HTTP/1.1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Try HTTP/3
    │
    ├── works → use HTTP/3
    │
    └── fails → use TCP-based HTTP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  QUIC is not simple
&lt;/h3&gt;

&lt;p&gt;QUIC still has to handle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reliability
loss detection
congestion control
flow control
streams
encryption
connection management
path validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complexity did not disappear.&lt;/p&gt;

&lt;p&gt;It moved into QUIC.&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure needs support
&lt;/h3&gt;

&lt;p&gt;Servers, CDNs, load balancers, firewalls, and monitoring tools may need proper QUIC support.&lt;/p&gt;

&lt;p&gt;So moving to HTTP/3 is not only a browser change.&lt;/p&gt;

&lt;p&gt;It can also be an infrastructure decision.&lt;/p&gt;




&lt;h2&gt;
  
  
  Which Version Is Better?
&lt;/h2&gt;

&lt;p&gt;There is no universal winner.&lt;/p&gt;

&lt;p&gt;A better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Which version fits the situation?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  HTTP/1.1
&lt;/h3&gt;

&lt;p&gt;Useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;compatibility matters most&lt;/li&gt;
&lt;li&gt;older systems are involved&lt;/li&gt;
&lt;li&gt;traffic is simple&lt;/li&gt;
&lt;li&gt;infrastructure is intentionally basic&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  HTTP/2
&lt;/h3&gt;

&lt;p&gt;Useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP/2 support is already mature&lt;/li&gt;
&lt;li&gt;you want multiplexing&lt;/li&gt;
&lt;li&gt;your TCP-based infrastructure already works well&lt;/li&gt;
&lt;li&gt;adding QUIC would add unnecessary complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  HTTP/3
&lt;/h3&gt;

&lt;p&gt;Useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;users are often on mobile networks&lt;/li&gt;
&lt;li&gt;latency is important&lt;/li&gt;
&lt;li&gt;packet loss is common&lt;/li&gt;
&lt;li&gt;many resources are transferred at once&lt;/li&gt;
&lt;li&gt;users may switch between Wi-Fi and mobile data&lt;/li&gt;
&lt;li&gt;the infrastructure already supports QUIC well&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The version number alone should not decide the choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Evolution in One Diagram
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1
More connections for concurrency

        ↓

HTTP/2
More streams on one TCP connection

        ↓

HTTP/3
More streams on a transport designed
to keep them more independent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, using a road analogy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP/1.1:&lt;/strong&gt; use more roads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP/2:&lt;/strong&gt; use one highway with many lanes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP/3:&lt;/strong&gt; redesign the transport so those lanes are less dependent on each other&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1
More connections

HTTP/2
More streams over TCP

HTTP/3
More streams over QUIC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTTP/3 is not automatically faster everywhere.&lt;/p&gt;

&lt;p&gt;Its main advantage is that QUIC is designed around independent streams, integrated security, connection migration, and a transport model that handles some forms of packet loss better than HTTP/2 over TCP.&lt;/p&gt;

&lt;p&gt;That makes HTTP/3 especially useful when latency, packet loss, and mobile connectivity matter.&lt;/p&gt;




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

&lt;p&gt;HTTP/3 is not interesting simply because it uses UDP.&lt;/p&gt;

&lt;p&gt;The real change is QUIC.&lt;/p&gt;

&lt;p&gt;HTTP/1.1 commonly used more connections to get more concurrency.&lt;/p&gt;

&lt;p&gt;HTTP/2 put many streams on one TCP connection.&lt;/p&gt;

&lt;p&gt;HTTP/3 moved those streams onto a transport designed with stream independence, security, loss recovery, and network changes in mind.&lt;/p&gt;

&lt;p&gt;That does not make HTTP/3 magically faster everywhere.&lt;/p&gt;

&lt;p&gt;But it gives the web a transport model that can behave better when latency, packet loss, and changing networks matter.&lt;/p&gt;

&lt;p&gt;If you remember only one thing, remember this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HTTP/1.1 opened more connections. HTTP/2 added more streams. HTTP/3 changed the transport underneath those streams.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;This article is based mainly on the following IETF standards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9112" rel="noopener noreferrer"&gt;RFC 9112 — HTTP/1.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9113" rel="noopener noreferrer"&gt;RFC 9113 — HTTP/2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9114" rel="noopener noreferrer"&gt;RFC 9114 — HTTP/3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9000" rel="noopener noreferrer"&gt;RFC 9000 — QUIC: A UDP-Based Multiplexed and Secure Transport&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9001" rel="noopener noreferrer"&gt;RFC 9001 — Using TLS to Secure QUIC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9204" rel="noopener noreferrer"&gt;RFC 9204 — QPACK: Field Compression for HTTP/3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you found this useful, feel free to share your experience with HTTP/2, HTTP/3, or QUIC in the comments.&lt;/p&gt;

</description>
      <category>http</category>
      <category>networking</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>Introducing Injectlynx: Attribute-Free Compile-Time Dependency Injection for .NET</title>
      <dc:creator>Nilesh Vishwakarma</dc:creator>
      <pubDate>Sun, 02 Aug 2026 12:27:35 +0000</pubDate>
      <link>https://dev.to/nilesh_vishwakarma_01e134/introducing-injectlynx-attribute-free-compile-time-dependency-injection-for-net-91m</link>
      <guid>https://dev.to/nilesh_vishwakarma_01e134/introducing-injectlynx-attribute-free-compile-time-dependency-injection-for-net-91m</guid>
      <description>&lt;p&gt;Dependency Injection (DI) is one of the foundations of modern .NET development. Whether you're building ASP.NET Core APIs, Minimal APIs, Worker Services, or libraries, you're probably using &lt;code&gt;Microsoft.Extensions.DependencyInjection&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It works well, but as applications grow, one challenge becomes obvious: &lt;strong&gt;service registration&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Most teams end up choosing one of two approaches.&lt;/p&gt;

&lt;p&gt;The first is manual registration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOrderService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;&amp;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;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IPaymentGateway&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StripePaymentGateway&lt;/span&gt;&lt;span class="p"&gt;&amp;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;AddTransient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetOrderQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="n"&gt;GetOrderQueryHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is explicit and easy to understand, but maintaining hundreds of registrations quickly becomes repetitive and error-prone.&lt;/p&gt;

&lt;p&gt;The second option is runtime assembly scanning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assembly scanning removes much of the boilerplate, but it relies on runtime discovery and reflection. That can make applications harder to reason about, especially when working with trimming, Native AOT, or large modular solutions.&lt;/p&gt;

&lt;p&gt;I wanted something that combined the best parts of both approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No repetitive manual registrations&lt;/li&gt;
&lt;li&gt;No attributes on every service class&lt;/li&gt;
&lt;li&gt;No runtime assembly scanning&lt;/li&gt;
&lt;li&gt;No custom DI container&lt;/li&gt;
&lt;li&gt;Full compatibility with Microsoft Dependency Injection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That idea became &lt;strong&gt;Injectlynx&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  What is Injectlynx?
&lt;/h1&gt;

&lt;p&gt;Injectlynx is an &lt;strong&gt;attribute-free, convention-based compile-time dependency injection toolkit for .NET&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of discovering services at runtime, Injectlynx uses a &lt;strong&gt;Roslyn Incremental Source Generator&lt;/strong&gt; to generate normal &lt;code&gt;IServiceCollection&lt;/code&gt; registrations during compilation.&lt;/p&gt;

&lt;p&gt;The generated code is exactly what you would write by hand.&lt;/p&gt;

&lt;p&gt;There is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ No runtime reflection for service discovery&lt;/li&gt;
&lt;li&gt;✅ No assembly scanning&lt;/li&gt;
&lt;li&gt;✅ No custom DI container&lt;/li&gt;
&lt;li&gt;✅ No attributes on every service&lt;/li&gt;
&lt;li&gt;✅ No startup performance penalty for service discovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You continue using the standard Microsoft DI container.&lt;/p&gt;

&lt;p&gt;Install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Injectlynx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  The Problem with Traditional Registration
&lt;/h1&gt;

&lt;p&gt;Imagine a project with hundreds of services.&lt;/p&gt;

&lt;p&gt;You eventually end up with code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOrderService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;&amp;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;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ICustomerService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomerService&lt;/span&gt;&lt;span class="p"&gt;&amp;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;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IInvoiceService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;InvoiceService&lt;/span&gt;&lt;span class="p"&gt;&amp;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;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IProductService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ProductService&lt;/span&gt;&lt;span class="p"&gt;&amp;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;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IEmailService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EmailService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Besides being repetitive, this list needs constant maintenance.&lt;/p&gt;

&lt;p&gt;Rename a service.&lt;/p&gt;

&lt;p&gt;Move a namespace.&lt;/p&gt;

&lt;p&gt;Add a new implementation.&lt;/p&gt;

&lt;p&gt;It's easy to forget updating the registration.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Injectlynx Way
&lt;/h1&gt;

&lt;p&gt;Instead of registering every service manually, define your conventions once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Injectlynx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Shop.Application&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;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationServiceConventions&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;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceConventionBuilder&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&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;FromNamespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Shop.Application.Services"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhereNameEndsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Service"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsMatchingInterface&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithScopedLifetime&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;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;Now create a normal service.&lt;br&gt;
&lt;/p&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;interface&lt;/span&gt; &lt;span class="nc"&gt;IOrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;OrderSummary&lt;/span&gt; &lt;span class="nf"&gt;GetOrder&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;id&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IOrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;OrderSummary&lt;/span&gt; &lt;span class="nf"&gt;GetOrder&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;id&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="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Created"&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;p&gt;During compilation, Injectlynx generates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOrderService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No attributes.&lt;/p&gt;

&lt;p&gt;No reflection.&lt;/p&gt;

&lt;p&gt;No runtime scanning.&lt;/p&gt;




&lt;h1&gt;
  
  
  Keep Your Service Classes Clean
&lt;/h1&gt;

&lt;p&gt;Many compile-time DI libraries require decorating every implementation.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Scoped&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&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;p&gt;Injectlynx deliberately avoids that.&lt;/p&gt;

&lt;p&gt;Your business classes stay focused on business logic instead of dependency injection metadata.&lt;/p&gt;




&lt;h1&gt;
  
  
  Works with Microsoft Dependency Injection
&lt;/h1&gt;

&lt;p&gt;Injectlynx doesn't replace the built-in container.&lt;/p&gt;

&lt;p&gt;The generated output is simply standard Microsoft DI code.&lt;/p&gt;

&lt;p&gt;Startup remains familiar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&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;AddInjectlynxServices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, in larger solutions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&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;AddApplicationServices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddInfrastructureServices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The registration methods are generated automatically during build.&lt;/p&gt;




&lt;h1&gt;
  
  
  More Than Just Service Registration
&lt;/h1&gt;

&lt;p&gt;Injectlynx also supports scenarios commonly found in enterprise applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convention-based registration
&lt;/h2&gt;

&lt;p&gt;Register services by namespace, naming pattern, or interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open generic registration
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TEntity&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;IRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generated automatically as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&amp;gt;),&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&amp;gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Multiple modules
&lt;/h2&gt;

&lt;p&gt;Each project can expose its own generated registration method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&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;AddApplicationServices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddInfrastructureServices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDomainServices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Property and method injection
&lt;/h2&gt;

&lt;p&gt;Constructor injection should always be the default.&lt;/p&gt;

&lt;p&gt;However, property and method injection are available when working with legacy code, framework-created instances, optional dependencies, or initialization scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build-time diagnostics
&lt;/h2&gt;

&lt;p&gt;Injectlynx validates registrations during compilation and can detect issues before the application starts.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing interfaces&lt;/li&gt;
&lt;li&gt;Duplicate registrations&lt;/li&gt;
&lt;li&gt;Invalid conventions&lt;/li&gt;
&lt;li&gt;Constructor issues&lt;/li&gt;
&lt;li&gt;Lifetime mismatches&lt;/li&gt;
&lt;li&gt;Circular dependencies&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Native AOT Friendly
&lt;/h1&gt;

&lt;p&gt;Modern .NET applications increasingly use trimming and Native AOT.&lt;/p&gt;

&lt;p&gt;Because Injectlynx generates registration code during compilation instead of discovering services at runtime, applications remain predictable and easier to validate in AOT-friendly scenarios.&lt;/p&gt;

&lt;p&gt;This makes it a good choice for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ASP.NET Core APIs&lt;/li&gt;
&lt;li&gt;Minimal APIs&lt;/li&gt;
&lt;li&gt;Worker Services&lt;/li&gt;
&lt;li&gt;Cloud-native applications&lt;/li&gt;
&lt;li&gt;Native AOT projects&lt;/li&gt;
&lt;li&gt;Large modular solutions&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Why I Built Injectlynx
&lt;/h1&gt;

&lt;p&gt;After working on large .NET projects, I noticed the same problems appearing repeatedly.&lt;/p&gt;

&lt;p&gt;Manual registration became difficult to maintain.&lt;/p&gt;

&lt;p&gt;Runtime scanning was convenient but less predictable for modern deployment models.&lt;/p&gt;

&lt;p&gt;Attribute-based compile-time solutions scattered dependency injection metadata across hundreds of files.&lt;/p&gt;

&lt;p&gt;I wanted a solution that kept service classes clean while giving the compiler enough information to generate registrations automatically.&lt;/p&gt;

&lt;p&gt;Injectlynx is the result of that idea.&lt;/p&gt;




&lt;h1&gt;
  
  
  When Should You Use It?
&lt;/h1&gt;

&lt;p&gt;Injectlynx is a good fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your project follows consistent naming conventions.&lt;/li&gt;
&lt;li&gt;You want compile-time generated registrations.&lt;/li&gt;
&lt;li&gt;You don't want attributes on every service.&lt;/li&gt;
&lt;li&gt;You use the Microsoft DI container.&lt;/li&gt;
&lt;li&gt;You are building modular applications.&lt;/li&gt;
&lt;li&gt;You care about trimming or Native AOT.&lt;/li&gt;
&lt;li&gt;You want build-time validation instead of runtime surprises.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For very small projects with only a handful of services, manual registration is still perfectly reasonable.&lt;/p&gt;

&lt;p&gt;If your application loads unknown plugin assemblies dynamically at runtime, runtime scanning may still be the better approach for that specific scenario.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Dependency injection registration shouldn't become a maintenance task.&lt;/p&gt;

&lt;p&gt;Injectlynx keeps service classes clean, removes repetitive registration code, generates standard Microsoft DI registrations during compilation, and provides build-time validation without changing the way you build .NET applications.&lt;/p&gt;

&lt;p&gt;The goal isn't to replace Microsoft's DI container—it's to make using it simpler, cleaner, and more predictable.&lt;/p&gt;

&lt;p&gt;If you're building modern .NET applications and want compile-time registration without runtime scanning or service attributes, give Injectlynx a try.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/it-nilesh/Injectlynx" rel="noopener noreferrer"&gt;https://github.com/it-nilesh/Injectlynx&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NuGet:&lt;/strong&gt; &lt;a href="https://www.nuget.org/packages/Injectlynx" rel="noopener noreferrer"&gt;https://www.nuget.org/packages/Injectlynx&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find the project useful, I'd love to hear your feedback, feature ideas, or contributions. Every suggestion helps improve the developer experience for the .NET community.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>aspdotnet</category>
      <category>core</category>
      <category>dependencyinversion</category>
    </item>
    <item>
      <title>Build a Clean ASP.NET Core API with Signalynx and CQRS</title>
      <dc:creator>Nilesh Vishwakarma</dc:creator>
      <pubDate>Fri, 31 Jul 2026 17:38:02 +0000</pubDate>
      <link>https://dev.to/nilesh_vishwakarma_01e134/build-a-clean-aspnet-core-api-with-signalynx-and-cqrs-1e1l</link>
      <guid>https://dev.to/nilesh_vishwakarma_01e134/build-a-clean-aspnet-core-api-with-signalynx-and-cqrs-1e1l</guid>
      <description>&lt;p&gt;ASP.NET Core controllers often begin with only a few lines of code.&lt;/p&gt;

&lt;p&gt;As an application grows, however, controllers can slowly become responsible for validation, business rules, database operations, logging, error handling, and coordinating multiple services.&lt;/p&gt;

&lt;p&gt;The result is often a controller that is difficult to understand, test, and maintain.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Mediator pattern&lt;/strong&gt; helps solve this problem by moving application logic away from controllers.&lt;/p&gt;

&lt;p&gt;Instead of directly calling services and repositories, the controller sends a strongly typed message. A dedicated handler receives that message and performs the requested operation.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build two common ASP.NET Core API operations using &lt;a href="https://signalynx.inilesh.dev/" rel="noopener noreferrer"&gt;Signalynx&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;POST&lt;/code&gt; endpoint implemented as a command&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;GET&lt;/code&gt; endpoint implemented as a query&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article focuses only on the in-process mediator functionality of Signalynx.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Mediator pattern?
&lt;/h2&gt;

&lt;p&gt;The Mediator pattern introduces a dispatcher between the code requesting an operation and the code responsible for performing it.&lt;/p&gt;

&lt;p&gt;Without a mediator, a controller may directly depend on several components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrdersController
   ├── OrderService
   ├── ValidationService
   ├── LoggingService
   └── OrderRepository
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a mediator, the controller sends a message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Request
     ↓
ASP.NET Core Controller
     ↓
Signalynx
     ↓
Command or Query Handler
     ↓
Application and Database Logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller remains responsible for HTTP concerns, while the handler owns the application use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use a mediator in ASP.NET Core?
&lt;/h2&gt;

&lt;p&gt;A mediator can improve an ASP.NET Core application in several ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thin controllers
&lt;/h3&gt;

&lt;p&gt;Controllers receive HTTP input, dispatch a message, and convert the result into an HTTP response.&lt;/p&gt;

&lt;p&gt;The application logic remains outside the API layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clear use cases
&lt;/h3&gt;

&lt;p&gt;Each command or query represents a specific application operation, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an order&lt;/li&gt;
&lt;li&gt;Retrieve an order&lt;/li&gt;
&lt;li&gt;Update a customer&lt;/li&gt;
&lt;li&gt;Cancel a payment&lt;/li&gt;
&lt;li&gt;Generate an invoice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The message name communicates the intention of the operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better testability
&lt;/h3&gt;

&lt;p&gt;Handlers can be tested independently without starting the complete ASP.NET Core application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lower coupling
&lt;/h3&gt;

&lt;p&gt;The controller does not need to know which repository, service, or infrastructure component performs the operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reusable pipeline logic
&lt;/h3&gt;

&lt;p&gt;Cross-cutting concerns can be implemented through mediator pipeline behaviors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Authorization&lt;/li&gt;
&lt;li&gt;Metrics&lt;/li&gt;
&lt;li&gt;Transactions&lt;/li&gt;
&lt;li&gt;Auditing&lt;/li&gt;
&lt;li&gt;Exception handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents the same logic from being repeated across controllers and handlers.&lt;/p&gt;

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

&lt;p&gt;Signalynx is a strongly typed mediator and dispatcher for modern .NET applications.&lt;/p&gt;

&lt;p&gt;For in-process application dispatch, it supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commands&lt;/li&gt;
&lt;li&gt;Queries&lt;/li&gt;
&lt;li&gt;Request-response messages&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;li&gt;Domain events&lt;/li&gt;
&lt;li&gt;Pipeline behaviors&lt;/li&gt;
&lt;li&gt;Dependency injection&lt;/li&gt;
&lt;li&gt;Validation and logging integrations&lt;/li&gt;
&lt;li&gt;Source-generated registration for NativeAOT scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Signalynx uses asynchronous, strongly typed contracts for messages and handlers.&lt;/p&gt;

&lt;p&gt;You can use only the mediator functionality in a simple ASP.NET Core API and add other packages as the application grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the ASP.NET Core project
&lt;/h2&gt;

&lt;p&gt;Create a new Web API project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new webapi &lt;span class="nt"&gt;-n&lt;/span&gt; SignalynxOrdersApi
&lt;span class="nb"&gt;cd &lt;/span&gt;SignalynxOrdersApi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the Signalynx dependency-injection package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Signalynx.DependencyInjection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This package includes the core runtime required for mediator dispatch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Register Signalynx
&lt;/h2&gt;

&lt;p&gt;Register Signalynx in &lt;code&gt;Program.cs&lt;/code&gt; and tell it which assembly contains your handlers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Signalynx.DependencyInjection&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;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;AddControllers&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;AddSignalynx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&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;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterServicesFromAssembly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&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;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;MapControllers&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;p&gt;Signalynx discovers and registers handlers from the selected assembly during application startup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Organize the project by feature
&lt;/h2&gt;

&lt;p&gt;A feature-based structure keeps each message close to its handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Features/
└── Orders/
    ├── CreateOrder/
    │   ├── CreateOrderCommand.cs
    │   └── CreateOrderCommandHandler.cs
    └── GetOrder/
        ├── GetOrderQuery.cs
        ├── GetOrderQueryHandler.cs
        └── OrderResponse.cs

Controllers/
└── OrdersController.cs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is also known as a vertical-slice structure.&lt;/p&gt;

&lt;p&gt;Instead of placing every command, handler, service, and model into separate global folders, files are grouped by the business operation they implement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create an order with a command
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;POST&lt;/code&gt; request normally changes application state.&lt;/p&gt;

&lt;p&gt;In a mediator-based application, this operation can be represented as a command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the command
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;Features/Orders/CreateOrder/CreateOrderCommand.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Signalynx.Abstractions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.CreateOrder&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;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommand&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;CustomerId&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;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommand&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command contains the data required to create an order.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ICommand&amp;lt;Guid&amp;gt;&lt;/code&gt; means that the command returns the ID of the newly created order.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the command handler
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;CreateOrderCommandHandler.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Signalynx.Abstractions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.CreateOrder&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommandHandler&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommandHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CreateOrderCommand&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;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt; &lt;span class="n"&gt;command&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;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&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;orderId&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="c1"&gt;// Replace this with your database or domain logic.&lt;/span&gt;
        &lt;span class="c1"&gt;// For example:&lt;/span&gt;
        &lt;span class="c1"&gt;//&lt;/span&gt;
        &lt;span class="c1"&gt;// var order = new Order(&lt;/span&gt;
        &lt;span class="c1"&gt;//     orderId,&lt;/span&gt;
        &lt;span class="c1"&gt;//     command.CustomerId,&lt;/span&gt;
        &lt;span class="c1"&gt;//     command.Amount);&lt;/span&gt;
        &lt;span class="c1"&gt;//&lt;/span&gt;
        &lt;span class="c1"&gt;// dbContext.Orders.Add(order);&lt;/span&gt;
        &lt;span class="c1"&gt;// await dbContext.SaveChangesAsync(cancellationToken);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler owns the application logic for creating an order.&lt;/p&gt;

&lt;p&gt;In a real project, it could receive an EF Core &lt;code&gt;DbContext&lt;/code&gt;, repository, domain service, or another dependency through constructor injection.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;OrdersDbContext&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommandHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CreateOrderCommand&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;&amp;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;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt; &lt;span class="n"&gt;command&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;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&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;order&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;Order&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Id&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="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Created"&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&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;dbContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;cancellationToken&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;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&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;
  
  
  Retrieve an order with a query
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;GET&lt;/code&gt; request reads data without intentionally changing application state.&lt;/p&gt;

&lt;p&gt;This makes it a good match for a query.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the response
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;Features/Orders/GetOrder/OrderResponse.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.GetOrder&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;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;OrderResponse&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;Id&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;CustomerId&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;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Define the query
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;GetOrderQuery.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Signalynx.Abstractions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.GetOrder&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;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;GetOrderQuery&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="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query contains the order ID.&lt;/p&gt;

&lt;p&gt;The nullable &lt;code&gt;OrderResponse?&lt;/code&gt; result indicates that the requested order might not exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the query handler
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;GetOrderQueryHandler.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Signalynx.Abstractions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.GetOrder&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetOrderQueryHandler&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IQueryHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetOrderQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderResponse&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;public&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;GetOrderQuery&lt;/span&gt; &lt;span class="n"&gt;query&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;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Replace this with an EF Core, Dapper,&lt;/span&gt;
        &lt;span class="c1"&gt;// or repository database query.&lt;/span&gt;

        &lt;span class="n"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;order&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="n"&gt;query&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;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="m"&gt;2499.00m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Created"&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;ValueTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&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;p&gt;In a production application, the handler could query a database using EF Core:&lt;br&gt;
&lt;/p&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetOrderQueryHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;OrdersDbContext&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IQueryHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetOrderQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderResponse&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;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;GetOrderQuery&lt;/span&gt; &lt;span class="n"&gt;query&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;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;query&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefaultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&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;p&gt;The database query remains inside the query handler rather than the API controller.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the request model
&lt;/h2&gt;

&lt;p&gt;Create a request model for the &lt;code&gt;POST&lt;/code&gt; endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.CreateOrder&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;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderRequest&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;CustomerId&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;p&gt;The request model represents the HTTP contract.&lt;/p&gt;

&lt;p&gt;The command represents the application operation.&lt;/p&gt;

&lt;p&gt;Keeping these contracts separate allows the API contract to evolve independently from the application layer when necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dispatch commands and queries from the controller
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;Controllers/OrdersController.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Signalynx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.CreateOrder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Features.Orders.GetOrder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SignalynxOrdersApi.Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/orders"&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrdersController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ISignalynx&lt;/span&gt; &lt;span class="n"&gt;signalynx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{id:guid}"&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetOrder&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;id&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;cancellationToken&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;query&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;GetOrderQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&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;order&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;signalynx&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetOrderQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;(&lt;/span&gt;
                &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;cancellationToken&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;order&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&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;HttpPost&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CreateOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CreateOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&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;cancellationToken&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;command&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;CreateOrderCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&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;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;signalynx&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DispatchAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CreateOrderCommand&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;&amp;gt;(&lt;/span&gt;
                &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;CreatedAtAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetOrder&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="n"&gt;id&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;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller remains small.&lt;/p&gt;

&lt;p&gt;It does not contain order-creation logic or data-access logic. It only:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads HTTP input.&lt;/li&gt;
&lt;li&gt;Creates a command or query.&lt;/li&gt;
&lt;li&gt;Sends it through Signalynx.&lt;/li&gt;
&lt;li&gt;Converts the result into an HTTP response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Its only application-level dependency is &lt;code&gt;ISignalynx&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the API
&lt;/h2&gt;

&lt;p&gt;Run the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create an order:&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/orders
Content-Type: application/json

{
  "customerId": "4d8c5790-724f-4b42-bdda-bfa7f4932773",
  "amount": 2499.00
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful response may look like this:&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0c9af8bd-478f-4480-a989-3cdb0aafee51"&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;Retrieve the order:&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;GET /api/orders/0c9af8bd-478f-4480-a989-3cdb0aafee51
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example response:&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0c9af8bd-478f-4480-a989-3cdb0aafee51"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4d8c5790-724f-4b42-bdda-bfa7f4932773"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2499.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Created"&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;The sample handlers currently return demonstration data. Connect them to your database to persist and retrieve real orders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add validation and logging with pipeline behaviors
&lt;/h2&gt;

&lt;p&gt;One major advantage of a mediator is the ability to apply common logic around multiple application operations.&lt;/p&gt;

&lt;p&gt;A mediator pipeline can be visualized like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controller
   ↓
Logging Behavior
   ↓
Validation Behavior
   ↓
Command or Query Handler
   ↓
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pipeline behaviors can be used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request validation&lt;/li&gt;
&lt;li&gt;Structured logging&lt;/li&gt;
&lt;li&gt;Authorization&lt;/li&gt;
&lt;li&gt;Performance measurement&lt;/li&gt;
&lt;li&gt;Database transactions&lt;/li&gt;
&lt;li&gt;Exception handling&lt;/li&gt;
&lt;li&gt;Auditing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Behaviors can be registered centrally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&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;AddSignalynx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&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;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterServicesFromAssembly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenBehavior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ValidationBehavior&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;,&amp;gt;));&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenBehavior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoggingBehavior&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;,&amp;gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps individual handlers focused on application and business logic.&lt;/p&gt;

&lt;p&gt;Only add behaviors that provide clear value to your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commands, queries, requests, and events
&lt;/h2&gt;

&lt;p&gt;Signalynx provides explicit message contracts for different application interactions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Recommended type&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Change application state&lt;/td&gt;
&lt;td&gt;Command&lt;/td&gt;
&lt;td&gt;Create or cancel an order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read application data&lt;/td&gt;
&lt;td&gt;Query&lt;/td&gt;
&lt;td&gt;Retrieve an order by ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;General one-to-one operation&lt;/td&gt;
&lt;td&gt;Request&lt;/td&gt;
&lt;td&gt;Calculate a value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notify multiple local handlers&lt;/td&gt;
&lt;td&gt;Notification or domain event&lt;/td&gt;
&lt;td&gt;Order created&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Using explicit message types makes the purpose of each operation visible in the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Command
&lt;/h3&gt;

&lt;p&gt;Use a command when an operation changes application state:&lt;br&gt;
&lt;/p&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;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CancelOrderCommand&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="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommand&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;

&lt;p&gt;Use a query when an operation retrieves data:&lt;br&gt;
&lt;/p&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;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;SearchOrdersQuery&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;SearchTerm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Request
&lt;/h3&gt;

&lt;p&gt;Use a request for a general one-to-one operation that does not clearly fit command or query semantics:&lt;br&gt;
&lt;/p&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;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CalculateShippingRequest&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;PostalCode&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;Weight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRequest&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Notification or domain event
&lt;/h3&gt;

&lt;p&gt;Use a notification or domain event when multiple local handlers should react independently:&lt;br&gt;
&lt;/p&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;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;OrderCreatedEvent&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="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IDomainEvent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Possible handlers could include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrderCreatedEvent
   ├── Send confirmation email
   ├── Update analytics
   ├── Write audit record
   └── Reserve inventory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mediator pattern versus direct service calls
&lt;/h2&gt;

&lt;p&gt;A mediator is useful when an application contains multiple use cases and repeated cross-cutting concerns.&lt;/p&gt;

&lt;p&gt;However, direct service calls may still be sufficient for a very small application.&lt;/p&gt;

&lt;p&gt;Adding a command and handler for every trivial operation can create unnecessary files when there is no meaningful architectural separation to gain.&lt;/p&gt;

&lt;p&gt;A mediator becomes more valuable when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Controllers are growing too large.&lt;/li&gt;
&lt;li&gt;Business logic is spread across API endpoints.&lt;/li&gt;
&lt;li&gt;Multiple endpoints repeat validation or logging.&lt;/li&gt;
&lt;li&gt;Application use cases need independent tests.&lt;/li&gt;
&lt;li&gt;The project follows Clean Architecture.&lt;/li&gt;
&lt;li&gt;The project follows vertical-slice architecture.&lt;/li&gt;
&lt;li&gt;Commands and queries need clear ownership.&lt;/li&gt;
&lt;li&gt;Cross-cutting behaviors must be applied consistently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to add more layers.&lt;/p&gt;

&lt;p&gt;The goal is to give each use case a clear boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing a handler
&lt;/h2&gt;

&lt;p&gt;Because the business operation lives in a handler, it can be tested without creating an ASP.NET Core test server.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommandHandlerTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&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="nf"&gt;HandleAsync_ReturnsOrderId&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;handler&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;CreateOrderCommandHandler&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;command&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;CreateOrderCommand&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="m"&gt;2499.00m&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;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;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NotEqual&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="n"&gt;Empty&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the handler uses a database or another dependency, provide a test implementation through its constructor.&lt;/p&gt;

&lt;p&gt;This keeps the test focused on one application operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built Signalynx
&lt;/h2&gt;

&lt;p&gt;I built Signalynx to provide a modern, strongly typed mediator experience for .NET applications while keeping in-process dispatch independent from optional infrastructure.&lt;/p&gt;

&lt;p&gt;For a simple ASP.NET Core API, you can use only the mediator functionality.&lt;/p&gt;

&lt;p&gt;As the application grows, you can add packages for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Source generation&lt;/li&gt;
&lt;li&gt;Domain events&lt;/li&gt;
&lt;li&gt;Durable messaging&lt;/li&gt;
&lt;li&gt;Inbox and outbox patterns&lt;/li&gt;
&lt;li&gt;Message-broker integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows teams to begin with a clean in-process architecture without forcing every application to immediately adopt distributed messaging infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The Mediator pattern can make ASP.NET Core APIs easier to maintain by separating HTTP endpoints from application use cases.&lt;/p&gt;

&lt;p&gt;With Signalynx:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;POST&lt;/code&gt; endpoint can dispatch a strongly typed command.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;GET&lt;/code&gt; endpoint can execute a strongly typed query.&lt;/li&gt;
&lt;li&gt;Each use case can have its own handler.&lt;/li&gt;
&lt;li&gt;Controllers remain focused on HTTP concerns.&lt;/li&gt;
&lt;li&gt;Validation and logging can be added through reusable pipelines.&lt;/li&gt;
&lt;li&gt;Handlers can be tested independently.&lt;/li&gt;
&lt;li&gt;Application code becomes easier to extend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install Signalynx using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Signalynx.DependencyInjection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explore the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://signalynx.inilesh.dev/" rel="noopener noreferrer"&gt;Signalynx documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/it-nilesh/Signalynx" rel="noopener noreferrer"&gt;Signalynx on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nuget.org/packages/Signalynx.Core" rel="noopener noreferrer"&gt;Signalynx.Core on NuGet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Signalynx is open source. If it helps your project, consider starring the GitHub repository and sharing your feedback.&lt;/p&gt;

&lt;p&gt;How do you currently keep your ASP.NET Core controllers thin?&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>aspdotnet</category>
      <category>aspdotnetcore</category>
      <category>signalynx</category>
    </item>
    <item>
      <title>Signalynx for .NET: Mediator, CQRS, Outbox, Inbox, and Durable Messaging Explained</title>
      <dc:creator>Nilesh Vishwakarma</dc:creator>
      <pubDate>Fri, 31 Jul 2026 17:29:28 +0000</pubDate>
      <link>https://dev.to/nilesh_vishwakarma_01e134/signalynx-for-net-mediator-cqrs-outbox-inbox-and-durable-messaging-explained-1bgf</link>
      <guid>https://dev.to/nilesh_vishwakarma_01e134/signalynx-for-net-mediator-cqrs-outbox-inbox-and-durable-messaging-explained-1bgf</guid>
      <description>&lt;p&gt;Modern .NET applications often begin with a straightforward architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controller
  → Service
  → Database
  → Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well while the application is small.&lt;/p&gt;

&lt;p&gt;As the project grows, however, business logic starts spreading across controllers, application services, background workers, event handlers, databases, and message brokers.&lt;/p&gt;

&lt;p&gt;Eventually, teams begin asking questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do we keep business logic out of controllers?&lt;/li&gt;
&lt;li&gt;How should commands and queries be organized?&lt;/li&gt;
&lt;li&gt;How can several modules respond to the same event?&lt;/li&gt;
&lt;li&gt;What happens when asynchronous processing fails?&lt;/li&gt;
&lt;li&gt;How do we prevent duplicate message processing?&lt;/li&gt;
&lt;li&gt;How can we introduce durable messaging without redesigning the entire application?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I created &lt;strong&gt;Signalynx&lt;/strong&gt; to provide a structured answer to these problems.&lt;/p&gt;

&lt;p&gt;Signalynx is an open-source, strongly typed mediator, CQRS dispatcher, and durable messaging toolkit for .NET 8, .NET 9, and .NET 10.&lt;/p&gt;

&lt;p&gt;It supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commands, queries, and request-response messages&lt;/li&gt;
&lt;li&gt;Notifications and domain events&lt;/li&gt;
&lt;li&gt;Pipeline behaviors&lt;/li&gt;
&lt;li&gt;Bulk processing&lt;/li&gt;
&lt;li&gt;Durable message processing&lt;/li&gt;
&lt;li&gt;Retries and scheduling&lt;/li&gt;
&lt;li&gt;Inbox and outbox patterns&lt;/li&gt;
&lt;li&gt;Dead-letter handling and replay&lt;/li&gt;
&lt;li&gt;SQL Server and PostgreSQL persistence&lt;/li&gt;
&lt;li&gt;RabbitMQ, Kafka, Azure Service Bus, and Amazon SQS&lt;/li&gt;
&lt;li&gt;NativeAOT-friendly source generation&lt;/li&gt;
&lt;li&gt;OpenTelemetry-compatible diagnostics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Project links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://signalynx.inilesh.dev/" rel="noopener noreferrer"&gt;Signalynx website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/it-nilesh/Signalynx" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The problem with direct service dependencies
&lt;/h2&gt;

&lt;p&gt;Consider an order endpoint that creates an order, sends a confirmation email, and reserves inventory:&lt;br&gt;
&lt;/p&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;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CreateOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;CreateOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&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;order&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;_orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&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;_emailService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendConfirmationAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&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;_inventoryService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReserveItemsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&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;p&gt;This code looks reasonable at first.&lt;/p&gt;

&lt;p&gt;But the controller is now responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating the order&lt;/li&gt;
&lt;li&gt;Triggering email delivery&lt;/li&gt;
&lt;li&gt;Reserving inventory&lt;/li&gt;
&lt;li&gt;Deciding execution order&lt;/li&gt;
&lt;li&gt;Coordinating dependencies&lt;/li&gt;
&lt;li&gt;Handling failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As more operations are added, the endpoint becomes tightly coupled to multiple services.&lt;/p&gt;

&lt;p&gt;With a mediator-based design, the endpoint describes &lt;strong&gt;what should happen&lt;/strong&gt; without coordinating every implementation detail:&lt;br&gt;
&lt;/p&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;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;signalynx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DispatchAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="n"&gt;CreateOrderCommand&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;&amp;gt;(&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command handler contains the business operation.&lt;/p&gt;

&lt;p&gt;Validation, logging, authorization, transactions, metrics, and other cross-cutting concerns can be handled through pipeline behaviors.&lt;/p&gt;

&lt;p&gt;The endpoint becomes an entry point rather than a container for business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strongly typed commands and queries
&lt;/h2&gt;

&lt;p&gt;Signalynx uses explicit contracts for different kinds of application operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commands
&lt;/h3&gt;

&lt;p&gt;Commands normally represent operations that change application state:&lt;br&gt;
&lt;/p&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;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommand&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;CustomerId&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;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommand&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler declares both the command it accepts and the result it returns:&lt;br&gt;
&lt;/p&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderHandler&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommandHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CreateOrderCommand&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;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt; &lt;span class="n"&gt;command&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;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&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;orderId&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="c1"&gt;// Save the order.&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&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="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;
  
  
  Queries
&lt;/h3&gt;

&lt;p&gt;Queries represent read operations:&lt;br&gt;
&lt;/p&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;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;GetOrderQuery&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="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Separating commands and queries makes the purpose of each operation easier to understand.&lt;/p&gt;

&lt;p&gt;Instead of tracing generic methods such as &lt;code&gt;ExecuteAsync&lt;/code&gt;, &lt;code&gt;ProcessAsync&lt;/code&gt;, or &lt;code&gt;HandleAsync&lt;/code&gt; across several services, developers can identify the application flow from the message contract itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pipeline behaviors
&lt;/h2&gt;

&lt;p&gt;Production applications usually need cross-cutting functionality such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Authorization&lt;/li&gt;
&lt;li&gt;Metrics&lt;/li&gt;
&lt;li&gt;Transactions&lt;/li&gt;
&lt;li&gt;Auditing&lt;/li&gt;
&lt;li&gt;Exception handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without a pipeline, this logic is often repeated inside controllers and handlers.&lt;/p&gt;

&lt;p&gt;Signalynx allows these concerns to be registered as pipeline behaviors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&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;AddSignalynx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&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;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterServicesFromAssembly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenBehavior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ValidationBehavior&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;,&amp;gt;));&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenBehavior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoggingBehavior&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;,&amp;gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler can then remain focused on the business operation.&lt;/p&gt;

&lt;p&gt;This also makes architectural changes easier. For example, changing the logging or validation strategy does not require modifying every command handler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain events and multiple handlers
&lt;/h2&gt;

&lt;p&gt;Some business operations need to notify several parts of an application.&lt;/p&gt;

&lt;p&gt;After an order is confirmed, the application may need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce inventory&lt;/li&gt;
&lt;li&gt;Send a confirmation notification&lt;/li&gt;
&lt;li&gt;Add an audit entry&lt;/li&gt;
&lt;li&gt;Update reporting data&lt;/li&gt;
&lt;li&gt;Trigger analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The order handler should not need direct dependencies on all of these modules.&lt;/p&gt;

&lt;p&gt;Instead, it can publish an event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;signalynx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PublishEventAsync&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;orderId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple handlers can respond to the event independently.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrderConfirmed
  ├── Inventory handler
  ├── Notification handler
  ├── Analytics handler
  └── Audit handler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially useful in modular monoliths, where business modules share one deployment but should remain logically independent.&lt;/p&gt;

&lt;p&gt;Signalynx supports sequential and parallel publishing.&lt;/p&gt;

&lt;p&gt;Sequential execution is generally easier to reason about because ordering and failure behavior are predictable. Parallel execution can be useful when handlers are independent and thread-safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local events are not durable
&lt;/h2&gt;

&lt;p&gt;In-process events are useful, but they have an important limitation.&lt;/p&gt;

&lt;p&gt;Suppose an application publishes an event and then shuts down before every handler completes. The unfinished work may be lost.&lt;/p&gt;

&lt;p&gt;That may be acceptable for optional local operations. It is usually not acceptable for critical processes such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payment processing&lt;/li&gt;
&lt;li&gt;Order fulfillment&lt;/li&gt;
&lt;li&gt;Security-event processing&lt;/li&gt;
&lt;li&gt;Customer notifications&lt;/li&gt;
&lt;li&gt;Financial reconciliation&lt;/li&gt;
&lt;li&gt;Integration with another service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When work must survive process restarts or temporary infrastructure failures, it needs durable messaging.&lt;/p&gt;

&lt;p&gt;Signalynx Messaging adds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Durable message envelopes&lt;/li&gt;
&lt;li&gt;Hosted processing workers&lt;/li&gt;
&lt;li&gt;Retries&lt;/li&gt;
&lt;li&gt;Delayed scheduling&lt;/li&gt;
&lt;li&gt;Inbox and outbox contracts&lt;/li&gt;
&lt;li&gt;Dead-letter handling&lt;/li&gt;
&lt;li&gt;Message replay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A message can be enqueued for asynchronous processing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;messageBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnqueueAsync&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;OrderSubmitted&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;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can also be scheduled for later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;messageBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ScheduleAsync&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;OrderSubmitted&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;DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If processing repeatedly fails, the message can be moved to a dead-letter store for investigation and later replay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the transactional outbox matters
&lt;/h2&gt;

&lt;p&gt;Distributed applications often need to perform two operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Save business data in a database.&lt;/li&gt;
&lt;li&gt;Publish an integration message.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Save Order
Publish OrderCreated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens when the database transaction succeeds but the message broker is unavailable?&lt;/p&gt;

&lt;p&gt;The order exists, but the &lt;code&gt;OrderCreated&lt;/code&gt; message is never delivered.&lt;/p&gt;

&lt;p&gt;The system is now inconsistent.&lt;/p&gt;

&lt;p&gt;The transactional outbox pattern addresses this problem by storing the business change and the outgoing message in the same database transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database transaction
  ├── Insert Order
  └── Insert Outbox Message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A background worker later reads the outbox record and publishes it to the broker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Outbox
  → Background publisher
  → Message broker
  → Consumer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the broker is temporarily unavailable, the message remains in the outbox and can be retried.&lt;/p&gt;

&lt;p&gt;Signalynx provides contracts and storage adapters for implementing this pattern.&lt;/p&gt;

&lt;p&gt;However, the application must still ensure that the business write and outbox insert participate in the same database transaction.&lt;/p&gt;

&lt;p&gt;A library can provide the infrastructure, but it cannot automatically make every business operation transactional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the inbox matters
&lt;/h2&gt;

&lt;p&gt;Most message brokers use at-least-once delivery.&lt;/p&gt;

&lt;p&gt;This means the same message may be delivered more than once.&lt;/p&gt;

&lt;p&gt;For example, a consumer might successfully process a message but fail before acknowledging it. The broker may then deliver it again.&lt;/p&gt;

&lt;p&gt;Without duplicate protection, the application could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Charge a customer twice&lt;/li&gt;
&lt;li&gt;Send the same email several times&lt;/li&gt;
&lt;li&gt;Reserve inventory twice&lt;/li&gt;
&lt;li&gt;Create duplicate records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The inbox pattern records processed message identifiers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Incoming Message
  → Check Inbox
      → Already processed: skip
      → New message: process and record
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Signalynx provides inbox contracts and durable store adapters to support this workflow.&lt;/p&gt;

&lt;p&gt;Even with an inbox, business handlers should still be designed with idempotency in mind, especially when they call external systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start locally and add infrastructure later
&lt;/h2&gt;

&lt;p&gt;One of the main design goals of Signalynx is modularity.&lt;/p&gt;

&lt;p&gt;Applications do not need to install durable messaging infrastructure just to use the mediator.&lt;/p&gt;

&lt;p&gt;You can begin with the core runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Signalynx.Core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Microsoft dependency injection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Signalynx.DependencyInjection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additional capabilities can be installed only when required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Signalynx.Validation
dotnet add package Signalynx.Logging
dotnet add package Signalynx.SourceGeneration
dotnet add package Signalynx.Messaging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows an application to evolve gradually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controller
  → Command
  → Handler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later, as requirements grow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API
  → Command or Query
  → Pipeline
  → Handler
  → Domain Event
  → Outbox
  → Message Broker
  → Consumer
  → Inbox
  → Business Operation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application does not need to adopt databases, durable stores, or message brokers before those capabilities are necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modular monoliths and microservices
&lt;/h2&gt;

&lt;p&gt;Signalynx can be used in both modular monoliths and microservice architectures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modular monolith
&lt;/h3&gt;

&lt;p&gt;A modular monolith might contain modules such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
├── Orders
├── Payments
├── Inventory
├── Customers
└── Notifications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commands and queries can organize direct operations inside each module.&lt;/p&gt;

&lt;p&gt;Notifications and domain events can communicate changes between modules without creating direct dependencies everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Microservices
&lt;/h3&gt;

&lt;p&gt;Inside a microservice, the mediator organizes local application logic.&lt;/p&gt;

&lt;p&gt;When communication crosses process boundaries, the durable messaging layer can handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persistence&lt;/li&gt;
&lt;li&gt;Retries&lt;/li&gt;
&lt;li&gt;Scheduling&lt;/li&gt;
&lt;li&gt;Broker delivery&lt;/li&gt;
&lt;li&gt;Duplicate detection&lt;/li&gt;
&lt;li&gt;Dead letters&lt;/li&gt;
&lt;li&gt;Replay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Signalynx includes transport integrations for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RabbitMQ&lt;/li&gt;
&lt;li&gt;Apache Kafka&lt;/li&gt;
&lt;li&gt;Azure Service Bus&lt;/li&gt;
&lt;li&gt;Amazon SQS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Durable store integrations are available for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Server&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a related programming model for both local dispatch and asynchronous communication.&lt;/p&gt;

&lt;h2&gt;
  
  
  NativeAOT and source generation
&lt;/h2&gt;

&lt;p&gt;Runtime assembly scanning is convenient, but it can create problems for trimming and NativeAOT applications.&lt;/p&gt;

&lt;p&gt;Signalynx supports source-generated handler registration.&lt;/p&gt;

&lt;p&gt;The source generator can create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dependency-injection registrations&lt;/li&gt;
&lt;li&gt;Handler metadata&lt;/li&gt;
&lt;li&gt;A static handler map&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This avoids runtime assembly scanning on the NativeAOT path.&lt;/p&gt;

&lt;p&gt;Traditional applications can continue using runtime discovery, while trimmed or NativeAOT applications can use generated registration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance considerations
&lt;/h2&gt;

&lt;p&gt;Signalynx is designed to keep mediator overhead low.&lt;/p&gt;

&lt;p&gt;Its performance-oriented design includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Typed handler calls&lt;/li&gt;
&lt;li&gt;Startup-time handler discovery&lt;/li&gt;
&lt;li&gt;Immutable handler metadata&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ValueTask&lt;/code&gt;-based asynchronous contracts&lt;/li&gt;
&lt;li&gt;Cached dispatch delegates&lt;/li&gt;
&lt;li&gt;Fast paths when no pipeline behaviors are registered&lt;/li&gt;
&lt;li&gt;No LINQ in core dispatch loops&lt;/li&gt;
&lt;li&gt;Optional source-generated registration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repository includes BenchmarkDotNet benchmarks covering mediator and messaging operations.&lt;/p&gt;

&lt;p&gt;However, isolated mediator benchmarks should not be treated as complete application benchmarks.&lt;/p&gt;

&lt;p&gt;Real-world performance also depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database access&lt;/li&gt;
&lt;li&gt;Network latency&lt;/li&gt;
&lt;li&gt;Message size&lt;/li&gt;
&lt;li&gt;Serialization&lt;/li&gt;
&lt;li&gt;Broker configuration&lt;/li&gt;
&lt;li&gt;Durability settings&lt;/li&gt;
&lt;li&gt;Batching&lt;/li&gt;
&lt;li&gt;Database indexes&lt;/li&gt;
&lt;li&gt;Business-handler logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Teams should benchmark their actual workload rather than choosing an architecture solely from isolated dispatch numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability
&lt;/h2&gt;

&lt;p&gt;A messaging system should not become a black box.&lt;/p&gt;

&lt;p&gt;Signalynx exposes activities and metrics for mediator dispatch and durable messaging, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dispatch duration and failures&lt;/li&gt;
&lt;li&gt;Publish duration and failures&lt;/li&gt;
&lt;li&gt;Messages enqueued&lt;/li&gt;
&lt;li&gt;Messages sent&lt;/li&gt;
&lt;li&gt;Messages handled&lt;/li&gt;
&lt;li&gt;Retry counts&lt;/li&gt;
&lt;li&gt;Dead-letter counts&lt;/li&gt;
&lt;li&gt;Handler duration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These signals can be connected to OpenTelemetry-compatible monitoring systems.&lt;/p&gt;

&lt;p&gt;In production, teams should monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Outbox depth and message age&lt;/li&gt;
&lt;li&gt;Retry rate&lt;/li&gt;
&lt;li&gt;Dead-letter growth&lt;/li&gt;
&lt;li&gt;Handler latency&lt;/li&gt;
&lt;li&gt;Consumer lag&lt;/li&gt;
&lt;li&gt;Broker availability&lt;/li&gt;
&lt;li&gt;Processing failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This helps detect failed integrations and growing queues before they affect customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you use Signalynx?
&lt;/h2&gt;

&lt;p&gt;Signalynx becomes useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Controllers contain too much business logic.&lt;/li&gt;
&lt;li&gt;Services have many direct dependencies.&lt;/li&gt;
&lt;li&gt;The application uses CQRS.&lt;/li&gt;
&lt;li&gt;Multiple handlers need to respond to an event.&lt;/li&gt;
&lt;li&gt;Validation and logging are repeated throughout the codebase.&lt;/li&gt;
&lt;li&gt;Background work must survive application restarts.&lt;/li&gt;
&lt;li&gt;Failed operations need controlled retries.&lt;/li&gt;
&lt;li&gt;Duplicate messages must be detected.&lt;/li&gt;
&lt;li&gt;The system needs inbox or outbox patterns.&lt;/li&gt;
&lt;li&gt;The architecture may later introduce a message broker.&lt;/li&gt;
&lt;li&gt;NativeAOT support is important.&lt;/li&gt;
&lt;li&gt;Dispatch overhead and allocations need to remain low.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important question is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should every .NET application use a mediator?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Has the application become complex enough that separating requests from their handlers will improve maintainability?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  When might it be unnecessary?
&lt;/h2&gt;

&lt;p&gt;Signalynx is not required for every project.&lt;/p&gt;

&lt;p&gt;A direct architecture may be better when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The application is very small.&lt;/li&gt;
&lt;li&gt;There are only a few simple operations.&lt;/li&gt;
&lt;li&gt;Direct service calls remain clear.&lt;/li&gt;
&lt;li&gt;The team does not use CQRS or event-driven patterns.&lt;/li&gt;
&lt;li&gt;Another mediator or messaging framework already works well.&lt;/li&gt;
&lt;li&gt;Adding another abstraction would create more complexity than value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A mediator should improve the architecture. It should not become architecture for its own sake.&lt;/p&gt;

&lt;p&gt;Installing a messaging package also does not automatically make an application production-ready.&lt;/p&gt;

&lt;p&gt;Reliability still depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transaction design&lt;/li&gt;
&lt;li&gt;Handler idempotency&lt;/li&gt;
&lt;li&gt;Retry classification&lt;/li&gt;
&lt;li&gt;Persistent storage&lt;/li&gt;
&lt;li&gt;Broker configuration&lt;/li&gt;
&lt;li&gt;Security controls&lt;/li&gt;
&lt;li&gt;Monitoring and alerts&lt;/li&gt;
&lt;li&gt;Backup and recovery&lt;/li&gt;
&lt;li&gt;Load testing&lt;/li&gt;
&lt;li&gt;Failure testing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Install the dependency-injection integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Signalynx.DependencyInjection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define a command:&lt;br&gt;
&lt;/p&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;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommand&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;CustomerId&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;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommand&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create its handler:&lt;br&gt;
&lt;/p&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderHandler&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommandHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CreateOrderCommand&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;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt; &lt;span class="n"&gt;command&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;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&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;ValueTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register Signalynx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&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;AddSignalynx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&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;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterServicesFromAssembly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&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;p&gt;Dispatch the command:&lt;br&gt;
&lt;/p&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;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;signalynx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DispatchAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="n"&gt;CreateOrderCommand&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;&amp;gt;(&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, you can introduce pipeline behaviors, domain events, durable messaging, database stores, or broker transports only when your application requires them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Signalynx is designed for .NET teams that need more than a basic mediator but do not want durable messaging infrastructure to be mandatory from the beginning.&lt;/p&gt;

&lt;p&gt;Its goal is to provide a gradual path from simple in-process dispatch to reliable distributed processing.&lt;/p&gt;

&lt;p&gt;You can begin with commands, queries, and handlers.&lt;/p&gt;

&lt;p&gt;As the architecture grows, you can add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pipeline behaviors&lt;/li&gt;
&lt;li&gt;Domain events&lt;/li&gt;
&lt;li&gt;Persistent queues&lt;/li&gt;
&lt;li&gt;Transactional outbox processing&lt;/li&gt;
&lt;li&gt;Inbox deduplication&lt;/li&gt;
&lt;li&gt;Retries and scheduling&lt;/li&gt;
&lt;li&gt;Dead-letter handling&lt;/li&gt;
&lt;li&gt;Message brokers&lt;/li&gt;
&lt;li&gt;Production observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Signalynx is open source under the MIT License.&lt;/p&gt;

&lt;p&gt;Feedback, testing, contributions, and real-world usage are welcome.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://signalynx.inilesh.dev/" rel="noopener noreferrer"&gt;Explore Signalynx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/it-nilesh/Signalynx" rel="noopener noreferrer"&gt;View the source on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What reliability or architecture problem has been the hardest part of introducing messaging into your .NET applications?&lt;/p&gt;

</description>
      <category>dotnetcore</category>
      <category>csharp</category>
      <category>aspdotnet</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
