<?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>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>
