<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ software architecture - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ software architecture - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 05 May 2026 14:47:07 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/software-architecture/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up OpenClaw and Design an A2A Plugin Bridge ]]>
                </title>
                <description>
                    <![CDATA[ OpenClaw is getting attention because it turns a popular AI idea into something you can actually run yourself. Instead of opening one more browser tab, you run a Gateway on your own machine or server  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/openclaw-a2a-plugin-architecture-guide/</link>
                <guid isPermaLink="false">69d542ca5da14bc70e7c1559</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Open Source ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nataraj Sundar ]]>
                </dc:creator>
                <pubDate>Tue, 07 Apr 2026 17:45:46 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/4be03b02-d128-49e9-afcb-fea0f771e746.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>OpenClaw is getting attention because it turns a popular AI idea into something you can actually run yourself. Instead of opening one more browser tab, you run a Gateway on your own machine or server and connect it to communication tools you already use.</p>
<p>That matters because OpenClaw is self-hosted, multi-channel, open source, and built around agent workflows such as sessions, tools, plugins, and multi-agent routing. It feels less like a toy chatbot and more like an operator-controlled agent runtime.</p>
<p>In this guide, you'll do three things. First, you'll learn what OpenClaw is and why developers are paying attention to it. Second, you'll get it running the beginner-friendly way through the dashboard. Third, you'll walk through an original design contribution: a proposed OpenClaw-to-A2A plugin architecture and a <a href="https://github.com/natarajsundar/openclaw-a2a-secure-agent-runtime"><code>proof-of-concept</code></a> relay that shows how OpenClaw’s session model could map to the A2A protocol.</p>
<p>That last part is important, so I want to frame it carefully. The A2A integration in this article is <strong>not</strong> presented as a built-in OpenClaw feature. It's a documented architecture proposal built on top of the extension points OpenClaw already exposes.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>This guide is beginner-friendly for OpenClaw itself, but it assumes a few basics so you can follow the architecture and proof-of-concept sections comfortably.</p>
<p>Before you continue, you should be familiar with:</p>
<ul>
<li><p>Basic JavaScript or Node.js (reading and running scripts)</p>
</li>
<li><p>How HTTP APIs work (requests, responses, JSON payloads)</p>
</li>
<li><p>Using a terminal to run commands</p>
</li>
<li><p>High-level concepts like services, APIs, or microservices</p>
</li>
</ul>
<p>You don't need prior experience with OpenClaw or A2A. The setup steps walk through everything you need to get started.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a href="#heading-what-openclaw-is">What OpenClaw Is</a></p>
</li>
<li><p><a href="#heading-why-openclaw-is-getting-so-much-attention">Why OpenClaw Is Getting So Much Attention</a></p>
</li>
<li><p><a href="#heading-what-the-a2a-protocol-is">What the A2A Protocol Is</a></p>
</li>
<li><p><a href="#heading-how-openclaw-and-a2a-relate">How OpenClaw and A2A Relate</a></p>
</li>
<li><p><a href="#heading-what-you-need-before-you-start">What You Need Before You Start</a></p>
</li>
<li><p><a href="#heading-step-1-install-openclaw">Install OpenClaw</a></p>
</li>
<li><p><a href="#heading-step-2-run-the-onboarding-wizard">Run the Onboarding Wizard</a></p>
</li>
<li><p><a href="#heading-step-3-check-the-gateway-and-open-the-dashboard">Check the Gateway and Open the Dashboard</a></p>
</li>
<li><p><a href="#heading-step-4-use-openclaw-as-a-private-coding-assistant">Use OpenClaw as a Private Coding Assistant</a></p>
</li>
<li><p><a href="#heading-step-5-understand-multi-agent-routing">Understand Multi Agent Routing</a></p>
</li>
<li><p><a href="#heading-where-a2a-could-fit-later">Where A2A Could Fit Later</a></p>
</li>
<li><p><a href="#heading-a-proposed-openclaw-to-a2a-plugin-architecture">A Proposed OpenClaw to A2A Plugin Architecture</a></p>
</li>
<li><p><a href="#heading-build-the-proof-of-concept-relay">Build the Proof of Concept Relay</a></p>
</li>
<li><p><a href="#heading-how-the-proof-of-concept-maps-to-a-real-openclaw-plugin">How the Proof of Concept Maps to a Real OpenClaw Plugin</a></p>
</li>
<li><p><a href="#heading-security-notes-before-you-go-further">Security Notes Before You Go Further</a></p>
</li>
<li><p><a href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ol>
<h2 id="heading-what-openclaw-is">What OpenClaw Is</h2>
<p>According to the <a href="https://docs.openclaw.ai/">official docs</a>, OpenClaw is a self-hosted gateway that connects chat apps like WhatsApp, Telegram, Discord, iMessage, and a browser dashboard to AI agents.</p>
<p>That wording is useful because it tells you where OpenClaw sits in the stack. It's not just a model wrapper. It's a Gateway that handles sessions, routing, and app connections, while agents, tools, plugins, and providers do the actual work.</p>
<p>Here is the simplest mental model:</p>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/ad5f3295-8fdf-4f9c-8488-f69808850295.png" alt="Diagram showing OpenClaw architecture where multiple chat apps and a browser dashboard connect to a central Gateway, which routes requests to different agents that use model providers and tools." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>If you're new to the project, this is the practical way to think about it:</p>
<ul>
<li><p>your chat apps are the front door</p>
</li>
<li><p>the Gateway is the traffic and control layer</p>
</li>
<li><p>the agent is the reasoning layer</p>
</li>
<li><p>the model provider and tools are what let the agent actually do work</p>
</li>
</ul>
<p>That's one reason OpenClaw feels different from a normal browser-only assistant.</p>
<h2 id="heading-why-developers-are-paying-attention-to-openclaw">Why Developers Are Paying Attention to OpenClaw</h2>
<p>OpenClaw is getting a lot of attention for a few reasons.</p>
<p>The first reason is control. The docs position OpenClaw as self-hosted and multi-channel, which means you can run it on your own machine or server instead of depending on a fully hosted assistant.</p>
<p>The second reason is that OpenClaw already looks like an agent platform. The docs talk about sessions, plugins, tools, skills, multi-agent routing, and ACP-backed external coding harnesses. That's a much richer story than “ask a model a question in a web page.”</p>
<p>The third reason is workflow fit. A lot of people don't want another inbox. They want an assistant that can live in the tools they already check every day.</p>
<p>There's also a broader industry trend behind the hype. Developers are actively looking for ways to connect multiple agents and multiple tools without giving up visibility into what's happening. OpenClaw sits directly in that conversation.</p>
<h2 id="heading-what-the-a2a-protocol-is">What the A2A Protocol Is</h2>
<p>A2A, short for Agent2Agent, is an open protocol for communication between agent systems. The <a href="https://a2a-protocol.org/latest/specification/">A2A specification</a> says its purpose is to help independent agent systems discover each other, negotiate interaction modes, manage collaborative tasks, and exchange information without exposing internal memory, tools, or proprietary logic.</p>
<p>That last point matters. A2A is about interoperability between agent systems, not about exposing all of one agent's internals to another.</p>
<p>A2A introduces a few core concepts that are worth learning early:</p>
<ul>
<li><p><strong>Agent Card</strong>: a JSON description of the remote agent, its URL, skills, capabilities, and auth requirements</p>
</li>
<li><p><strong>Task</strong>: the main unit of remote work</p>
</li>
<li><p><strong>Artifact</strong>: the output of a task</p>
</li>
<li><p><strong>Context ID</strong>: a stable interaction boundary across multiple related turns</p>
</li>
</ul>
<p>A2A tasks follow a fairly clean lifecycle:</p>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/3b5a43e8-dabd-45e3-bff1-0081e2b37e0d.png" alt="State diagram illustrating the A2A task lifecycle including submitted, working, input required, completed, failed, rejected, and canceled states.." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The A2A docs also explain that A2A and MCP are complementary, not competing. A2A is for agent-to-agent collaboration. MCP is for agent-to-tool communication.</p>
<p>That distinction is useful when you compare A2A with OpenClaw, because OpenClaw already has strong local tool and session concepts.</p>
<h2 id="heading-how-openclaw-and-a2a-relate">How OpenClaw and A2A Relate</h2>
<p>OpenClaw and A2A are not the same thing, but they line up in interesting ways.</p>
<p>OpenClaw already documents several features that point in a multi-agent direction:</p>
<ul>
<li><p><a href="https://docs.openclaw.ai/concepts/multi-agent/">multi-agent routing</a> for multiple isolated agents in one running Gateway</p>
</li>
<li><p><a href="https://docs.openclaw.ai/concepts/session-tool/">session tools</a> such as <code>sessions_send</code> and <code>sessions_spawn</code></p>
</li>
<li><p>a <a href="https://docs.openclaw.ai/tools/plugin/">plugin system</a> that can register tools, HTTP routes, Gateway RPC methods, and background services</p>
</li>
<li><p><a href="https://docs.openclaw.ai/tools/acp-agents/">ACP support</a> and the <a href="https://docs.openclaw.ai/cli/acp"><code>openclaw acp</code> bridge</a> for external coding clients</p>
</li>
</ul>
<p>But it's still important to stay precise here.</p>
<p>OpenClaw documents ACP, plugins, and local multi-agent coordination today. The docs I checked do <strong>not</strong> describe native A2A support as a first-class built-in capability.</p>
<p>That means the honest claim is this:</p>
<p><strong>OpenClaw can be meaningfully connected to A2A in theory because the architectural pieces line up, but the A2A bridge still has to be built.</strong></p>
<h3 id="heading-acp-versus-a2a">ACP versus A2A</h3>
<p>ACP and A2A solve different problems.</p>
<p>ACP in OpenClaw today is about bridging an IDE or coding client to a Gateway-backed session.</p>
<p>A2A is about one agent system talking to another agent system across a protocol boundary.</p>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/9790f239-528c-422f-bbc5-3e82c7f1a171.png" alt="Diagram showing A2A interaction where an OpenClaw agent communicates through a plugin to discover a remote agent via an Agent Card and send tasks for execution." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/c4d4279b-3099-4c1b-92b6-3eaf817a6e84.png" alt="Diagram showing ACP flow where an IDE or coding client connects through an OpenClaw ACP bridge to a Gateway-backed session." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>That difference is one reason I prefer the phrase <strong>plugin bridge</strong> here instead of <strong>native A2A support</strong>.</p>
<h2 id="heading-what-you-need-before-you-start">What You Need Before You Start</h2>
<p>The easiest first run does <strong>not</strong> require WhatsApp, Telegram, or Discord.</p>
<p>The OpenClaw onboarding docs say the fastest first chat is the dashboard. That makes this a much more approachable beginner setup.</p>
<p>Before you start, you'll need:</p>
<ol>
<li><p>Node 24 if possible, or Node 22.16+ for compatibility</p>
</li>
<li><p>an API key for the model provider you want to use</p>
</li>
<li><p>If you're on Windows, WSL2 is the recommended path for the full experience. Native Windows works for core CLI and Gateway flows, but the docs call out caveats and position WSL2 as the more stable setup.</p>
</li>
<li><p>about five minutes for the first dashboard-based run</p>
</li>
</ol>
<h2 id="heading-step-1-install-openclaw">Step 1: Install OpenClaw</h2>
<p>The official getting-started page recommends the installer script.</p>
<p>On macOS, Linux, or WSL2, run:</p>
<pre><code class="language-bash">curl -fsSL https://openclaw.ai/install.sh | bash
</code></pre>
<p>On Windows PowerShell, the docs show this:</p>
<pre><code class="language-powershell">iwr -useb https://openclaw.ai/install.ps1 | iex
</code></pre>
<p>If you're on Windows, the platform docs recommend installing WSL2 first:</p>
<pre><code class="language-powershell">wsl --install
</code></pre>
<p>Then open Ubuntu and continue with the Linux commands there.</p>
<h2 id="heading-step-2-run-the-onboarding-wizard">Step 2: Run the Onboarding Wizard</h2>
<p>Once the CLI is installed, run the onboarding wizard.</p>
<pre><code class="language-bash">openclaw onboard --install-daemon
</code></pre>
<p>The onboarding wizard is the recommended path in the docs. It configures auth, gateway settings, optional channels, skills, and workspace defaults in one guided flow.</p>
<p>The most beginner-friendly choice is to keep the first run simple. Don't worry about chat apps yet. Get the local Gateway working first.</p>
<h2 id="heading-step-3-check-the-gateway-and-open-the-dashboard">Step 3: Check the Gateway and Open the Dashboard</h2>
<p>After onboarding, verify that the Gateway is running.</p>
<pre><code class="language-bash">openclaw gateway status
</code></pre>
<p>Then open the dashboard:</p>
<pre><code class="language-bash">openclaw dashboard
</code></pre>
<p>The docs call this the fastest first chat because it avoids channel setup. It's also the safest way to start, because the dashboard is local and the OpenClaw docs clearly say the Control UI is an admin surface and should not be exposed publicly.</p>
<p>The beginner setup flow looks like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/eab78250-65d6-4d97-be3d-bf7167b9099e.png" alt="Sequence diagram showing OpenClaw setup flow from installation and onboarding to starting the Gateway and opening the dashboard for the first chat." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>If you can chat in the dashboard, your day-zero setup is working.</p>
<h2 id="heading-step-4-use-openclaw-as-a-private-coding-assistant">Step 4: Use OpenClaw as a Private Coding Assistant</h2>
<p>The best first use case is not to drop OpenClaw into a public group chat.</p>
<p>Use it as a private coding assistant in the dashboard.</p>
<p>For example, try a prompt like this:</p>
<blockquote>
<p>I am building a small Node.js utility that reads Markdown files and generates a table of contents. Turn this idea into a project plan, a README outline, and the first five implementation tasks.</p>
</blockquote>
<p>That kind of prompt is ideal for a first run because it gives you something concrete back right away.</p>
<p>You can also use it to:</p>
<ol>
<li><p>turn rough notes into a plan,</p>
</li>
<li><p>summarize a bug report into action items,</p>
</li>
<li><p>draft a README,</p>
</li>
<li><p>propose a folder structure, or</p>
</li>
<li><p>write a safe first implementation checklist.</p>
</li>
</ol>
<p>That is already enough to make OpenClaw useful before you touch any advanced protocol work.</p>
<h2 id="heading-step-5-understand-multi-agent-routing">Step 5: Understand Multi Agent Routing</h2>
<p>Once the basic setup is working, it helps to understand OpenClaw’s local multi-agent model.</p>
<p>The docs describe multi-agent routing as a way to run multiple isolated agents in one Gateway, with separate workspaces, state directories, and sessions.</p>
<p>That means you can imagine setups like this:</p>
<ul>
<li><p>a personal assistant</p>
</li>
<li><p>a coding assistant</p>
</li>
<li><p>a research assistant</p>
</li>
<li><p>an alerts assistant</p>
</li>
</ul>
<p>OpenClaw already has a model for that:</p>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/c640a7c4-0421-4513-a2c2-658916504e3b.png" alt="Diagram illustrating OpenClaw multi-agent routing where incoming messages are matched to different agents such as main, coding, and alerts, each with separate sessions." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>You don't need to set this up on day one.</p>
<p>But it matters for the A2A discussion, because once you understand how OpenClaw routes work between local agents, it becomes much easier to think about routing work to <strong>remote</strong> agents through a protocol like A2A.</p>
<h2 id="heading-where-a2a-could-fit-later">Where A2A Could Fit Later</h2>
<p>A2A could fit into OpenClaw in two broad ways.</p>
<h3 id="heading-option-1-openclaw-as-an-a2a-client">Option 1: OpenClaw as an A2A Client</h3>
<p>In this model, OpenClaw stays your personal edge assistant.</p>
<p>It receives a request from the dashboard or a chat app, decides the task needs a specialist, discovers a remote A2A agent through an Agent Card, sends the task, waits for updates or artifacts, and translates the result back into a normal OpenClaw reply.</p>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/99a2e611-54ac-4c0f-8f8f-c1ce3246bb96.png" alt="Diagram showing OpenClaw acting as an A2A client, delegating tasks from a local session to a remote agent via an Agent Card and returning results to the user." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>This is the cleaner story for a personal assistant. OpenClaw stays the front door, and A2A becomes a delegation path behind the scenes.</p>
<h3 id="heading-option-2-openclaw-as-an-a2a-server">Option 2: OpenClaw as an A2A Server</h3>
<p>In this model, OpenClaw exposes some of its own capabilities to other agents.</p>
<p>A plugin could theoretically publish an A2A Agent Card, advertise a narrow skill set, accept A2A tasks, and map those tasks into OpenClaw sessions or sub-agent runs.</p>
<p>That's technically plausible because the plugin system can register HTTP routes, tools, Gateway methods, and background services.</p>
<p>It's also the riskier direction for a personal assistant, which is why I think <strong>client-first</strong> is the right starting point.</p>
<h2 id="heading-a-proposed-openclaw-to-a2a-plugin-architecture">A Proposed OpenClaw to A2A Plugin Architecture</h2>
<p>This section is my original contribution in the article.</p>
<p>I think the cleanest first architecture is <strong>not</strong> a full bidirectional bridge. It's a narrow outbound delegation plugin that lets OpenClaw call a small allowlist of remote A2A agents.</p>
<p>The design goal is simple:</p>
<p><strong>Reuse OpenClaw for user-facing conversations and local tool access, but use A2A only when a remote specialist agent is the best place to do the work.</strong></p>
<p>Here is the architecture I would start with:</p>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/e88f06dd-f108-48b2-a9ee-b74eac6b733b.png" alt="Architecture diagram of an OpenClaw-to-A2A plugin showing components such as delegation tool, policy engine, Agent Card cache, session-to-task mapper, task poller, and remote A2A agent." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h3 id="heading-why-this-design-is-a-good-fit-for-openclaw">Why This Design is a Good Fit for OpenClaw</h3>
<p>This proposal is grounded in extension points OpenClaw already documents.</p>
<p>A plugin can register:</p>
<ul>
<li><p>an <strong>agent tool</strong> for delegation,</p>
</li>
<li><p>a <strong>Gateway method</strong> for health and diagnostics,</p>
</li>
<li><p>an <strong>HTTP route</strong> for future callbacks or webhook verification, and</p>
</li>
<li><p>a <strong>background service</strong> for cache warming, task subscriptions, or cleanup.</p>
</li>
</ul>
<p>That means the bridge doesn't have to modify OpenClaw core to be credible.</p>
<h3 id="heading-the-mapping-table">The Mapping Table</h3>
<p>The most important design decision is how to map OpenClaw’s session model to A2A’s task model.</p>
<p>Here is the mapping I recommend:</p>
<table>
<thead>
<tr>
<th>OpenClaw concept</th>
<th>A2A concept</th>
<th>Why this mapping works</th>
</tr>
</thead>
<tbody><tr>
<td><code>sessionKey</code></td>
<td><code>contextId</code></td>
<td>A single OpenClaw conversation should keep a stable remote context across related delegated turns</td>
</tr>
<tr>
<td>one delegated remote call</td>
<td>one <code>Task</code></td>
<td>each remote specialization request becomes a discrete unit of work</td>
</tr>
<tr>
<td>plugin tool call</td>
<td><code>SendMessage</code></td>
<td>the delegation tool is the natural point where the local agent crosses the protocol boundary</td>
</tr>
<tr>
<td>remote output</td>
<td><code>Artifact</code></td>
<td>A2A wants task outputs returned as artifacts rather than chat-only replies</td>
</tr>
<tr>
<td>plugin HTTP route</td>
<td>callback or future push handler</td>
<td>gives you a place to verify webhooks if you later adopt async push</td>
</tr>
<tr>
<td>Gateway method</td>
<td>status endpoint</td>
<td>gives operators a direct way to inspect relay health without going through the model</td>
</tr>
<tr>
<td>background service</td>
<td>polling or cache work</td>
<td>keeps asynchronous and maintenance work out of the tool call path</td>
</tr>
</tbody></table>
<p>This is the key architectural claim in the article:</p>
<p><strong>Treat the OpenClaw session as the long-lived conversational boundary, and treat each remote A2A task as one delegated execution inside that boundary.</strong></p>
<p>That preserves both sides cleanly.</p>
<h3 id="heading-the-design-in-one-sentence">The Design in One Sentence</h3>
<p>The <code>a2a_delegate</code> tool should:</p>
<ol>
<li><p>resolve an allowlisted remote Agent Card,</p>
</li>
<li><p>reuse an existing A2A <code>contextId</code> for the current <code>sessionKey</code> when possible,</p>
</li>
<li><p>create a fresh remote <code>Task</code> for the new delegated turn,</p>
</li>
<li><p>normalize remote artifacts back into a simple local answer, and</p>
</li>
<li><p>never expose the whole OpenClaw Gateway directly to the public internet.</p>
</li>
</ol>
<p>I like this design because it is incremental, testable, and consistent with OpenClaw’s personal-assistant trust model.</p>
<h2 id="heading-build-the-proof-of-concept-relay">Build the Proof of Concept Relay</h2>
<p>To make the architecture concrete, I built a small proof-of-concept relay.</p>
<p><a href="https://github.com/natarajsundar/openclaw-a2a-secure-agent-runtime">https://github.com/natarajsundar/openclaw-a2a-secure-agent-runtime</a></p>
<p>It's intentionally small. It doesn't try to become a full production plugin. Instead, it proves the hardest conceptual part of the bridge: how to map one OpenClaw session to a reusable A2A context while creating a fresh A2A task per delegated turn.</p>
<p>Here's the repository layout:</p>
<pre><code class="language-plaintext">openclaw-a2a-secure-agent-runtime/
├── README.md
├── package.json
├── examples/
│   └── openclaw-plugin-entry.example.ts
├── src/
│   ├── a2a-client.mjs
│   ├── agent-card-cache.mjs
│   ├── demo.mjs
│   ├── mock-remote-agent.mjs
│   ├── openclaw-a2a-relay.mjs
│   ├── session-task-map.mjs
│   └── utils.mjs
└── test/
    └── relay.test.mjs
</code></pre>
<p>The PoC does six things:</p>
<ol>
<li><p>fetches a remote Agent Card from <code>/.well-known/agent-card.json</code>,</p>
</li>
<li><p>caches it with simple <code>ETag</code> revalidation,</p>
</li>
<li><p>records local <code>sessionKey</code> to remote <code>contextId</code> mappings,</p>
</li>
<li><p>sends an A2A <code>SendMessage</code> request,</p>
</li>
<li><p>polls <code>GetTask</code> until the task finishes, and</p>
</li>
<li><p>converts the remote artifact into a local text answer.</p>
</li>
</ol>
<h3 id="heading-run-the-demo">Run the Demo</h3>
<p>The repo uses only built-in Node.js modules.</p>
<pre><code class="language-shell">cd openclaw-a2a-secure-agent-runtime
npm run demo
</code></pre>
<p>The demo spins up a mock remote A2A server, delegates one task, delegates a second task from the <strong>same</strong> local session, and shows that the same remote <code>contextId</code> is reused.</p>
<h3 id="heading-the-core-relay-idea">The Core Relay Idea</h3>
<p>This is the important logic in plain English:</p>
<ol>
<li><p>look up the most recent remote mapping for the current OpenClaw <code>sessionKey</code></p>
</li>
<li><p>reuse the old <code>contextId</code> if one exists</p>
</li>
<li><p>create a fresh A2A <code>Task</code> for the new request</p>
</li>
<li><p>poll until that task becomes <code>TASK_STATE_COMPLETED</code></p>
</li>
<li><p>turn the returned artifact into a normal text result that OpenClaw can send back to the user</p>
</li>
</ol>
<p>That makes the bridge predictable.</p>
<p>Here's a shortened version of the relay logic:</p>
<pre><code class="language-js">const previous = await sessionTaskMap.latestForSession(sessionKey, remoteBaseUrl);
const contextId = previous?.contextId ?? crypto.randomUUID();

const sendResult = await client.sendMessage({
  text,
  contextId,
  metadata: {
    openclawSessionKey: sessionKey,
    requestedSkillId: skillId,
  },
});

let task = sendResult.task;
while (!isTerminalTaskState(task.status?.state)) {
  await sleep(pollIntervalMs);
  task = await client.getTask(task.id);
}

return {
  contextId,
  taskId: task.id,
  answer: taskArtifactsToText(task),
};
</code></pre>
<p>That's the heart of the design.</p>
<h3 id="heading-why-this-repo-is-a-useful-proof-of-concept">Why This Repo is a Useful Proof of Concept</h3>
<p>A lot of “integration” articles stay too abstract. This repo avoids that problem in three ways.</p>
<p>First, it makes the session-to-context mapping explicit.</p>
<p>Second, it includes a mock remote A2A agent so you can test the flow without needing a large external setup.</p>
<p>Third, it includes a test that checks the most important invariant: repeated delegations from one local OpenClaw session reuse the same A2A context.</p>
<p>That is the piece I most wanted to make concrete, because it is where architecture turns into implementation.</p>
<h2 id="heading-how-the-proof-of-concept-maps-to-a-real-openclaw-plugin">How the Proof of Concept Maps to a Real OpenClaw Plugin</h2>
<p>The proof of concept is the relay core.</p>
<p>A real OpenClaw plugin would wrap that relay with four extension surfaces that the OpenClaw docs already describe.</p>
<h3 id="heading-1-a-delegation-tool">1: A Delegation Tool</h3>
<p>This is the main entry point.</p>
<p>A plugin would register an optional tool like <code>a2a_delegate</code> so the local agent can explicitly choose to delegate work.</p>
<p>That tool should be optional, not always-on, because remote delegation is a side effect and should be easy to disable.</p>
<h3 id="heading-2-a-gateway-method-for-diagnostics">2: A Gateway Method for Diagnostics</h3>
<p>A method like <code>a2a.status</code> would let you inspect whether the relay is healthy, which remote cards are cached, and whether any tasks are still being tracked.</p>
<p>That is much better than asking the model to “tell me if the bridge is healthy.”</p>
<h3 id="heading-3-a-plugin-http-route">3: A Plugin HTTP Route</h3>
<p>You may not need this on day one.</p>
<p>But once you move beyond polling and want push-style callbacks or webhook verification, a plugin route gives you the right boundary for that work.</p>
<h3 id="heading-4-a-background-service">4: A Background Service</h3>
<p>A small service is a clean place to do cache warming, cleanup, or later subscription handling.</p>
<p>That keeps the tool path focused on delegation instead of maintenance work.</p>
<p>If I were turning this into a real plugin package, I would sequence the work in this order:</p>
<ol>
<li><p>wrap the relay in <code>registerTool</code>,</p>
</li>
<li><p>add a small config schema with an allowlist of remote agents,</p>
</li>
<li><p>add <code>a2a.status</code>,</p>
</li>
<li><p>keep polling as the first async model,</p>
</li>
<li><p>add a callback route only if a real use case needs it.</p>
</li>
</ol>
<p>That is the most practical path from theory to a real extension.</p>
<p>I tested the relay flow locally with the mock remote agent and confirmed that repeated delegations from the same local session reused the same remote <code>contextId</code>.</p>
<h2 id="heading-security-notes-before-you-go-further">Security Notes Before You Go Further</h2>
<p>This is the section you should not skip.</p>
<p>The OpenClaw security docs explicitly say the project assumes a <strong>personal assistant</strong> trust model: one trusted operator boundary per Gateway. They also say a shared Gateway for mutually untrusted or adversarial users is not the supported boundary model.</p>
<p>That has a direct consequence for A2A.</p>
<p>A2A is designed for communication across agent systems and organizational boundaries. That is powerful, but it is also a different threat model from a single private OpenClaw deployment.</p>
<p>So the safer design is <strong>not</strong> this:</p>
<ul>
<li><p>expose your personal OpenClaw Gateway publicly,</p>
</li>
<li><p>let arbitrary remote agents reach it,</p>
</li>
<li><p>and hope the tool boundaries are enough.</p>
</li>
</ul>
<p>The safer design is closer to this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/5ab4460a-6c00-4880-a29c-ddc1db00b5fa.png" alt="Diagram illustrating separation between a private OpenClaw deployment and an external A2A interoperability boundary, highlighting secure delegation through a controlled relay." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>This diagram shows two separate trust boundaries.</p>
<p>On the left is your <strong>private OpenClaw deployment</strong>. This includes your Gateway, your sessions, your workspace, and any credentials or tools your agent can access. This boundary is designed for a single trusted operator.</p>
<p>On the right is the <strong>external A2A ecosystem</strong>, where remote agents live. These agents may belong to other teams or organizations and operate under different security assumptions.</p>
<p>The key idea is that communication between these two sides should happen through a <strong>controlled relay layer</strong>, not by directly exposing your OpenClaw Gateway. The relay enforces allowlists, limits what data is sent out, and ensures that remote agents cannot directly access your local tools or state.</p>
<p>This separation lets you experiment with agent interoperability while keeping your personal assistant environment safe.</p>
<p>In plain English, keep your personal assistant boundary private.</p>
<p>If you experiment with A2A, treat that as a <strong>separate exposure boundary</strong> with its own allowlists, auth, and operational controls.</p>
<p>That is why the proof-of-concept relay in this article starts with an explicit remote allowlist.</p>
<h3 id="heading-why-this-design-and-not-the-other-one">Why This Design and Not the Other One?</h3>
<p>A natural question is why this article proposes an <strong>outbound-only A2A bridge first</strong>, instead of immediately building a full bidirectional or server-style integration.</p>
<p>The short answer is that OpenClaw’s current design is centered around a <strong>personal assistant trust boundary</strong>, where one operator controls the Gateway, sessions, and tools. Introducing external agents into that environment requires careful control over what is exposed.</p>
<p>Starting with outbound delegation gives you a safer and more incremental path.</p>
<p>Outbound-only first means:</p>
<ul>
<li><p>preserving the personal-assistant trust boundary, so your local OpenClaw deployment remains private and operator-controlled</p>
</li>
<li><p>avoiding exposing the OpenClaw Gateway as a public A2A server before you have strong auth, policy, and monitoring in place</p>
</li>
<li><p>allowing you to test remote delegation patterns (Agent Cards, tasks, artifacts) without committing to full interoperability complexity</p>
</li>
<li><p>keeping OpenClaw as the user-facing control plane, while remote agents act as optional specialists</p>
</li>
</ul>
<p>This approach follows a common systems design pattern: start with <strong>controlled outbound integration</strong>, validate behavior and constraints, and only then consider expanding to inbound or bidirectional communication.</p>
<p>In practice, this means you can experiment with A2A safely, learn how the models fit together, and evolve the system without introducing unnecessary risk early on.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>OpenClaw is worth learning because it gives you a self-hosted assistant that can live in the communication tools you already use.</p>
<p>The simplest beginner path is still the right one:</p>
<ol>
<li><p>install it,</p>
</li>
<li><p>run onboarding,</p>
</li>
<li><p>check the Gateway,</p>
</li>
<li><p>open the dashboard,</p>
</li>
<li><p>try one private workflow.</p>
</li>
</ol>
<p>That is already a real end-to-end setup.</p>
<p>A2A belongs in the conversation because it gives you a credible way to connect OpenClaw to remote specialist agents later.</p>
<p>But the most important thing in this article isn't the buzzword. It's the boundary design.</p>
<p>If you keep OpenClaw as the private user-facing edge and use a narrow plugin bridge for outbound delegation, the OpenClaw session model and the A2A task model can fit together cleanly.</p>
<p>That is the architectural idea I wanted to make concrete here.</p>
<h3 id="heading-diagram-attribution">Diagram Attribution</h3>
<p>All diagrams in this article were created by the author specifically for this guide.</p>
<h2 id="heading-further-reading">Further Reading</h2>
<ul>
<li><p><a href="https://docs.openclaw.ai/">OpenClaw docs home</a></p>
</li>
<li><p><a href="https://docs.openclaw.ai/start/getting-started">OpenClaw Getting Started</a></p>
</li>
<li><p><a href="https://docs.openclaw.ai/start/wizard">OpenClaw Onboarding Wizard</a></p>
</li>
<li><p><a href="https://docs.openclaw.ai/concepts/multi-agent/">OpenClaw Multi-Agent Routing</a></p>
</li>
<li><p><a href="https://docs.openclaw.ai/concepts/session-tool/">OpenClaw Session Tools</a></p>
</li>
<li><p><a href="https://docs.openclaw.ai/tools/plugin/">OpenClaw Plugin System</a></p>
</li>
<li><p><a href="https://docs.openclaw.ai/plugins/agent-tools">OpenClaw Plugin Agent Tools</a></p>
</li>
<li><p><a href="https://docs.openclaw.ai/cli/acp">OpenClaw ACP bridge</a></p>
</li>
<li><p><a href="https://docs.openclaw.ai/gateway/security">OpenClaw Security</a></p>
</li>
<li><p><a href="https://a2a-protocol.org/latest/specification/">A2A specification</a></p>
</li>
<li><p><a href="https://a2a-protocol.org/latest/topics/agent-discovery/">A2A Agent Discovery</a></p>
</li>
<li><p><a href="https://a2a-protocol.org/latest/topics/a2a-and-mcp/">A2A and MCP</a></p>
</li>
<li><p><a href="https://a2a-protocol.org/latest/definitions/">A2A protocol definition and schema</a></p>
</li>
<li><p><a href="https://a2a-protocol.org/latest/announcing-1.0/">A2A version 1.0 announcement</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build AI Agents That Remember User Preferences (Without Breaking Context) ]]>
                </title>
                <description>
                    <![CDATA[ Why Personalization Breaks Most AI Agents Personalization is one of the most requested features in AI-powered applications. Users expect an agent to remember their preferences, adapt to their style, and improve over time. In practice, personalization... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-ai-agents-that-remember-user-preferences-without-breaking-context/</link>
                <guid isPermaLink="false">698cc32db8fec0245bd9996d</guid>
                
                    <category>
                        <![CDATA[ ai agents ]]>
                    </category>
                
                    <category>
                        <![CDATA[ llm ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ observability ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tools ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nataraj Sundar ]]>
                </dc:creator>
                <pubDate>Wed, 11 Feb 2026 17:58:05 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770832641633/da49bdca-617e-4272-b5b7-012f3c6c1d61.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="heading-why-personalization-breaks-most-ai-agents"><strong>Why Personalization Breaks Most AI Agents</strong></h2>
<p>Personalization is one of the most requested features in AI-powered applications. Users expect an agent to remember their preferences, adapt to their style, and improve over time.</p>
<p>In practice, personalization is unfortunately also one of the fastest ways to break an otherwise working AI agent.</p>
<p>Many agents start with a simple idea: keep adding more conversation history to the prompt. This approach works for demos, but it quickly fails in real applications. Context windows grow too large. Irrelevant information leaks into decisions. Costs increase. Debugging becomes nearly impossible.</p>
<p>If you want a personalized agent that survives production, you need more than a large language model. You need a way to connect the agent to tools, manage multi-step workflows, and store user preferences safely over time – without turning your system into a tangled mess of prompts and callbacks.</p>
<p>In this tutorial, you’ll learn how to design a personalized AI agent using three core building blocks:</p>
<ul>
<li><p><strong>Agent Development Kit (ADK)</strong> to orchestrate agent reasoning and execution</p>
</li>
<li><p><strong>Model Context Protocol (MCP)</strong> to connect tools with clear boundaries</p>
</li>
<li><p><strong>Long-term memory</strong> to store preferences without polluting context</p>
</li>
</ul>
<p>Rather than focusing on setup commands or vendor-specific walkthroughs, we'll focus on the architectural patterns that make personalized agents reliable, debuggable, and maintainable.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770578645884/2fd77443-31d5-4db3-98f0-bba685122a6f.png" alt="User preferences influence an AI agent’s personalized response" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><em>Figure 1 — Personalization influences agent responses</em></p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#what-personalized-means-in-a-real-ai-agent">What “Personalized” Means in a Real AI Agent</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-the-agent-architecture-fits-together">How the Agent Architecture Fits Together</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-design-the-agent-core-with-adk">How to Design the Agent Core with ADK</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-connect-tools-safely-with-mcp">How to Connect Tools Safely with MCP</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-add-long-term-memory-without-polluting-context">How to Add Long-Term Memory Without Polluting Context</a></p>
<ul>
<li><a class="post-section-overview" href="#privacy-consent-and-lifecycle-controls-production-checklist">Privacy, Consent, and Lifecycle Controls (Production Checklist)</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#how-the-end-to-end-agent-flow-works">How the End-to-End Agent Flow Works</a></p>
</li>
<li><p><a class="post-section-overview" href="#common-pitfalls-youll-hit-and-how-to-avoid-them">Common Pitfalls You’ll Hit (and How to Avoid Them)</a></p>
</li>
<li><p><a class="post-section-overview" href="#what-you-learned-and-where-to-go-next">What You Learned and Where to Go Next</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>To follow along with this tutorial, you should have:</p>
<ul>
<li><p>Basic familiarity with Python</p>
</li>
<li><p>A general understanding of how large language models work</p>
</li>
<li><p>Optional: a Google Cloud account if you want to run an end-to-end demo. Otherwise, you can follow the architecture and code patterns locally with stubs. We’ll avoid deep infrastructure setup and focus on design patterns rather than deployment mechanics.</p>
</li>
</ul>
<p>You don’t need prior experience with ADK or MCP. I’ll introduce each concept as it appears.</p>
<h2 id="heading-what-personalized-means-in-a-real-ai-agent"><strong>What “Personalized” Means in a Real AI Agent</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770578714303/4d25a7e4-fcdd-4a1a-a12c-411e41f2021f.png" alt="An AI agent accesses external tools through a protocol boundary/control layer" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><em>Figure 2 — Keep preferences out of the prompt: agent ↔ tools across a protocol boundary</em></p>
<p>Before writing any code, it’s important to define what personalization means in an AI agent.</p>
<p>Personalization is not the same as “remembering everything.” In practice, agent state usually falls into three categories:</p>
<ol>
<li><p><strong>Short-term context:</strong> Information needed to complete the current task. This belongs in the prompt.</p>
</li>
<li><p><strong>Session state:</strong> Temporary decisions or selections made during a workflow. This should be structured and scoped to a session.</p>
</li>
<li><p><strong>Long-term memory:</strong> Durable user preferences or facts that should persist across sessions.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770577191953/3df5aa02-2eb9-4214-bbef-52f18ddb353a.png" alt="Three panels comparing short-term context, session state, and long-term memory" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><em>Figure 3 — Three kinds of agent state: context (now), session (today), memory (always)</em></p>
<p>Most problems happen when these categories are mixed together.</p>
<p>If you store long-term preferences directly in the prompt, the agent’s behavior becomes unpredictable. If you store everything permanently, memory grows without bounds. If you don’t scope memory at all, unrelated sessions start influencing each other.</p>
<p>A well-designed, personalized agent treats memory as a first-class system component, not as extra text added to a prompt.</p>
<p>In the next section, we'll look at how to structure the agent so these concerns stay separated. </p>
<p>By the end of this tutorial, you’ll understand how to design a personalized AI agent that uses long-term memory safely, connects to tools through clear boundaries, and remains debuggable as it grows.</p>
<h2 id="heading-how-the-agent-architecture-fits-together"><strong>How the Agent Architecture Fits Together</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770577351960/9b14cadf-d650-4098-8ce1-9fd706537bb9.png" alt="Reference architecture showing a user, an AI agent core, tools, a memory service, and an orchestration runtime" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><em>Figure 4 — Reference architecture: agent core + tools + memory service + orchestration runtime</em></p>
<p>The above diagram shows a high-level, personalized AI agent architecture. In it, an agent core handles reasoning and planning while interacting with a tool interface layer, a long-term memory service, and an orchestration runtime.</p>
<p>Let’s now understand the moving parts of a personalized agent and how they interact.</p>
<p>At a high level, the system has four responsibilities:</p>
<ol>
<li><p><strong>Reasoning</strong> – deciding what to do next</p>
</li>
<li><p><strong>Execution</strong> – calling tools and services</p>
</li>
<li><p><strong>Memory</strong> – storing and retrieving long-term preferences</p>
</li>
<li><p><strong>Boundaries</strong> – controlling what the agent is allowed to do</p>
</li>
</ol>
<p>A common mistake you’ll see is to blur these responsibilities together. For example, letting the model decide when to write memory, or allowing tools to execute actions without clear constraints.</p>
<p>Instead, you'll design the system so each responsibility has a clear owner. The core components look like this:</p>
<ul>
<li><p><strong>Agent core</strong>: Handles reasoning and planning</p>
</li>
<li><p><strong>Tools</strong>: Perform external actions (read or write)</p>
</li>
<li><p><strong>MCP layer</strong>: Defines how tools are exposed and invoked</p>
</li>
<li><p><strong>Memory services</strong>: Store long-term user data safely</p>
</li>
</ul>
<p>ADK sits at the center, orchestrating how requests flow between these components. The model never directly talks to databases or services. It reasons about actions, and ADK coordinates execution.</p>
<p>This separation makes the system easier to reason about, debug, and extend.</p>
<h2 id="heading-how-to-design-the-agent-core-with-adk"><strong>How to Design the Agent Core with ADK</strong></h2>
<p>Before we dive in, a quick note on what ADK is<strong>.</strong>  </p>
<p><strong>Agent Development Kit (ADK)</strong> is an agent orchestration framework – the glue code between a large language model and your application. Instead of treating the model as a black box that directly “does things”, ADK helps you structure the agent as a system:</p>
<ul>
<li><p>The model focuses on <strong>reasoning</strong> (turning user intent, context, and memory into a structured plan)</p>
</li>
<li><p>Your runtime stays in control of <strong>execution</strong> (deciding which tools can run, how they run, and what gets logged or persisted)</p>
</li>
</ul>
<p>In other words, ADK is what lets you take tool calling and multi-step workflows out of a giant prompt and turn them into a maintainable and testable architecture. In this tutorial, we’ll use ADK to refer to that orchestration layer. The same patterns apply if you use a different agent framework.</p>
<p><strong>Note:</strong> The following code snippets are simplified reference examples intended to illustrate architectural patterns. They’re not production-ready drop-ins.</p>
<p>Once you understand the architecture, you can start designing the agent core. The agent core is responsible for reasoning, not execution.</p>
<p>A helpful mental model is to think of the agent as a planner, not a doer. Its role is to interpret the user’s goal, consider available context and memory, and produce a structured plan that can later be executed in a controlled way.</p>
<p>To make this concrete, the following example shows how an agent can translate user input and memory into an explicit plan. In practice, ADK orchestrates this using a large language model, but the important idea is that the output is structured intent, not side effects.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Reference example for illustration.</span>

<span class="hljs-keyword">from</span> dataclasses <span class="hljs-keyword">import</span> dataclass
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> List, Dict, Any

<span class="hljs-meta">@dataclass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Step</span>:</span>
    tool: str
    args: Dict[str, Any]

<span class="hljs-meta">@dataclass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Plan</span>:</span>
    goal: str
    steps: List[Step]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">build_plan</span>(<span class="hljs-params">user_text: str, memory: Dict[str, Any]</span>) -&gt; Plan:</span>
    <span class="hljs-comment"># In practice, the LLM produces this structure via ADK orchestration.</span>
    goal = <span class="hljs-string">f"Help user: <span class="hljs-subst">{user_text}</span>"</span>
    steps = []
    <span class="hljs-keyword">if</span> memory.get(<span class="hljs-string">"prefers_short_answers"</span>):
        steps.append(Step(tool=<span class="hljs-string">"set_style"</span>, args={<span class="hljs-string">"verbosity"</span>: <span class="hljs-string">"low"</span>}))
    steps.append(Step(tool=<span class="hljs-string">"search_docs"</span>, args={<span class="hljs-string">"query"</span>: user_text}))
    steps.append(Step(tool=<span class="hljs-string">"summarize"</span>, args={<span class="hljs-string">"max_bullets"</span>: <span class="hljs-number">5</span>}))
    <span class="hljs-keyword">return</span> Plan(goal=goal, steps=steps)
</code></pre>
<p>This example illustrates an important constraint: the agent produces a plan, but it doesn’t execute anything directly.</p>
<p>The agent decides <em>what</em> should happen and <em>in what order</em>, while ADK controls <em>when</em> and <em>how</em> each step runs. This separation lets you inspect, test, and reason about decisions before they result in real-world actions.</p>
<p>When personalization is involved, this distinction becomes critical. Preferences may influence planning, but execution should remain tightly controlled by the runtime.</p>
<p>Again, we can consider the agent to be a planner, not a doer.</p>
<p>It should not:</p>
<ul>
<li><p>Perform side effects directly</p>
</li>
<li><p>Write to databases</p>
</li>
<li><p>Call external APIs without supervision</p>
</li>
</ul>
<p>In ADK, this separation is natural. The agent produces intents and tool calls, while the runtime controls how and when those calls are executed.</p>
<p>This design has two major benefits:</p>
<ol>
<li><p><strong>Safety</strong> – you can restrict which tools the agent can access</p>
</li>
<li><p><strong>Debuggability</strong> – you can inspect decisions before execution</p>
</li>
</ol>
<p>When personalization is involved, this becomes even more important. Preferences influence reasoning, but execution should remain tightly controlled.</p>
<h2 id="heading-how-to-connect-tools-safely-with-mcp"><strong>How to Connect Tools Safely with MCP</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770578793149/2e3f8282-341a-4f03-9313-df3f8c9c5174.png" alt="Tool call routed through a control layer with request, validation, execution, and response steps." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><em>Figure 5 — Tool calls with guardrails: request → validate → execute → respond</em></p>
<p>Tools are how agents interact with the real world. They fetch data, generate artifacts, and sometimes perform actions with side effects.</p>
<p>Without clear boundaries, tool usage quickly becomes a source of fragility. Hardcoded API calls leak into prompts, tools evolve independently, and agents gain more authority than intended.</p>
<p>To avoid these problems, tools should be explicitly registered and invoked through a narrow interface. The following example shows a simple tool registry pattern that mirrors how MCP exposes tools to an agent without tightly coupling it to implementations.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Reference example (pseudocode for illustration)</span>

<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Callable, Dict, Any

ToolFn = Callable[[Dict[str, Any]], Dict[str, Any]]

TOOLS: Dict[str, ToolFn] = {}

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">register_tool</span>(<span class="hljs-params">name: str</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">decorator</span>(<span class="hljs-params">fn: ToolFn</span>):</span>
        TOOLS[name] = fn
        <span class="hljs-keyword">return</span> fn
    <span class="hljs-keyword">return</span> decorator

<span class="hljs-meta">@register_tool("search_docs")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">search_docs</span>(<span class="hljs-params">args: Dict[str, Any]</span>) -&gt; Dict[str, Any]:</span>
    query = args[<span class="hljs-string">"query"</span>]
    <span class="hljs-comment"># Replace with your MCP client call (or local tool implementation).</span>
    <span class="hljs-keyword">return</span> {<span class="hljs-string">"results"</span>: [<span class="hljs-string">f"doc://example?q=<span class="hljs-subst">{query}</span>"</span>]}

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">invoke_tool</span>(<span class="hljs-params">name: str, args: Dict[str, Any]</span>) -&gt; Dict[str, Any]:</span>
    <span class="hljs-keyword">if</span> name <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> TOOLS:
        <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Tool not allowed: <span class="hljs-subst">{name}</span>"</span>)
    <span class="hljs-keyword">return</span> TOOLS[name](args)
</code></pre>
<p>The Model Context Protocol (MCP) provides a clean way to formalize this pattern. You can think of MCP the same way operating systems treat system calls.</p>
<p>An application does not directly manipulate hardware. Instead, it requests operations through well-defined system calls. The kernel decides whether the operation is allowed and how it executes.</p>
<p>In the same way, the agent knows <em>what</em> capabilities exist, MCP defines <em>how</em> those capabilities are invoked, and the runtime controls <em>when</em> and <em>whether</em> they execute.</p>
<p>This separation prevents several common problems, including hardcoded API details in prompts, unexpected breakage when tools change, and agents performing unrestricted side effects.</p>
<p>When designing tools, it helps to classify them by risk: read tools for safe queries, generate tools for planning or synthesis, and commit tools for irreversible actions. In a personalized agent, commit tools should be rare and tightly guarded.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770580271505/d5d34514-3b98-4997-85ed-dee55e65d711.png" alt="Observability around tool calls using logs, traces, and timing across decision points" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><em>Figure 6 — Observability around tool calls: logs, traces, timing, decision points</em></p>
<h2 id="heading-how-to-add-long-term-memory-without-polluting-context"><strong>How to Add Long-Term Memory Without Polluting Context</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770577944241/b2a3de65-c5e2-456e-8a33-e9fd4d2695f0.png" alt="Memory candidates extracted from user input, filtered and validated, then stored asynchronously" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><em>Figure 7 — Memory admission pipeline: extract → filter/validate → persist asynchronously</em></p>
<p>Memory is where personalization either succeeds or fails.</p>
<p>You can start by storing everything the user says and feed it back into the prompt. This works briefly, then collapses under its own weight as context grows, costs rise, and behavior becomes unpredictable.</p>
<p>A better approach is to treat memory as structured, curated data so you can control what the agent remembers and why with clear admission rules. Before persisting anything, the system should explicitly decide whether the information is worth remembering. The following function demonstrates a simple memory admission policy.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Simplified Reference Only</span>
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Optional, Dict, Any

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">memory_candidate</span>(<span class="hljs-params">user_text: str</span>) -&gt; Optional[Dict[str, Any]]:</span>
    text = user_text.lower()

    <span class="hljs-comment"># Durable</span>
    <span class="hljs-keyword">if</span> <span class="hljs-string">"for this session"</span> <span class="hljs-keyword">in</span> text <span class="hljs-keyword">or</span> <span class="hljs-string">"ignore after"</span> <span class="hljs-keyword">in</span> text:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>

    <span class="hljs-comment"># Reusable</span>
    <span class="hljs-keyword">if</span> <span class="hljs-string">"my preferred language is"</span> <span class="hljs-keyword">in</span> text:
        <span class="hljs-keyword">return</span> {<span class="hljs-string">"type"</span>: <span class="hljs-string">"preference"</span>, <span class="hljs-string">"key"</span>: <span class="hljs-string">"language"</span>, <span class="hljs-string">"value"</span>: user_text.split()[<span class="hljs-number">-1</span>]}

    <span class="hljs-comment"># Safe (basic example; add PII checks for your use case)</span>
    <span class="hljs-keyword">if</span> <span class="hljs-string">"password"</span> <span class="hljs-keyword">in</span> text <span class="hljs-keyword">or</span> <span class="hljs-string">"ssn"</span> <span class="hljs-keyword">in</span> text:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>

    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>  <span class="hljs-comment"># default: don’t store</span>
</code></pre>
<p>This policy encodes three questions every memory candidate must answer:</p>
<ul>
<li><p>Is it durable? Will it still matter in the future?</p>
</li>
<li><p>Is it reusable? Will it influence future decisions meaningfully?</p>
</li>
<li><p>Is it safe to persist? Does it avoid sensitive or session-specific data?</p>
</li>
</ul>
<p>Only information that passes all three checks should become long-term memory. In practice, this usually includes stable preferences and long-lived constraints, not temporary instructions or intermediate reasoning.</p>
<h3 id="heading-privacy-consent-and-lifecycle-controls-production-checklist"><strong>Privacy, Consent, and Lifecycle Controls (Production Checklist)</strong></h3>
<p>Even if your admission rules are solid, long-term memory introduces governance requirements:</p>
<ul>
<li><p><strong>User control:</strong> allow users to view, export, and delete stored preferences at any time.</p>
</li>
<li><p><strong>Sensitive data handling:</strong> never store secrets/PII. Run PII detection on every memory candidate (and consider redaction).</p>
</li>
<li><p><strong>Retention + consent:</strong> use explicit consent for persistent memory and apply retention windows (TTL) so memory expires unless it’s still useful.</p>
</li>
<li><p><strong>Security + auditability:</strong> encrypt at rest, restrict access by service identity, and keep an audit log of memory writes/updates.</p>
</li>
</ul>
<p>Memory writes should also be asynchronous. The agent should never block while persisting memory, which keeps interactions responsive and avoids coupling reasoning to storage latency.</p>
<h2 id="heading-how-the-end-to-end-agent-flow-works"><strong>How the End-to-End Agent Flow Works</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770578847727/f3cbc4b9-5bc9-4026-ae69-6fd7bc1625fc.png" alt="End-to-end flow showing user input, agent reasoning, tool invocation, and memory updates with feedback loops" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><em>Figure 8 — End-to-end request lifecycle: user input → plan → tools → memory updates</em></p>
<p>At this point, you can trace exactly how memory and tools interact during a single request. With the individual components in place, it’s helpful to see how they work together during a single request. The following example walks through the full lifecycle of a personalized interaction, from user input to response.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Reference example (pseudocode for illustration)</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_request</span>(<span class="hljs-params">user_id: str, user_text: str</span>) -&gt; str:</span>
    memory = memory_store.get(user_id)  <span class="hljs-comment"># e.g., {"prefers_short_answers": True}</span>
    plan = build_plan(user_text, memory)

    tool_outputs = []
    <span class="hljs-keyword">for</span> step <span class="hljs-keyword">in</span> plan.steps:
        out = invoke_tool(step.tool, step.args)
        tool_outputs.append({step.tool: out})

    response = render_response(goal=plan.goal, tool_outputs=tool_outputs, memory=memory)

    cand = memory_candidate(user_text)
    <span class="hljs-keyword">if</span> cand:
        <span class="hljs-comment"># Never block the user on storage.</span>
        memory_store.write_async(user_id, cand)
    <span class="hljs-keyword">return</span> response
</code></pre>
<p>At a high level, the flow looks like this:</p>
<ol>
<li><p>The user sends a message.</p>
</li>
<li><p>Relevant long-term memory is retrieved.</p>
</li>
<li><p>The agent reasons about the request and produces a plan.</p>
</li>
<li><p>ADK invokes tools through MCP as needed.</p>
</li>
<li><p>Results flow back to the agent.</p>
</li>
<li><p>The agent decides whether new information should be persisted.</p>
</li>
<li><p>Memory is written asynchronously.</p>
</li>
<li><p>The final response is returned to the user.</p>
</li>
</ol>
<p>Notice what does <strong>not</strong> happen: the model does not directly write memory, tools do not execute without coordination, and context does not grow without bounds. This structure keeps personalization controlled and predictable.</p>
<h2 id="heading-common-pitfalls-youll-hit-and-how-to-avoid-them"><strong>Common Pitfalls You’ll Hit (and How to Avoid Them)</strong></h2>
<p>Even with a solid architecture, there are a few failure modes that show up repeatedly in real systems. Many of them stem from allowing agents to perform irreversible actions without explicit checks.</p>
<p>The following example shows a simple guardrail for commit-style tools that require approval before execution.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Reference example (pseudocode for illustration)</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">invoke_commit_tool</span>(<span class="hljs-params">name: str, args: Dict[str, Any], approved: bool</span>) -&gt; Dict[str, Any]:</span>
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> approved:
        <span class="hljs-comment"># Require explicit confirmation or policy approval before side effects.</span>
        <span class="hljs-keyword">return</span> {<span class="hljs-string">"status"</span>: <span class="hljs-string">"blocked"</span>, <span class="hljs-string">"reason"</span>: <span class="hljs-string">"commit tools require approval"</span>}

    <span class="hljs-comment"># For example: create_ticket, send_email, submit_order, update_record</span>
    <span class="hljs-keyword">return</span> invoke_tool(name, args)
</code></pre>
<p>This pattern forces a clear decision point before side effects occur. It also creates an audit trail that explains <em>why</em> an action was allowed or blocked.</p>
<p>Other common pitfalls include over-personalization, leaky memory that persists session-specific data, uncontrolled tool growth, and debugging blind spots caused by unclear boundaries. If you see these symptoms, it usually means responsibilities are not clearly separated.</p>
<h2 id="heading-what-you-learned-and-where-to-go-next"><strong>What You Learned and Where to Go Next</strong></h2>
<p>Personalized AI agents are powerful, but they require discipline. The key insight is that personalization is a <strong>systems problem</strong>, not a prompt problem.</p>
<p>By separating reasoning from execution, structuring memory carefully, and using protocols like MCP to enforce boundaries, you can build agents that scale beyond demos and remain maintainable in production.</p>
<p>As you extend this system, resist the urge to add “just one more prompt tweak.” Instead, ask whether the change belongs in memory, tools, or orchestration.  </p>
<p>That mindset will save you time as your agent grows in complexity.  </p>
<p>If you’d like to continue the conversation, you can find me on <a target="_blank" href="https://www.linkedin.com/in/natarajsundar/">LinkedIn</a>.</p>
<p>*All diagrams in this article were created by the author for educational purposes.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use the Singleton Design Pattern in Flutter: Lazy, Eager, and Factory Variations ]]>
                </title>
                <description>
                    <![CDATA[ In software engineering, sometimes you need only one instance of a class across your entire application. Creating multiple instances in such cases can lead to inconsistent behavior, wasted memory, or resource conflicts. The Singleton Design Pattern i... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-the-singleton-design-pattern-in-flutter-lazy-eager-and-factory-variations/</link>
                <guid isPermaLink="false">69740b7bc3e68b8de44a179f</guid>
                
                    <category>
                        <![CDATA[ Singleton Design Pattern ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Object Oriented Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ design patterns ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ood ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Dart ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ flutter development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Factory Design Pattern ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Mobile Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mobile app development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwaseyi Fatunmole ]]>
                </dc:creator>
                <pubDate>Fri, 23 Jan 2026 23:59:55 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769212761076/11d41d2a-8efa-4ddb-9ee2-218f5be00d9f.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In software engineering, sometimes you need only one instance of a class across your entire application. Creating multiple instances in such cases can lead to inconsistent behavior, wasted memory, or resource conflicts.</p>
<p>The Singleton Design Pattern is a creational design pattern that solves this problem by ensuring that a class has exactly one instance and provides a global point of access to it.</p>
<p>This pattern is widely used in mobile apps, backend systems, and Flutter applications for managing shared resources such as:</p>
<ul>
<li><p>Database connections</p>
</li>
<li><p>API clients</p>
</li>
<li><p>Logging services</p>
</li>
<li><p>Application configuration</p>
</li>
<li><p>Security checks during app bootstrap</p>
</li>
</ul>
<p>In this article, we'll explore what the Singleton pattern is, how to implement it in Flutter/Dart, its variations (eager, lazy, and factory), and physical examples. By the end, you'll understand the proper way to use this pattern effectively and avoid common pitfalls.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-the-singleton-pattern">What is the Singleton Pattern?</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-when-to-use-the-singleton-pattern">When to Use the Singleton Pattern</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-a-singleton-class">How to Create a Singleton Class</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-eager-singleton">Eager Singleton</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lazy-singleton">Lazy Singleton</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-choosing-between-eager-and-lazy">Choosing Between Eager and Lazy</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-factory-constructors-in-the-singleton-pattern">Factory Constructors in the Singleton Pattern</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-are-factory-constructors">What Are Factory Constructors?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-implementing-singleton-with-factory-constructor">Implementing Singleton with Factory Constructor</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-when-not-to-use-a-singleton">When Not to Use a Singleton</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-singletons-can-be-problematic">Why Singletons Can Be Problematic</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-scenarios-where-you-should-avoid-singletons">Scenarios Where You Should Avoid Singletons</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-general-guidelines">General Guidelines</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before diving into this tutorial, you should have:</p>
<ol>
<li><p>Basic understanding of the Dart programming language</p>
</li>
<li><p>Familiarity with Object-Oriented Programming (OOP) concepts, particularly classes and constructors</p>
</li>
<li><p>Basic knowledge of Flutter development (helpful but not required)</p>
</li>
<li><p>Understanding of static variables and methods in Dart</p>
</li>
<li><p>Familiarity with the concept of class instantiation</p>
</li>
</ol>
<h2 id="heading-what-is-the-singleton-pattern">What is the Singleton Pattern?</h2>
<p>The Singleton pattern is a creational design pattern that ensures a class has only one instance and that there is a global point of access to the instance.</p>
<p>Again, this is especially powerful when managing shared resources across an application.</p>
<h3 id="heading-when-to-use-the-singleton-pattern">When to Use the Singleton Pattern</h3>
<p>You should use a Singleton when you are designing parts of your system that must exist once, such as:</p>
<ol>
<li><p>Global app state (user session, auth token, app config)</p>
</li>
<li><p>Shared services (logger, API client, database connection)</p>
</li>
<li><p>Resource heavy logic (encryption handlers, ML models, cache manager)</p>
</li>
<li><p>Application boot security (run platform-specific root/jailbreak checks)</p>
</li>
</ol>
<p>For example, in a Flutter app, Android may check developer mode or root status, while iOS checks jailbroken device state. A Singleton security class is a perfect way to enforce that these checks run once globally during app startup.</p>
<h2 id="heading-how-to-create-a-singleton-class">How to Create a Singleton Class</h2>
<p>We have two major ways of creating a singleton class:</p>
<ol>
<li><p>Eager Instantiation</p>
</li>
<li><p>Lazy Instantiation</p>
</li>
</ol>
<h3 id="heading-eager-singleton">Eager Singleton</h3>
<p>This is where the Singleton is created at load time, whether it's used or not.</p>
<p>In this case, the instance of the singleton class as well as any initialization logic runs at load time, regardless of when this class is actually needed or used. Here's how it works:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EagerSingleton</span> </span>{
  EagerSingleton._internal();
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> EagerSingleton _instance = EagerSingleton._internal();

  <span class="hljs-keyword">static</span> EagerSingleton <span class="hljs-keyword">get</span> instance =&gt; _instance;

  <span class="hljs-keyword">void</span> sayHello() =&gt; <span class="hljs-built_in">print</span>(<span class="hljs-string">"Hello from Eager Singleton"</span>);
}

<span class="hljs-comment">//usage</span>
<span class="hljs-keyword">void</span> main() {
  <span class="hljs-comment">// Accessing the singleton globally</span>
  EagerSingleton.instance.sayHello();
}
</code></pre>
<h4 id="heading-how-the-eager-singleton-works">How the Eager Singleton Works</h4>
<p>Let's break down what's happening in this implementation:</p>
<p>First, <code>EagerSingleton._internal()</code> is a private named constructor (notice the underscore prefix). This prevents external code from creating new instances using <code>EagerSingleton()</code>. The only way to get an instance is through the controlled mechanism we're about to define.</p>
<p>Next, <code>static final EagerSingleton _instance = EagerSingleton._internal();</code> is the key line. This creates the single instance immediately when the class is first loaded into memory. Because it's <code>static final</code>, it belongs to the class itself (not any particular instance) and can only be assigned once. The instance is created right here, at declaration time.</p>
<p>The <code>static EagerSingleton get instance =&gt; _instance;</code> getter provides global access to that single instance. Whenever you call <code>EagerSingleton.instance</code> anywhere in your code, you're getting the exact same object that was created when the class loaded.</p>
<p>Finally, <code>sayHello()</code> is just a regular method to demonstrate that the singleton works. You could replace this with any business logic your singleton needs to perform.</p>
<p>When you run the code in <code>main()</code>, the class loads, the instance is created immediately, and <code>EagerSingleton.instance.sayHello()</code> accesses that pre-created instance to call the method.</p>
<h4 id="heading-pros">Pros:</h4>
<ol>
<li><p>This is simple and thread safe, meaning it's not affected by concurrency, especially when your app runs on multithreads.</p>
</li>
<li><p>It's ideal if the instance is lightweight and may be accessed frequently.</p>
</li>
</ol>
<h4 id="heading-cons">Cons:</h4>
<ol>
<li>If this instance is never used through the runtime, it results in wasted memory and could impact application performance.</li>
</ol>
<h3 id="heading-lazy-singleton">Lazy Singleton</h3>
<p>In this case, the singleton instance is only created when the class is called or needed in runtime. Here, a trigger needs to happen before the instance is created. Let's see an example:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LazySingleton</span> </span>{
  LazySingleton._internal(); 
  <span class="hljs-keyword">static</span> LazySingleton? _instance;

  <span class="hljs-keyword">static</span> LazySingleton <span class="hljs-keyword">get</span> instance {
    _instance ??= LazySingleton._internal();
    <span class="hljs-keyword">return</span> _instance!;
  }

  <span class="hljs-keyword">void</span> sayHello() =&gt; <span class="hljs-built_in">print</span>(<span class="hljs-string">"Hello from LazySingleton"</span>);
}

<span class="hljs-comment">//usage </span>
<span class="hljs-keyword">void</span> main() {
  <span class="hljs-comment">// Accessing the singleton globally</span>
  LazySingleton.instance.sayHello();
}
</code></pre>
<h4 id="heading-how-the-lazy-singleton-works">How the Lazy Singleton Works</h4>
<p>The lazy implementation differs from eager in one crucial way: timing.</p>
<p>Again, <code>LazySingleton._internal()</code> is a private constructor that prevents external instantiation.</p>
<p>But notice that <code>static LazySingleton? _instance;</code> is declared as nullable and not initialized. Unlike the eager version, no instance is created at load time. The variable simply exists as <code>null</code> until it's needed.</p>
<p>The magic happens in the getter: <code>_instance ??= LazySingleton._internal();</code> uses Dart's null-aware assignment operator. This line says "if <code>_instance</code> is null, create a new instance and assign it. Otherwise, keep the existing one." This is the lazy initialization: the instance is only created the first time someone accesses it.</p>
<p>The first time you call <code>LazySingleton.instance</code>, <code>_instance</code> is null, so a new instance is created. Every subsequent call finds that <code>_instance</code> already exists, so it just returns that same instance.</p>
<p>The <code>return _instance!;</code> uses the null assertion operator because we know <code>_instance</code> will never be null at this point (we just ensured it's not null in the previous line).</p>
<p>This approach saves memory because if you never call <code>LazySingleton.instance</code> in your app, the instance never gets created.</p>
<h4 id="heading-pros-1">Pros:</h4>
<ol>
<li><p>Saves application memory, as it only creates what is needed in runtime.</p>
</li>
<li><p>Avoids memory leaks.</p>
</li>
<li><p>Is ideal for resource heavy objects while considering application performance.</p>
</li>
</ol>
<h4 id="heading-cons-1">Cons:</h4>
<ol>
<li>Could be difficult to manage in multithreaded environments, as you have to ensure thread safety while following this pattern.</li>
</ol>
<h3 id="heading-choosing-between-eager-and-lazy">Choosing Between Eager and Lazy</h3>
<p>Now that we've broken down these two major types of singleton instantiation, it's worthy of note that you'll need to be intentional while deciding whether to create a singleton the eager or lazy way. Your use case/context should help you determine what singleton pattern you need to apply during object creation.</p>
<p>As an engineer, you need to ask yourself these questions when using a singleton for object creation:</p>
<ol>
<li><p>Do I need this class instantiated when the app loads?</p>
</li>
<li><p>Based on the user journey, will this class always be needed during every session?</p>
</li>
<li><p>Can a user journey be completed without needing to call any logic in this class?</p>
</li>
</ol>
<p>These three questions will determine what pattern (eager or lazy) you should use to fulfill best practices while maintaining scalability and high performance in your application.</p>
<h2 id="heading-factory-constructors-in-the-singleton-pattern">Factory Constructors in the Singleton Pattern</h2>
<p>Applying factory constructors in the Singleton pattern can be powerful if you use them properly. But first, let's understand what factory constructors are.</p>
<h3 id="heading-what-are-factory-constructors">What Are Factory Constructors?</h3>
<p>A factory constructor in Dart is a special type of constructor that doesn't always create a new instance of its class. Unlike regular constructors that must return a new instance, factory constructors can:</p>
<ol>
<li><p>Return an existing instance (perfect for singletons)</p>
</li>
<li><p>Return a subclass instance</p>
</li>
<li><p>Apply logic before deciding what to return</p>
</li>
<li><p>Perform validation or initialization before returning an object</p>
</li>
</ol>
<p>The <code>factory</code> keyword tells Dart that this constructor has the flexibility to return any instance of the class (or its subtypes), not necessarily a fresh one.</p>
<h3 id="heading-implementing-singleton-with-factory-constructor">Implementing Singleton with Factory Constructor</h3>
<p>This allows you to apply initialization logic while your class instance is being created before returning the instance.</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FactoryLazySingleton</span> </span>{
  FactoryLazySingleton._internal();
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> FactoryLazySingleton _instance = FactoryLazySingleton._internal();

  <span class="hljs-keyword">static</span> FactoryLazySingleton <span class="hljs-keyword">get</span> instance =&gt; _instance;

  <span class="hljs-keyword">factory</span> FactoryLazySingleton() {
    <span class="hljs-comment">// Your logic runs here</span>
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Factory constructor called"</span>);
    <span class="hljs-keyword">return</span> _instance;
  }
}
</code></pre>
<h4 id="heading-how-the-factory-constructor-singleton-works">How the Factory Constructor Singleton Works</h4>
<p>This implementation combines aspects of both eager and lazy patterns with additional control.</p>
<p>The <code>FactoryLazySingleton._internal()</code> private constructor and <code>static final _instance</code> create an eager singleton. The instance is created immediately when the class loads.</p>
<p>The <code>static get instance</code> provides the traditional singleton access pattern we've seen before.</p>
<p>But the interesting part is the <code>factory FactoryLazySingleton()</code> constructor. This is a public constructor that looks like a normal constructor call, but behaves differently. When you call <code>FactoryLazySingleton()</code>, instead of creating a new instance, it runs whatever logic you've placed inside (in this case, a print statement), then returns the existing <code>_instance</code>.</p>
<p>This pattern is powerful because:</p>
<ol>
<li><p>You can log when someone tries to create an instance</p>
</li>
<li><p>You can validate conditions before returning the instance</p>
</li>
<li><p>You can apply configuration based on parameters passed to the factory</p>
</li>
<li><p>You can choose to return different singleton instances based on conditions</p>
</li>
</ol>
<p>For example, you might have different configuration singletons for development vs production:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">factory</span> FactoryLazySingleton({<span class="hljs-built_in">bool</span> isProduction = <span class="hljs-keyword">false</span>}) {
  <span class="hljs-keyword">if</span> (isProduction) {
    <span class="hljs-comment">// Apply production configuration</span>
    _instance.configure(productionSettings);
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// Apply development configuration</span>
    _instance.configure(devSettings);
  }
  <span class="hljs-keyword">return</span> _instance;
}
</code></pre>
<h4 id="heading-pros-2">Pros</h4>
<ol>
<li><p>You can add logic before returning an instance</p>
</li>
<li><p>You can cache or reuse the same object</p>
</li>
<li><p>You can dynamically return a subtype if needed</p>
</li>
<li><p>You avoid unnecessary instantiation</p>
</li>
<li><p>You can inject configuration or environment logic</p>
</li>
</ol>
<h4 id="heading-cons-2">Cons</h4>
<ol>
<li><p>Adds slight complexity compared to simple getter access</p>
</li>
<li><p>The factory constructor syntax might confuse developers unfamiliar with the pattern</p>
</li>
<li><p>If overused with complex logic, it can make debugging harder</p>
</li>
<li><p>Can create misleading code where <code>FactoryLazySingleton()</code> looks like it creates a new instance but doesn't</p>
</li>
</ol>
<h2 id="heading-when-not-to-use-a-singleton">When Not to Use a Singleton</h2>
<p>While singletons are powerful, they're not always the right solution. Understanding when to avoid them is just as important as knowing when to use them.</p>
<h3 id="heading-why-singletons-can-be-problematic">Why Singletons Can Be Problematic</h3>
<p>Singletons create global state, which can make your application harder to reason about and test. They introduce tight coupling between components that shouldn't necessarily know about each other, and they can make it difficult to isolate components for unit testing.</p>
<h3 id="heading-scenarios-where-you-should-avoid-singletons">Scenarios Where You Should Avoid Singletons</h3>
<p>Avoid using the Singleton pattern if:</p>
<h4 id="heading-you-need-multiple-independent-instances">You need multiple independent instances</h4>
<p>If different parts of your app need their own separate configurations or states, singletons force you into a one-size-fits-all approach.</p>
<p>For example, if you're building a multi-tenant application where each tenant needs isolated data, a singleton would cause data to bleed between tenants.</p>
<p><strong>Alternative</strong>: Use dependency injection to pass different instances to different parts of your app. Each component receives the specific instance it needs through its constructor or a service locator.</p>
<pre><code class="lang-dart"><span class="hljs-comment">// Instead of singleton</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserRepository</span> </span>{
  <span class="hljs-keyword">final</span> DatabaseConnection db;
  UserRepository(<span class="hljs-keyword">this</span>.db); 
}

<span class="hljs-comment">// Usage</span>
<span class="hljs-keyword">final</span> dbForTenantA = DatabaseConnection(tenantId: <span class="hljs-string">'A'</span>);
<span class="hljs-keyword">final</span> dbForTenantB = DatabaseConnection(tenantId: <span class="hljs-string">'B'</span>);
<span class="hljs-keyword">final</span> repoA = UserRepository(dbForTenantA);
<span class="hljs-keyword">final</span> repoB = UserRepository(dbForTenantB);
</code></pre>
<h4 id="heading-your-architecture-avoids-shared-global-state">Your architecture avoids shared global state</h4>
<p>Modern architectural patterns like BLoC, Provider, or Riverpod in Flutter specifically aim to avoid global mutable state. Singletons work against these patterns by reintroducing global state.</p>
<p><strong>Alternative</strong>: Use state management solutions designed for Flutter. Provider, Riverpod, BLoC, or GetX offer better ways to share data across your app while maintaining testability and avoiding tight coupling.</p>
<pre><code class="lang-dart"><span class="hljs-comment">// Using Provider instead of singleton</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppConfig</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> apiUrl;
  AppConfig(<span class="hljs-keyword">this</span>.apiUrl);
}

<span class="hljs-comment">// Provide it at the top level</span>
<span class="hljs-keyword">void</span> main() {
  runApp(
    Provider&lt;AppConfig&gt;(
      create: (_) =&gt; AppConfig(<span class="hljs-string">'https://api.example.com'</span>),
      child: MyApp(),
    ),
  );
}

<span class="hljs-comment">// Access it anywhere in the widget tree</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">final</span> config = Provider.of&lt;AppConfig&gt;(context);

  }
}
</code></pre>
<h4 id="heading-it-forces-tight-coupling-between-unrelated-classes">It forces tight coupling between unrelated classes</h4>
<p>When multiple unrelated classes depend on the same singleton, they become indirectly coupled. Changes to the singleton affect all these classes, making the codebase fragile and hard to refactor.</p>
<p><strong>Alternative</strong>: Use interfaces and dependency injection. Define what behavior you need through an interface, then inject implementations. This way, classes depend on abstractions, not concrete singletons.</p>
<pre><code class="lang-dart"><span class="hljs-comment">// Define an interface</span>
<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Logger</span> </span>{
  <span class="hljs-keyword">void</span> log(<span class="hljs-built_in">String</span> message);
}

<span class="hljs-comment">// Implementation</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConsoleLogger</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Logger</span> </span>{
  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> log(<span class="hljs-built_in">String</span> message) =&gt; <span class="hljs-built_in">print</span>(message);
}

<span class="hljs-comment">// Classes depend on the interface, not a singleton</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaymentService</span> </span>{
  <span class="hljs-keyword">final</span> Logger logger;
  PaymentService(<span class="hljs-keyword">this</span>.logger);

  <span class="hljs-keyword">void</span> processPayment() {
    logger.log(<span class="hljs-string">'Processing payment'</span>);
  }
}

<span class="hljs-comment">// Easy to test with mock</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MockLogger</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Logger</span> </span>{
  <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">String</span>&gt; logs = [];
  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> log(<span class="hljs-built_in">String</span> message) =&gt; logs.add(message);
}
</code></pre>
<h4 id="heading-you-need-clean-isolated-testing">You need clean, isolated testing</h4>
<p>Singletons maintain state between tests, causing test pollution where one test affects another. This makes tests unreliable and order-dependent.</p>
<p><strong>Alternative</strong>: Use dependency injection and create fresh instances for each test. Most testing frameworks support this pattern, allowing you to inject mocks or fakes easily.</p>
<pre><code class="lang-dart"><span class="hljs-comment">// Testable code</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderService</span> </span>{
  <span class="hljs-keyword">final</span> PaymentProcessor processor;
  OrderService(<span class="hljs-keyword">this</span>.processor);
}

<span class="hljs-comment">// In tests</span>
<span class="hljs-keyword">void</span> main() {
  test(<span class="hljs-string">'processes order successfully'</span>, () {
    <span class="hljs-keyword">final</span> mockProcessor = MockPaymentProcessor();
    <span class="hljs-keyword">final</span> service = OrderService(mockProcessor); 

  });
}
</code></pre>
<h3 id="heading-general-guidelines">General Guidelines</h3>
<p>Use singletons sparingly and only when you truly need exactly one instance of something for the entire application lifecycle. Good candidates include logging systems, application-level configuration, and hardware interface managers.</p>
<p>For most other cases, prefer dependency injection, state management solutions, or simply passing instances where needed. These approaches make your code more flexible, testable, and maintainable.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The Singleton pattern is a powerful creational tool, but like every tool, you should use it strategically.</p>
<p>Overusing singletons can make apps tightly coupled, hard to test, and less maintainable.</p>
<p>But when used correctly, the Singleton pattern helps you save memory, enforce consistency, and control object lifecycle beautifully.</p>
<p>The key is understanding your specific use case and choosing the right implementation approach – whether eager, lazy, or factory-based – that best serves your application's needs while maintaining clean, testable code.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ System Architecture Documentation Best Practices and Tools ]]>
                </title>
                <description>
                    <![CDATA[ Imagine being asked to give UX feedback on a system workflow document and realizing you can’t understand a word of it. That’s exactly what happened to me. As an IT support officer, I can put myself in the perspective of a user and identify friction p... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/system-architecture-documentation-best-practices-and-tools/</link>
                <guid isPermaLink="false">691484910576aea108fc08d8</guid>
                
                    <category>
                        <![CDATA[ documentation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Collaboration ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ifeoma Udu ]]>
                </dc:creator>
                <pubDate>Wed, 12 Nov 2025 12:58:57 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762950321590/b67b93ef-de20-430b-a160-13631259c1d5.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Imagine being asked to give UX feedback on a system workflow document and realizing you can’t understand a word of it. That’s exactly what happened to me.</p>
<p>As an IT support officer, I can put myself in the perspective of a user and identify friction points, but this document had no visuals, no simplified explanations, just walls of backend jargon: <em>service mesh, container orchestration, async queues, REST APIs… you name it.</em></p>
<p>I realized quickly: if someone like me struggles to understand this, so will PMs, frontend devs, new hires, and even other IT staff.</p>
<p>Here’s a practical guide for creating system architecture documentation that anyone on your team can read and use:</p>
<ul>
<li><p><a class="post-section-overview" href="#heading-step-1-show-the-system-from-different-angles">Step 1: Show the System from Different Angles</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-make-diagrams-the-star">Step 2: Make Diagrams the Star</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-translate-tech-into-user-relevant-outcomes">Step 3: Translate Tech Into User-Relevant Outcomes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-make-communication-clear">Step 4: Make Communication Clear</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-keep-it-simple-and-consistent">Step 5: Keep it Simple and Consistent</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-system-architecture-documentation-tools-for-teams">System Architecture Documentation Tools for Teams</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-step-1-show-the-system-from-different-angles"><strong>Step 1: Show the System from Different Angles</strong></h2>
<p>A good architecture doc isn’t just a list of tech terms. Think about who is reading it:</p>
<p><strong>A. Conceptual View (PM/UX/business folks)</strong></p>
<ul>
<li><p>What the system does for the user.</p>
</li>
<li><p>Example: <em>“User Authentication System,” “Checkout Service”</em></p>
</li>
<li><p>Focus on user value and business goals.</p>
</li>
</ul>
<p><strong>B. Component View (frontend developers/IT staff)</strong></p>
<ul>
<li><p>How the parts interact.</p>
</li>
<li><p>Example: <em>“Web App calls API Gateway → Microservice → Database”</em></p>
</li>
<li><p>Focus on data flow and system boundaries.</p>
</li>
</ul>
<p><strong>C.   Operational View (backend/DevOps)</strong></p>
<ul>
<li><p>Where the system runs and how.</p>
</li>
<li><p>Example: <em>servers, databases, cloud setup, scaling.</em></p>
</li>
<li><p>Focus on infrastructure and deployment.</p>
</li>
</ul>
<p>This way, everyone can find what’s relevant to their role without getting lost in technical weeds.</p>
<h2 id="heading-step-2-make-diagrams-the-star"><strong>Step 2: Make Diagrams the Star</strong></h2>
<p>Words alone don’t cut it. Diagrams help people visualize the system, especially if they’re not experts.</p>
<p><strong>Types of Diagrams to Include</strong></p>
<ul>
<li><p><strong>System Context Diagram:</strong> Shows the system and its external dependencies. UX/PM/IT staff can see how it touches users and other systems.</p>
</li>
<li><p><strong>Container Diagram:</strong> Shows main boundaries like <em>“Web App,” “Auth API,” “Database.”</em> Frontend and backend teams benefit.</p>
</li>
<li><p><strong>UML/Component Diagram:</strong> Shows internal structure or interactions. Mostly backend focus, but helps everyone understand flow.</p>
</li>
</ul>
<p><strong>Tip:</strong> Even a simple flowchart drawn in PowerPoint, Figma, or by hand is better than none. Clarity matters more than perfection.</p>
<p>Diagrams help:</p>
<ul>
<li><p>UX sees user impact.</p>
</li>
<li><p>Frontend knows which services to hook up.</p>
</li>
<li><p>Backend sees infrastructure and interactions.</p>
</li>
<li><p>Everyone shares the same mental picture.</p>
</li>
</ul>
<h3 id="heading-example-1-system-context-diagram">Example 1: System Context Diagram</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762421963002/ab9f11d4-e30f-4e4a-9510-55b4b3f5e8ad.jpeg" alt="Flowchart illustrating an end user visiting a web app, which processes payments via Stripe API and sends emails through SendGrid using webhooks." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>This diagram illustrates who uses the system (a person on the web) and which external services it depends on, like Stripe for payments and SendGrid for emails. It doesn't show the internal workings of the system, just what it connects to.</p>
<h3 id="heading-example-2-container-diagram">Example 2: Container Diagram</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762612172310/1ef1b7e9-0441-4d34-b136-2283ec0b4c56.jpeg" alt="Flowchart depicting a web application architecture. The sequence starts with a web browser, leading to a frontend app, then to an API gateway. The gateway splits into two paths: one leads to an Auth Service connected to a User Database, and the other to an Order Service connected to an Orders Database." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>This diagram illustrates the main components of the system: the Web App, which is the user interface; the Auth API, responsible for handling login and security; and the User Database, where user profiles are stored. The arrows indicate how these components interact with each other.</p>
<p><strong>Practical Tip: Tools to Create Clear Architecture Docs.</strong></p>
<h2 id="heading-step-3-translate-tech-into-user-relevant-outcomes"><strong>Step 3: Translate Tech Into User-Relevant Outcomes</strong></h2>
<p>System architecture goes beyond databases and queues, focusing on making the product fast, reliable, and secure for users. Link technical requirements to outcomes everyone can understand:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Requirement</strong></td><td>❌ <strong>Technical Jargon</strong></td><td>✅ <strong>User Outcome</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Scalability</td><td>Kubernetes for container orchestration</td><td>Can handle 10x daily users without slowdowns.</td></tr>
<tr>
<td>Performance</td><td>CDN + caching</td><td>Pages load in under 500ms, no “loading” screens.</td></tr>
<tr>
<td>Security</td><td>TLS 1.3 for data transfer</td><td>User data is safe; only authorized systems access PII(Personally Identifiable Information)<strong>.</strong></td></tr>
</tbody>
</table>
</div><p>Even a person with basic UX awareness can see why tech decisions matter.</p>
<h2 id="heading-step-4-make-communication-clear"><strong>Step 4: Make Communication Clear</strong></h2>
<p>One major source of confusion is how different parts of the system talk to each other. Spell it out:</p>
<p>a) Frontend ↔ Backend: Clearly explain how your frontend connects to the backend.</p>
<p>Example: <em>“The website sends login requests to the Auth API.”</em></p>
<p>b) Backend ↔ Backend: Explain whether services communicate with each other instantly (synchronous) or through background tasks like message queues (asynchronous). This helps the team understand why some actions feel instant to users, while others take time.</p>
<p>Even non-backend readers can follow the flow and understand how it impacts the product.</p>
<h2 id="heading-step-5-keep-it-simple-and-consistent"><strong>Step 5: Keep it Simple and Consistent</strong></h2>
<ul>
<li><p>Use headings, bullet points, and a table of contents. Don’t write a novel.</p>
</li>
<li><p>Keep names consistent: <em>“User Service”</em> in diagrams should match text labels.</p>
</li>
<li><p>Explain the “why” behind major decisions:</p>
</li>
</ul>
<p><em>“We chose a NoSQL DB for User Profiles because it requires fast read/write for non-relational data.”</em></p>
<p>Consistency and simplicity make the doc useful to everyone, not just backend experts.</p>
<h2 id="heading-system-architecture-documentation-tools-for-teams"><strong>System Architecture Documentation Tools for Teams</strong></h2>
<p>Great architecture documentation lives where your team already works and uses tools that are easy to update, share, and understand. Below are the common types of tools teams use, grouped by purpose.</p>
<h3 id="heading-documentation-platforms-where-you-write-the-full-doc">Documentation Platforms (Where You Write the Full Doc)</h3>
<p>You can use these tools to combine text, diagrams, and structure a document.</p>
<p><strong>Google Docs</strong><br>Simple, familiar, and collaborative. Supports real-time comments, edit history, and easy sharing. Perfect if your team already uses Gmail or Drive. Just paste diagrams as images.</p>
<p><strong>Confluence</strong><br>Common in larger companies. Integrates with Jira, supports page templates, and lets you embed diagrams. Good for structured knowledge bases.</p>
<p><strong>Notion</strong><br>Flexible workspace for small teams. Mix docs, tasks, and diagrams in one place. Great if your team uses Notion for other work.</p>
<p><strong>GitHub/GitLab Wikis (with Markdown)</strong><br>Ideal for engineering-heavy teams. Docs live next to your code, and you can include diagrams using simple code (like Mermaid). Changes are tracked like code.</p>
<blockquote>
<p><strong>Start with Google Docs</strong> if you’re unsure. A living doc people actually read is better than a “perfect” one no one opens.</p>
</blockquote>
<h3 id="heading-diagramming-tools-where-you-create-visuals"><strong>Diagramming Tools (Where You Create Visuals)</strong></h3>
<p>These help you draw the architecture diagrams you’ll add to your documentation platform.</p>
<p><strong>Draw.io</strong><br>Free, browser-based, and drag-and-drop simple. No sign-up needed. Exports clean PNG/SVG files you can paste into Docs or Confluence. Great for C4-style diagrams (System Context, Container, and so on).</p>
<p><strong>Figma</strong><br>If your team already uses Figma for design, you can create architecture diagrams using basic shapes and arrows. Real-time commenting makes feedback easy. Just export as PNG for Docs.</p>
<p><strong>Mermaid (Diagrams as Code)</strong><br>Write simple text like <code>User --&gt; Web App</code>, and it becomes a diagram. Works in GitHub, GitLab, and tools like Obsidian. Use the <a target="_blank" href="https://mermaid.live/">Mermaid Live Editor</a> to design, then download and paste into Google Docs.</p>
<h4 id="heading-a-key-insight-from-practicing-architects"><strong>A Key Insight from Practicing Architects.</strong></h4>
<p>Avoid tools that <strong>only produce static images</strong> (like PowerPoint, Canva, or basic whiteboards) for anything beyond quick sketches. If the same service appears in three diagrams and you rename it, you’ll have to update all three manually, leading to outdated and inconsistent docs.</p>
<h3 id="heading-how-they-work-together"><strong>How They Work Together</strong></h3>
<ol>
<li><p>Write your doc in Google Docs (or your team’s existing platform).</p>
</li>
<li><p>Create diagrams in Draw.io or Figma (or try Mermaid if you’re curious).</p>
</li>
<li><p>Paste the diagram into your doc, add alt text, and explain what it shows in plain language.</p>
</li>
</ol>
<p>This combo gives you accessibility, collaboration, and maintainability without overwhelming you or your team.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>You don’t need to be a senior engineer to write great architecture docs. You just need clarity, empathy, and the willingness to explain “why.”</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Event-Based Architectures in JavaScript: A Handbook for Devs ]]>
                </title>
                <description>
                    <![CDATA[ In modern software development, event-driven architectures have become one of the most powerful ways to build scalable, decoupled, and responsive systems. Instead of relying on direct calls between components, event-driven systems communicate through... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/event-based-architectures-in-javascript-a-handbook-for-devs/</link>
                <guid isPermaLink="false">690b87a7da01ae579df92085</guid>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ events ]]>
                    </category>
                
                    <category>
                        <![CDATA[ event-driven-architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ German Cocca ]]>
                </dc:creator>
                <pubDate>Wed, 05 Nov 2025 17:21:43 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762296111539/a47bf1c2-1d4d-4c3b-8006-4f3479647f75.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In modern software development, <strong>event-driven architectures</strong> have become one of the most powerful ways to build scalable, decoupled, and responsive systems.</p>
<p>Instead of relying on direct calls between components, event-driven systems communicate through events – messages that signal that something has happened.</p>
<p>JavaScript, with its inherently asynchronous nature and built-in event loop, is a natural fit for this paradigm. From browser interactions to backend microservices, event-based communication enables flexibility, performance, and maintainability across the entire stack.</p>
<p>This handbook explores how event-driven architectures work, how they can be implemented in JavaScript (both in Node.js and in the browser), and why they are foundational to building modern distributed applications.</p>
<h3 id="heading-prerequisites-what-you-should-already-know">Prerequisites: What you should already know</h3>
<ul>
<li><p>JavaScript fundamentals (ES6+): modules, classes, closures, <code>this</code></p>
</li>
<li><p>Asynchronous JS: callbacks, Promises, <code>async</code>/<code>await</code>, and the event loop</p>
</li>
<li><p>Node.js basics</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-introduction">1. Introduction</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-an-event-driven-architecture">What Is an Event-Driven Architecture?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-javascript-naturally-fits-this-paradigm">Why JavaScript Naturally Fits This Paradigm</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-event-driven-vs-request-driven-architectures">Event-Driven vs. Request-Driven Architectures</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-it-makes-sense-to-use-an-event-driven-architecture">When It Makes Sense to Use an Event-Driven Architecture</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-it-might-not-be-the-right-choice">When It Might Not Be the Right Choice</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-typical-business-use-cases">Typical Business Use Cases</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-2-fundamentals-of-the-event-model-in-javascript">2. Fundamentals of the Event Model in JavaScript</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-the-event-loop-task-queue-and-call-stack">The Event Loop, Task Queue, and Call Stack</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-eventemitter-and-the-pubsub-pattern">EventEmitter and the Pub/Sub Pattern</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-eventtarget-customevent-and-browser-events">EventTarget, CustomEvent, and Browser Events</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-putting-it-all-together">Putting It All Together</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-3-publishersubscriber-pubsub-pattern">3. Publisher–Subscriber (Pub/Sub) Pattern</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-concept-and-advantages-of-decoupling">Concept and Advantages of Decoupling</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-basic-implementation-in-plain-javascript">Basic Implementation in Plain JavaScript</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-limitations-and-when-to-use-a-library">Limitations and When to Use a Library</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-4-implementations-in-nodejs">4. Implementations in Node.js</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-use-the-native-events-module">How to Use the Native events Module</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-real-example-event-oriented-microservice">Real Example: Event-Oriented Microservice</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-error-handling-and-backpressure">Error Handling and Backpressure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-an-event-bus-across-services">How to Build an Event Bus Across Services</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary-1">Summary</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-5-event-driven-microservices-architecture">5. Event-Driven Microservices Architecture</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-asynchronous-communication-via-message-brokers">Asynchronous Communication via Message Brokers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-example-order-inventory-notification-flow">Example: Order → Inventory → Notification Flow</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-designing-event-contracts-event-schemas">Designing Event Contracts (Event Schemas)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-to-use-an-event-driven-microservice-architecture">When to Use an Event-Driven Microservice Architecture</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary-2">Summary</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-6-frontend-applications-and-events">6. Frontend Applications and Events</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-custom-events-in-the-browser">Custom Events in the Browser</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-event-communication-in-modern-frameworks">Event Communication in Modern Frameworks</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-real-time-architectures-websockets-and-server-sent-events">Real-Time Architectures: WebSockets and Server-Sent Events</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary-3">Summary</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-7-event-sourcing-and-cqrs-command-query-responsibility-segregation">7. Event Sourcing and CQRS (Command Query Responsibility Segregation)</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-event-sourcing-the-core-idea">Event Sourcing: The Core Idea</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-example-reconstructing-state-from-events">Example: Reconstructing State from Events</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cqrs-command-query-responsibility-segregation">CQRS: Command Query Responsibility Segregation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-difference-between-event-sourcing-and-pubsub">Difference Between Event Sourcing and Pub/Sub</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-to-use-event-sourcing-and-cqrs">When to Use Event Sourcing and CQRS</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary-4">Summary</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-8-benefits-and-challenges">8. Benefits and Challenges</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-benefits-of-eda">Benefits of EDA</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-challenges-of-eda">Challenges of EDA</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary-5">Summary</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-9-real-world-use-cases">9. Real-World Use Cases</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-financial-and-banking-systems">1. Financial and Banking Systems</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-e-commerce-platforms">2. E-commerce Platforms</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-iot-and-sensor-networks">3. IoT and Sensor Networks</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-real-time-analytics-and-monitoring">4. Real-Time Analytics and Monitoring</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-social-networks-and-messaging-apps">5. Social Networks and Messaging Apps</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-6-workflow-automation-and-orchestration">6. Workflow Automation and Orchestration</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary-6">Summary</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-10-best-practices-and-conclusions">10. Best Practices and Conclusions</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-version-and-validate-events">1. Version and Validate Events</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-design-for-idempotency">2. Design for Idempotency</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-keep-events-meaningful-and-self-contained">3. Keep Events Meaningful and Self-Contained</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-implement-robust-error-handling-and-dead-letter-queues">4. Implement Robust Error Handling and Dead-Letter Queues</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-ensure-observability-and-traceability">5. Ensure Observability and Traceability</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-6-use-patterns-for-reliability">6. Use Patterns for Reliability</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-7-choose-the-right-broker-for-the-job">7. Choose the Right Broker for the Job</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-8-balance-event-driven-and-request-driven-approaches">8. Balance Event-Driven and Request-Driven Approaches</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-9-educate-and-align-the-team">9. Educate and Align the Team</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-10-start-small-then-evolve">10. Start Small, Then Evolve</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-1-introduction">1. Introduction</h2>
<p>Software systems are becoming increasingly distributed, asynchronous, and complex. Traditional <strong>request–response</strong> architectures – where one component directly calls another and waits for a reply – often create tight coupling and limit scalability.</p>
<p>In contrast, <strong>event-driven architectures (EDA)</strong> embrace asynchrony by letting components communicate through events (messages that represent a change or an occurrence in the system). When an event happens (for example, <em>“Order Created”</em>), other parts of the system that care about that event can react to it independently, without knowing who triggered it or when.</p>
<p>This simple shift from <strong>commands</strong> to <strong>events</strong> has profound implications for scalability, resilience, and system design. It allows applications to evolve as loosely coupled collections of independent components that listen for and emit events, rather than monolithic blocks of code that depend directly on each other.</p>
<h3 id="heading-what-is-an-event-driven-architecture">What Is an Event-Driven Architecture?</h3>
<p>An event-driven architecture is a software design pattern where the flow of the program is determined by events. An <strong>event</strong> can be any significant change in state, like a user action, a message from another system, a sensor reading, or even an internal trigger like a database update.</p>
<p>In this model:</p>
<ul>
<li><p><strong>Producers</strong> (also called emitters or publishers) generate and broadcast events.</p>
</li>
<li><p><strong>Consumers</strong> (or listeners or subscribers) react to those events asynchronously.</p>
</li>
</ul>
<p>Unlike traditional request-driven systems, producers and consumers don’t directly call each other. Instead, they communicate through a <strong>mediator</strong> (like an event bus, queue, or topic), achieving loose coupling and higher flexibility.</p>
<h3 id="heading-why-javascript-naturally-fits-this-paradigm">Why JavaScript Naturally Fits This Paradigm</h3>
<p>JavaScript was built around an event-driven model from its very beginning. In the browser, every user interaction – clicks, scrolls, network responses – is handled through events. The <strong>event loop</strong>, <strong>callback queue</strong>, and <strong>non-blocking I/O</strong> make JavaScript particularly well-suited for systems where many things happen concurrently.</p>
<p>In Node.js, this model extends to the backend. The <code>EventEmitter</code> API, asynchronous I/O, and the single-threaded event loop allow developers to write scalable services that handle thousands of concurrent connections efficiently. This makes JavaScript a natural language for implementing and experimenting with event-driven systems across the full stack, from the UI to distributed microservices.</p>
<h3 id="heading-event-driven-vs-request-driven-architectures">Event-Driven vs. Request-Driven Architectures</h3>
<p>Here’s a quick summary of the main features and differences:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Aspect</td><td>Request-Driven</td><td>Event-Driven</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Communication</strong></td><td>Direct, synchronous (A calls B)</td><td>Indirect, asynchronous (A emits event, B reacts)</td></tr>
<tr>
<td><strong>Coupling</strong></td><td>Tight (services know each other)</td><td>Loose (services only know event types)</td></tr>
<tr>
<td><strong>Scalability</strong></td><td>Limited by synchronous blocking</td><td>Naturally scalable with asynchronous flows</td></tr>
<tr>
<td><strong>Failure handling</strong></td><td>Errors propagate directly</td><td>Components fail independently</td></tr>
<tr>
<td><strong>Typical example</strong></td><td>REST API call chain</td><td>Message bus or event broker (Kafka, RabbitMQ)</td></tr>
</tbody>
</table>
</div><p>Event-driven systems tend to perform better in environments that require real-time updates, asynchronous workflows, or high concurrency, such as financial transaction systems, IoT platforms, and analytics pipelines.</p>
<p>But adopting an Event-Driven Architecture is not a universal solution. It introduces its own complexities and is best suited to problems where loose coupling, scalability, and reactivity are primary goals.</p>
<h3 id="heading-when-it-makes-sense-to-use-an-event-driven-architecture">When It Makes Sense to Use an Event-Driven Architecture</h3>
<ul>
<li><p><strong>Asynchronous or real-time requirements</strong>: When the system needs to react to changes instantly (for example, new data, user interactions, or external triggers).</p>
</li>
<li><p><strong>High scalability and resilience</strong>: When services must handle variable workloads independently, without blocking or waiting for each other.</p>
</li>
<li><p><strong>Microservices or distributed systems</strong>: When independent services must communicate without strong dependencies or shared state.</p>
</li>
<li><p><strong>Extensibility and flexibility</strong>: When you expect the system to evolve over time, adding new consumers without modifying existing producers.</p>
</li>
<li><p><strong>Data streaming or continuous processing</strong>: When the system processes streams of events (for example, telemetry, logs, or payments) rather than discrete requests.</p>
</li>
</ul>
<h3 id="heading-when-it-might-not-be-the-right-choice">When It Might <em>Not</em> Be the Right Choice</h3>
<ul>
<li><p><strong>Simple, synchronous applications</strong>: For small systems where interactions are linear (for example, a CRUD API or a small monolith), introducing an event bus may be unnecessary overhead.</p>
</li>
<li><p><strong>Strong consistency requirements</strong>; When the system must maintain a strict order of operations or immediate transactional integrity, asynchronous event flows can complicate data coherence.</p>
</li>
<li><p><strong>Limited observability or operational tooling</strong>: Debugging distributed events is harder – tracing and replaying events requires good logging and monitoring infrastructure.</p>
</li>
<li><p><strong>Team inexperience</strong>: If the development team is not familiar with asynchronous systems, event versioning, or message brokers, the cognitive load may outweigh the benefits.</p>
</li>
</ul>
<h3 id="heading-typical-business-use-cases">Typical Business Use Cases</h3>
<ol>
<li><p><strong>E-commerce platforms:</strong> Events like <em>OrderPlaced</em>, <em>PaymentProcessed</em>, <em>ItemShipped</em> trigger workflows across inventory, billing, and logistics services.</p>
</li>
<li><p><strong>Financial and banking systems:</strong> Real-time updates of transactions, fraud detection, and asynchronous settlement processing.</p>
</li>
<li><p><strong>IoT and telemetry processing:</strong> Devices emit data continuously. The backend aggregates, filters, and reacts to these events asynchronously.</p>
</li>
<li><p><strong>Streaming analytics and monitoring:</strong> Continuous event ingestion from applications or sensors to update dashboards and trigger alerts.</p>
</li>
<li><p><strong>Social networks and messaging apps:</strong> Notifications, chat updates, and activity feeds naturally map to event streams that multiple consumers can subscribe to.</p>
</li>
<li><p><strong>Workflow orchestration systems:</strong> Each step in a process (for example, document signed, email sent, approval granted) triggers subsequent actions automatically.</p>
</li>
</ol>
<p>Event-driven architectures change the way we think about program flow. Instead of pulling data or waiting for responses, components <strong>react</strong> to what’s happening in the system.</p>
<p>By leveraging JavaScript’s asynchronous foundations, like the event loop, promises, and non-blocking I/O, developers can build architectures that are more responsive, resilient, and scalable than traditional request-driven designs.</p>
<p>In the next section, we’ll dive deeper into how JavaScript’s event model works, exploring the event loop, the task queue, and the key mechanisms (like <code>EventEmitter</code>) that make this paradigm possible.</p>
<h2 id="heading-2-fundamentals-of-the-event-model-in-javascript">2. Fundamentals of the Event Model in JavaScript</h2>
<p>JavaScript is inherently event-driven. From its earliest days in the browser to its modern incarnation on the server with Node.js, the language has been designed to handle asynchronous operations gracefully through events – signals that something has happened.</p>
<p>Understanding how this works under the hood is essential before applying event-driven principles to large systems.</p>
<h3 id="heading-the-event-loop-task-queue-and-call-stack">The Event Loop, Task Queue, and Call Stack</h3>
<p>At the heart of JavaScript’s concurrency model lies the <strong>event loop</strong>, a mechanism that enables asynchronous, non-blocking behavior in a single-threaded environment.</p>
<p>Let’s break it down:</p>
<ol>
<li><p><strong>Call Stack</strong>: This is where JavaScript executes code line by line. Each function call creates a new frame on the stack.</p>
</li>
<li><p><strong>Task Queue (or Callback Queue)</strong>: When asynchronous operations finish (like a <code>setTimeout</code> or a network request), their callbacks are queued here for later execution.</p>
</li>
<li><p><strong>Event Loop</strong>: Constantly checks if the call stack is empty. When it is, the loop dequeues a task and pushes it onto the stack to execute.</p>
</li>
</ol>
<p>This cycle repeats indefinitely – hence the term <em>“event loop.”</em></p>
<pre><code class="lang-typescript"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">"A"</span>);

<span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"B"</span>);
}, <span class="hljs-number">0</span>);

<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"C"</span>);

<span class="hljs-comment">// Output:</span>
<span class="hljs-comment">// A</span>
<span class="hljs-comment">// C</span>
<span class="hljs-comment">// B</span>
</code></pre>
<p>Even though the timeout is <code>0</code>, the callback runs <strong>after</strong> the synchronous code because it’s queued in the task queue and executed only when the call stack is clear.</p>
<p>This model allows JavaScript to remain responsive and non-blocking, even while performing I/O operations or waiting for user input.</p>
<h3 id="heading-eventemitter-and-the-pubsub-pattern">EventEmitter and the Pub/Sub Pattern</h3>
<p>Node.js exposes its event-driven core through the <code>EventEmitter</code> class – one of its most fundamental building blocks.</p>
<p>An <code>EventEmitter</code> lets objects emit events and subscribe to them. This mechanism forms the foundation for countless Node.js APIs, from HTTP servers to file streams.</p>
<p>Here’s a simple example:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> EventEmitter = <span class="hljs-built_in">require</span>(<span class="hljs-string">'events'</span>);
<span class="hljs-keyword">const</span> emitter = <span class="hljs-keyword">new</span> EventEmitter();

<span class="hljs-comment">// Subscriber (listener)</span>
emitter.on(<span class="hljs-string">'dataReceived'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Data received: <span class="hljs-subst">${data}</span>`</span>);
});

<span class="hljs-comment">// Publisher (emitter)</span>
emitter.emit(<span class="hljs-string">'dataReceived'</span>, <span class="hljs-string">'User profile loaded'</span>);
</code></pre>
<p><strong>Output:</strong></p>
<pre><code class="lang-typescript">Data received: User profile loaded
</code></pre>
<p>Each event has:</p>
<ul>
<li><p>A <strong>name</strong> (string or symbol)</p>
</li>
<li><p>A set of <strong>listeners</strong> (functions) that react to it</p>
</li>
</ul>
<p>This is the classic <strong>Publisher–Subscriber</strong> pattern (Pub/Sub): components publish events, while others subscribe to react – without direct references to each other.</p>
<h3 id="heading-eventtarget-customevent-and-browser-events">EventTarget, CustomEvent, and Browser Events</h3>
<p>In the browser, the same concept exists through the <code>EventTarget</code> API. Every DOM element can listen for or dispatch events.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> button = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'button'</span>);

button.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Button clicked!'</span>);
});
</code></pre>
<p>We can also create <strong>custom events</strong> to simulate our own event-driven behavior:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> userEvent = <span class="hljs-keyword">new</span> CustomEvent(<span class="hljs-string">'userLoggedIn'</span>, {
  detail: { name: <span class="hljs-string">'Alice'</span> }
});

<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'userLoggedIn'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Welcome, <span class="hljs-subst">${e.detail.name}</span>!`</span>);
});

<span class="hljs-built_in">document</span>.dispatchEvent(userEvent);
</code></pre>
<p><strong>Output:</strong></p>
<pre><code class="lang-typescript">Welcome, Alice!
</code></pre>
<p>This lightweight mechanism allows front-end applications to coordinate behavior across components without tight coupling.</p>
<h3 id="heading-putting-it-all-together">Putting It All Together</h3>
<p>Whether in the browser or Node.js, JavaScript’s asynchronous runtime and event-driven APIs form a natural foundation for building reactive, modular, and scalable systems.</p>
<p>In Node.js, nearly everything is an event emitter – HTTP requests, streams, process signals, and even errors. In the browser, events are how users and systems interact through clicks, network responses, and state changes.</p>
<p>This unified model across client and server is what makes JavaScript uniquely powerful for implementing end-to-end event-driven architectures.</p>
<p>In the next section, we’ll explore the Pub/Sub pattern in depth: we’ll understand its advantages, pitfalls, and how to implement it cleanly in plain JavaScript before scaling up to distributed systems.</p>
<h2 id="heading-3-publishersubscriber-pubsub-pattern">3. Publisher–Subscriber (Pub/Sub) Pattern</h2>
<p>The Publisher–Subscriber pattern, often abbreviated as Pub/Sub, is one of the most common and powerful foundations of event-driven systems. It defines how components can communicate asynchronously without knowing each other directly – a principle known as <strong>loose coupling</strong>.</p>
<p>In a Pub/Sub model:</p>
<ul>
<li><p><strong>Publishers</strong> (or emitters) broadcast events.</p>
</li>
<li><p><strong>Subscribers</strong> (or listeners) register interest in those events.</p>
</li>
<li><p>A <strong>broker</strong> (or event bus) acts as a mediator between the two.</p>
</li>
</ul>
<p>This separation allows systems to evolve and scale independently: new subscribers can be added without changing the publishers, and vice versa.</p>
<h3 id="heading-concept-and-advantages-of-decoupling">Concept and Advantages of Decoupling</h3>
<p>In traditional architectures, one component often depends directly on another:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processOrder</span>(<span class="hljs-params">order</span>) </span>{
  sendInvoice(order);
  notifyWarehouse(order);
}
</code></pre>
<p>Here, <code>processOrder</code> is tightly coupled to the functions it calls. If we later need to send a shipping confirmation or trigger analytics, we must modify <code>processOrder</code> again. This violates the <strong>Open/Closed Principle</strong> (open for extension, closed for modification).</p>
<p>In a Pub/Sub model, the same logic becomes event-driven:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> EventEmitter = <span class="hljs-built_in">require</span>(<span class="hljs-string">'events'</span>);
<span class="hljs-keyword">const</span> bus = <span class="hljs-keyword">new</span> EventEmitter();

bus.on(<span class="hljs-string">'order:created'</span>, sendInvoice);
bus.on(<span class="hljs-string">'order:created'</span>, notifyWarehouse);

bus.emit(<span class="hljs-string">'order:created'</span>, { id: <span class="hljs-number">42</span>, items: <span class="hljs-number">3</span> });
</code></pre>
<p>Now, <code>processOrder</code> doesn’t need to know who’s listening. It simply emits an event (<code>order:created</code>), and any number of subscribers can react to it – even ones that didn’t exist when the code was written.</p>
<p><strong>Advantages:</strong></p>
<ul>
<li><p>✅ <strong>Loose coupling</strong> between components</p>
</li>
<li><p>⚙️ <strong>Easier extensibility</strong>: add new behaviors by adding listeners</p>
</li>
<li><p>🚀 <strong>Parallel evolution</strong>: teams can work on producers and consumers independently</p>
</li>
<li><p>🧩 <strong>Greater testability</strong>: events can be simulated in isolation</p>
</li>
</ul>
<h3 id="heading-basic-implementation-in-plain-javascript">Basic Implementation in Plain JavaScript</h3>
<p>While Node.js provides a ready-to-use <code>EventEmitter</code>, you can easily build a minimal event bus in plain JavaScript. This helps illustrate the underlying logic:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createEventBus</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> listeners = {};

  <span class="hljs-keyword">return</span> {
    subscribe(event, callback) {
      <span class="hljs-keyword">if</span> (!listeners[event]) listeners[event] = [];
      listeners[event].push(callback);
    },
    publish(event, data) {
      (listeners[event] || []).forEach(<span class="hljs-function">(<span class="hljs-params">callback</span>) =&gt;</span> callback(data));
    },
    unsubscribe(event, callback) {
      listeners[event] = (listeners[event] || []).filter(<span class="hljs-function">(<span class="hljs-params">cb</span>) =&gt;</span> cb !== callback);
    }
  };
}

<span class="hljs-comment">// Example usage</span>
<span class="hljs-keyword">const</span> bus = createEventBus();

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onUserRegistered</span>(<span class="hljs-params">user</span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Welcome, <span class="hljs-subst">${user.name}</span>!`</span>);
}

bus.subscribe(<span class="hljs-string">'user:registered'</span>, onUserRegistered);
bus.publish(<span class="hljs-string">'user:registered'</span>, { name: <span class="hljs-string">'Alice'</span> });
bus.unsubscribe(<span class="hljs-string">'user:registered'</span>, onUserRegistered);
</code></pre>
<p>This simple implementation already captures the essence of Pub/Sub:</p>
<ul>
<li><p>You can <strong>subscribe</strong> to an event.</p>
</li>
<li><p>You can <strong>publish</strong> events with data.</p>
</li>
<li><p>You can <strong>unsubscribe</strong> dynamically.</p>
</li>
</ul>
<h3 id="heading-limitations-and-when-to-use-a-library"><strong>Limitations and When to Use a Library</strong></h3>
<p>While the above implementation works for small-scale use, real-world systems often require:</p>
<ul>
<li><p>Wildcard or hierarchical event names (for example, <code>order.*</code> or <code>user.created</code>)</p>
</li>
<li><p>Asynchronous delivery (with message queues or brokers)</p>
</li>
<li><p>Error handling and retries</p>
</li>
<li><p>Event persistence or replay</p>
</li>
<li><p>Cross-process or distributed communication</p>
</li>
</ul>
<p>In those cases, using a dedicated library or broker is more appropriate.</p>
<p>Popular options include Node.js’s built-in <code>EventEmitter</code> for in-process events, <code>RxJS</code> for reactive programming and stream composition, and message brokers like RabbitMQ, Kafka, or Redis Streams for distributed, scalable architectures</p>
<p>Each of these tools extends the Pub/Sub model to handle larger scale, fault tolerance, and observability – essential features in modern distributed systems.</p>
<h3 id="heading-summary">Summary</h3>
<p>The Publisher–Subscriber pattern is the backbone of event-driven design. It transforms direct, synchronous function calls into indirect, asynchronous communications, allowing systems to evolve gracefully and handle change without friction.</p>
<p>In JavaScript, this pattern is everywhere – from browser DOM events to Node.js streams and microservice architectures.</p>
<p>In the next section, we’ll dive deeper into practical implementations in Node.js, exploring how the <code>events</code> module powers many of the platform’s most important features and how it can be extended to build robust, event-oriented systems.</p>
<h2 id="heading-4-implementations-in-nodejs">4. Implementations in Node.js</h2>
<p>Node.js was designed from the ground up around the <strong>event-driven paradigm</strong>. Its single-threaded, non-blocking I/O model allows it to handle thousands of concurrent operations efficiently – not by running code in parallel, but by reacting to events as they occur.</p>
<p>At the heart of this model lies the <code>events</code> module, which exposes the <code>EventEmitter</code> class used throughout Node’s core APIs, from HTTP servers to file streams.</p>
<h3 id="heading-how-to-use-the-native-events-module">How to Use the Native <code>events</code> Module</h3>
<p>The <code>EventEmitter</code> class provides a standard way to <strong>emit</strong> and <strong>listen for</strong> events within a Node.js process.<br>It’s a lightweight yet powerful abstraction for asynchronous communication between components.</p>
<p>Let’s look at a simple example:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> EventEmitter = <span class="hljs-built_in">require</span>(<span class="hljs-string">'events'</span>);
<span class="hljs-keyword">const</span> emitter = <span class="hljs-keyword">new</span> EventEmitter();

<span class="hljs-comment">// Register an event listener</span>
emitter.on(<span class="hljs-string">'user:login'</span>, <span class="hljs-function">(<span class="hljs-params">user</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`User logged in: <span class="hljs-subst">${user.name}</span>`</span>);
});

<span class="hljs-comment">// Emit the event</span>
emitter.emit(<span class="hljs-string">'user:login'</span>, { name: <span class="hljs-string">'Alice'</span> });
</code></pre>
<p><strong>Output:</strong></p>
<pre><code class="lang-typescript">User logged <span class="hljs-keyword">in</span>: Alice
</code></pre>
<p>Each <code>EventEmitter</code> instance maintains an internal map of event names to listener functions. Listeners can be added using <code>.on()</code> or <code>.once()</code> (for one-time execution), and events are triggered asynchronously with <code>.emit()</code>.</p>
<h3 id="heading-real-example-event-oriented-microservice">Real Example: Event-Oriented Microservice</h3>
<p>To see this in action, imagine a simplified order-processing microservice:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> EventEmitter = <span class="hljs-built_in">require</span>(<span class="hljs-string">'events'</span>);
<span class="hljs-keyword">const</span> bus = <span class="hljs-keyword">new</span> EventEmitter();

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createOrder</span>(<span class="hljs-params">order</span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Order created: <span class="hljs-subst">${order.id}</span>`</span>);
  bus.emit(<span class="hljs-string">'order:created'</span>, order);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendInvoice</span>(<span class="hljs-params">order</span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Invoice sent for order <span class="hljs-subst">${order.id}</span>`</span>);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateInventory</span>(<span class="hljs-params">order</span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Inventory updated for order <span class="hljs-subst">${order.id}</span>`</span>);
}

<span class="hljs-comment">// Subscribe listeners</span>
bus.on(<span class="hljs-string">'order:created'</span>, sendInvoice);
bus.on(<span class="hljs-string">'order:created'</span>, updateInventory);

<span class="hljs-comment">// Simulate an order</span>
createOrder({ id: <span class="hljs-number">123</span>, items: [<span class="hljs-string">'Book'</span>, <span class="hljs-string">'Pen'</span>] });
</code></pre>
<p><strong>Output:</strong></p>
<pre><code class="lang-typescript">Order created: <span class="hljs-number">123</span>
Invoice sent <span class="hljs-keyword">for</span> order <span class="hljs-number">123</span>
Inventory updated <span class="hljs-keyword">for</span> order <span class="hljs-number">123</span>
</code></pre>
<p>Here, the microservice emits an <code>order:created</code> event whenever a new order is placed. Multiple listeners (invoice and inventory handlers) react independently – a miniature event-driven architecture in a single process.</p>
<p>This approach scales naturally as the system grows. New behaviors, like sending notifications or analytics tracking, can be added by simply subscribing new listeners.</p>
<h3 id="heading-error-handling-and-backpressure">Error Handling and Backpressure</h3>
<p>In event-driven systems, error management is crucial because unhandled exceptions inside event listeners can crash the entire Node.js process.</p>
<p>To prevent this, Node provides built-in mechanisms:</p>
<ol>
<li><p><strong>Error events</strong>: You can emit and handle errors explicitly.</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">const</span> EventEmitter = <span class="hljs-built_in">require</span>(<span class="hljs-string">'events'</span>);
 <span class="hljs-keyword">const</span> emitter = <span class="hljs-keyword">new</span> EventEmitter();

 emitter.on(<span class="hljs-string">'error'</span>, <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
   <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'An error occurred:'</span>, err.message);
 });

 emitter.emit(<span class="hljs-string">'error'</span>, <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Database connection failed'</span>));
</code></pre>
<p> If an <code>'error'</code> event is emitted without at least one listener, Node.js will throw it as an uncaught exception and terminate the process.</p>
</li>
<li><p><strong>Backpressure management</strong>: In streaming scenarios, producers can emit data faster than consumers can handle.</p>
<p> Node.js Streams solve this through <strong>backpressure</strong>, where consumers signal when they are ready for more data.</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);
 <span class="hljs-keyword">const</span> readable = fs.createReadStream(<span class="hljs-string">'large-file.txt'</span>);
 <span class="hljs-keyword">const</span> writable = fs.createWriteStream(<span class="hljs-string">'copy.txt'</span>);

 readable.pipe(writable); <span class="hljs-comment">// Automatically manages flow control</span>
</code></pre>
<p> Under the hood, streams use event-based coordination (<code>data</code>, <code>drain</code>, <code>end</code>) to ensure stability even under heavy load.</p>
</li>
</ol>
<h3 id="heading-how-to-build-an-event-bus-across-services">How to Build an Event Bus Across Services</h3>
<p>While <code>EventEmitter</code> works within a single process, real-world architectures often span multiple microservices or containers. In such cases, an external message broker (like RabbitMQ, Kafka, or Redis Streams) acts as a distributed event bus.</p>
<p>Each service becomes either:</p>
<ul>
<li><p>a <strong>producer</strong> (publishing events), or</p>
</li>
<li><p>a <strong>consumer</strong> (subscribing and reacting).</p>
</li>
</ul>
<p>Node.js integrates seamlessly with these systems using community libraries:</p>
<ul>
<li><p><a target="_blank" href="https://www.npmjs.com/package/amqplib"><code>amqplib</code></a> for RabbitMQ</p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/kafkajs"><code>kafkajs</code></a> for Apache Kafka</p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/redis"><code>redis</code></a> for Redis Pub/Sub</p>
</li>
</ul>
<p>Example (simplified with Redis):</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> { createClient } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'redis'</span>);
<span class="hljs-keyword">const</span> publisher = createClient();
<span class="hljs-keyword">const</span> subscriber = createClient();

<span class="hljs-keyword">await</span> publisher.connect();
<span class="hljs-keyword">await</span> subscriber.connect();

subscriber.subscribe(<span class="hljs-string">'user:created'</span>, <span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`New user event received: <span class="hljs-subst">${message}</span>`</span>);
});

<span class="hljs-keyword">await</span> publisher.publish(<span class="hljs-string">'user:created'</span>, <span class="hljs-built_in">JSON</span>.stringify({ id: <span class="hljs-number">1</span>, name: <span class="hljs-string">'Alice'</span> }));
</code></pre>
<p>This pattern allows <strong>cross-service communication</strong> without tight coupling. Each service reacts to events asynchronously, whether it’s hosted locally or across a cluster.</p>
<h3 id="heading-summary-1">Summary</h3>
<p>The Node.js <code>EventEmitter</code> encapsulates the essence of event-driven design at the process level: lightweight, decoupled, and asynchronous. Combined with external message brokers, it becomes a powerful tool for building scalable, distributed event-driven systems.</p>
<p>Through events, Node.js applications can handle multiple concurrent workflows efficiently, maintain clear separation of concerns, and grow organically as the system evolves.</p>
<p>In the next section, we’ll extend this idea beyond a single application. We’ll explore <strong>Event-Driven Microservices Architecture</strong>, where multiple independent services communicate entirely through asynchronous event flows.</p>
<h2 id="heading-5-event-driven-microservices-architecture">5. Event-Driven Microservices Architecture</h2>
<p>As applications grow, a single event bus inside one process is no longer enough. When your system consists of multiple independently deployed services – each owning its own data and responsibilities – the Event-Driven Architecture becomes a natural fit for enabling asynchronous, decoupled communication.</p>
<p>In an event-driven microservice ecosystem, services don’t call each other directly through HTTP or RPC.<br>Instead, they publish and consume events through a <strong>message broker</strong> – a central medium that handles delivery, queuing, and persistence of messages between services.</p>
<h3 id="heading-asynchronous-communication-via-message-brokers">Asynchronous Communication via Message Brokers</h3>
<p>In a request-driven microservice system, one service directly invokes another via REST or gRPC:</p>
<pre><code class="lang-typescript">Order Service  →  Inventory Service  →  Notification Service
</code></pre>
<p>Each call is synchronous, meaning the caller waits for a response. This creates coupling and potential cascading failures if one service is down or slow.</p>
<p>In an event-driven model, communication happens asynchronously through events:</p>
<pre><code class="lang-typescript">Order Service  →  [Event Bus]  →  Inventory Service, Notification Service
</code></pre>
<p>The event bus becomes the backbone of the system. Each service publishes events and subscribes to those it needs, without knowing who will consume them.</p>
<p>This brings several advantages:</p>
<ul>
<li><p>⚙️ <strong>Loose coupling:</strong> services don’t depend on each other’s availability</p>
</li>
<li><p>📈 <strong>Scalability:</strong> new consumers can subscribe without changing existing code</p>
</li>
<li><p>🔁 <strong>Resilience:</strong> temporary outages are absorbed by the broker’s queues</p>
</li>
<li><p>🧩 <strong>Extensibility:</strong> new workflows can be added just by listening to existing events</p>
</li>
</ul>
<h3 id="heading-example-order-inventory-notification-flow">Example: Order → Inventory → Notification Flow</h3>
<p>Let’s consider a practical scenario in an e-commerce platform:</p>
<ol>
<li><p><strong>Order Service</strong> publishes an <code>order:created</code> event when a user places an order.</p>
</li>
<li><p><strong>Inventory Service</strong> subscribes to <code>order:created</code> and decrements stock.</p>
</li>
<li><p><strong>Notification Service</strong> also subscribes to <code>order:created</code> and sends a confirmation email.</p>
</li>
</ol>
<pre><code class="lang-typescript">          ┌──────────────────────┐
          │   Order Service      │
          │ emits <span class="hljs-string">"order:created"</span>│
          └──────────┬───────────┘
                     │
          ┌──────────▼───────────┐
          │     Event Bus        │
          │ (Kafka, RabbitMQ...) │
          └──────┬───────────────┘
      ┌──────────┴───────────┐   ┌────────────────────┐
      │ Inventory Service    │   │ Notification Service│
      │ updates stock        │   │ sends email         │
      └──────────────────────┘   └────────────────────┘
</code></pre>
<p><strong>Node.js example (simplified with Redis):</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// order-service.js</span>
<span class="hljs-keyword">const</span> { createClient } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'redis'</span>);
<span class="hljs-keyword">const</span> publisher = createClient();
<span class="hljs-keyword">await</span> publisher.connect();

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createOrder</span>(<span class="hljs-params">order</span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Order created: <span class="hljs-subst">${order.id}</span>`</span>);
  <span class="hljs-keyword">await</span> publisher.publish(<span class="hljs-string">'order:created'</span>, <span class="hljs-built_in">JSON</span>.stringify(order));
}

createOrder({ id: <span class="hljs-number">42</span>, items: [<span class="hljs-string">'Book'</span>, <span class="hljs-string">'Pen'</span>] });
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// inventory-service.js</span>
<span class="hljs-keyword">const</span> { createClient } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'redis'</span>);
<span class="hljs-keyword">const</span> subscriber = createClient();
<span class="hljs-keyword">await</span> subscriber.connect();

<span class="hljs-keyword">await</span> subscriber.subscribe(<span class="hljs-string">'order:created'</span>, <span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> order = <span class="hljs-built_in">JSON</span>.parse(message);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Updating inventory for order <span class="hljs-subst">${order.id}</span>`</span>);
});
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// notification-service.js</span>
<span class="hljs-keyword">const</span> { createClient } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'redis'</span>);
<span class="hljs-keyword">const</span> subscriber = createClient();
<span class="hljs-keyword">await</span> subscriber.connect();

<span class="hljs-keyword">await</span> subscriber.subscribe(<span class="hljs-string">'order:created'</span>, <span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> order = <span class="hljs-built_in">JSON</span>.parse(message);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Sending confirmation email for order <span class="hljs-subst">${order.id}</span>`</span>);
});
</code></pre>
<p>Each service is now independent. They communicate only through <strong>events</strong>, not direct calls.</p>
<h3 id="heading-designing-event-contracts-event-schemas">Designing Event Contracts (Event Schemas)</h3>
<p>In a distributed system, events are <strong>contracts</strong> – they define what information producers share and consumers rely on. Defining and maintaining these contracts carefully is crucial to avoid breaking downstream consumers.</p>
<p>A good event should:</p>
<ul>
<li><p>Contain enough context for consumers to act independently</p>
</li>
<li><p>Use a <strong>versioned schema</strong> to evolve safely over time</p>
</li>
<li><p>Include metadata like <code>eventId</code>, <code>timestamp</code>, and <code>source</code></p>
</li>
</ul>
<p><strong>Example event schema (JSON):</strong></p>
<pre><code class="lang-typescript">{
  <span class="hljs-string">"event"</span>: <span class="hljs-string">"order:created"</span>,
  <span class="hljs-string">"version"</span>: <span class="hljs-number">1</span>,
  <span class="hljs-string">"timestamp"</span>: <span class="hljs-string">"2025-10-29T18:45:00Z"</span>,
  <span class="hljs-string">"data"</span>: {
    <span class="hljs-string">"orderId"</span>: <span class="hljs-number">42</span>,
    <span class="hljs-string">"userId"</span>: <span class="hljs-number">123</span>,
    <span class="hljs-string">"items"</span>: [
      { <span class="hljs-string">"sku"</span>: <span class="hljs-string">"BOOK-001"</span>, <span class="hljs-string">"quantity"</span>: <span class="hljs-number">2</span> },
      { <span class="hljs-string">"sku"</span>: <span class="hljs-string">"PEN-003"</span>, <span class="hljs-string">"quantity"</span>: <span class="hljs-number">1</span> }
    ],
    <span class="hljs-string">"total"</span>: <span class="hljs-number">39.90</span>
  }
}
</code></pre>
<p>Best practices:</p>
<ul>
<li><p>Use namespaced event types (<code>order:created</code>, <code>payment:failed</code>)</p>
</li>
<li><p>Include a version number (<code>v1</code>, <code>v2</code>) to avoid schema drift</p>
</li>
<li><p>Store events in a central registry (for example, JSON Schema repository)</p>
</li>
<li><p>Log all events for auditing and debugging</p>
</li>
</ul>
<h3 id="heading-when-to-use-an-event-driven-microservice-architecture">When to Use an Event-Driven Microservice Architecture</h3>
<p>Event-driven microservices are especially valuable when:</p>
<ul>
<li><p>Systems require real-time updates (for example, notifications, analytics)</p>
</li>
<li><p>Components must operate independently and asynchronously</p>
</li>
<li><p>The platform needs to scale horizontally across services</p>
</li>
<li><p>New capabilities should be added without touching existing code</p>
</li>
</ul>
<p>But this architecture also brings challenges:</p>
<ul>
<li><p>Harder to trace flows across multiple asynchronous hops</p>
</li>
<li><p>Requires observability tools (logs, traces, metrics) to debug issues</p>
</li>
<li><p>Event ordering and exact-once delivery can be complex</p>
</li>
<li><p>Increased operational overhead from managing brokers and message queues</p>
</li>
</ul>
<h3 id="heading-summary-2">Summary</h3>
<p>Event-driven microservices take the principles of the Pub/Sub pattern and scale them across distributed systems. By communicating exclusively through asynchronous events, services become autonomous, resilient, and extensible. This is ideal for modern cloud architectures and high-throughput applications.</p>
<p>In the next section, we’ll shift our focus to the front end and explore how event-driven principles power reactivity in browsers and frameworks like React and Vue, and how technologies like <strong>WebSockets</strong> and <strong>Server-Sent Events</strong> enable real-time user experiences.</p>
<h2 id="heading-6-frontend-applications-and-events">6. Frontend Applications and Events</h2>
<p>While backend systems use event-driven architectures to coordinate between services, frontend applications have relied on event-based programming since JavaScript’s creation. And again, every user interaction is handled through events.</p>
<p>Understanding how events flow in the browser, and how modern frameworks like React and Vue build upon this model, is key to creating responsive, decoupled, and real-time user interfaces.</p>
<h3 id="heading-custom-events-in-the-browser">Custom Events in the Browser</h3>
<p>In vanilla JavaScript, every DOM element can emit and listen to events through the <code>EventTarget</code> API.<br>This mechanism is the foundation of how browsers handle user interaction and component communication.</p>
<p><strong>Example – Basic Event Handling:</strong></p>
<pre><code class="lang-typescript">&lt;button id=<span class="hljs-string">"subscribeBtn"</span>&gt;Subscribe&lt;/button&gt;
&lt;script&gt;
  <span class="hljs-keyword">const</span> btn = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'subscribeBtn'</span>);
  btn.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'User subscribed!'</span>);
  });
&lt;/script&gt;
</code></pre>
<p>Here, the button acts as an <strong>event emitter</strong>. When the <code>click</code> event occurs, the listener function reacts. This is a simple example of publish-subscribe behavior within the DOM.</p>
<p>You can also define <strong>custom events</strong> to allow decoupled communication between components:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> userEvent = <span class="hljs-keyword">new</span> CustomEvent(<span class="hljs-string">'user:registered'</span>, {
  detail: { name: <span class="hljs-string">'Alice'</span>, email: <span class="hljs-string">'alice@example.com'</span> }
});

<span class="hljs-comment">// Listen for the event</span>
<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'user:registered'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Welcome <span class="hljs-subst">${e.detail.name}</span>!`</span>);
});

<span class="hljs-comment">// Dispatch it</span>
<span class="hljs-built_in">document</span>.dispatchEvent(userEvent);
</code></pre>
<p>Output:</p>
<pre><code class="lang-typescript">Welcome Alice!
</code></pre>
<p>This approach allows different parts of the UI to react to user actions or system changes without directly calling each other.</p>
<h3 id="heading-event-communication-in-modern-frameworks">Event Communication in Modern Frameworks</h3>
<p>Modern JavaScript frameworks like React, Vue, and Angular abstract the native event system, but the core idea remains the same: <strong>components react to events</strong>.</p>
<h4 id="heading-react-example">React Example</h4>
<p>React’s synthetic event system wraps the browser’s native events, providing a unified interface across browsers.</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">NewsletterSignup</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleSubmit</span>(<span class="hljs-params">e</span>) </span>{
    e.preventDefault();
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Newsletter form submitted!'</span>);
  }

  <span class="hljs-keyword">return</span> (
    &lt;form onSubmit={handleSubmit}&gt;
      &lt;input <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span> placeholder=<span class="hljs-string">"Your email"</span> /&gt;
      &lt;button <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>&gt;Subscribe&lt;/button&gt;
    &lt;/form&gt;
  );
}
</code></pre>
<p>Behind the scenes, React uses an <strong>event delegation</strong> model: it attaches a single listener at the root and dispatches events down the component tree efficiently.</p>
<p>For cross-component communication, React developers often use:</p>
<ul>
<li><p>Context or state managers (like Redux, Zustand, or Recoil)</p>
</li>
<li><p>Event emitter utilities (like <code>mitt</code> or <code>nanoevents</code>)</p>
</li>
<li><p>Custom hooks for modular event handling</p>
</li>
</ul>
<p>Example using a lightweight emitter (<code>mitt</code>):</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> mitt <span class="hljs-keyword">from</span> <span class="hljs-string">'mitt'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> bus = mitt();
</code></pre>
<p>Then anywhere in your app:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Component A</span>
bus.emit(<span class="hljs-string">'theme:changed'</span>, <span class="hljs-string">'dark'</span>);

<span class="hljs-comment">// Component B</span>
bus.on(<span class="hljs-string">'theme:changed'</span>, <span class="hljs-function">(<span class="hljs-params">theme</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Theme updated to <span class="hljs-subst">${theme}</span>`</span>);
});
</code></pre>
<p>This simple event bus decouples components that don’t share a direct parent-child relationship.</p>
<h4 id="heading-vue-example">Vue Example</h4>
<p>Vue provides a native event system for <strong>child-to-parent</strong> communication and also supports global event buses.</p>
<pre><code class="lang-typescript">&lt;template&gt;
  &lt;button <span class="hljs-meta">@click</span>=<span class="hljs-string">"notify"</span>&gt;Notify Parent&lt;/button&gt;
&lt;/template&gt;

&lt;script&gt;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  methods: {
    notify() {
      <span class="hljs-built_in">this</span>.$emit(<span class="hljs-string">'user-registered'</span>, { name: <span class="hljs-string">'Alice'</span> });
    }
  }
};
&lt;/script&gt;
</code></pre>
<p>The parent component can listen for <code>user-registered</code> and react accordingly. Vue 3 also supports custom event buses via external libraries like <code>mitt</code>, enabling component-to-component events without tight coupling.</p>
<h3 id="heading-real-time-architectures-websockets-and-server-sent-events">Real-Time Architectures: WebSockets and Server-Sent Events</h3>
<p>In modern web applications, the event-driven model extends beyond the client, connecting the front end and back end in real-time.</p>
<h4 id="heading-websockets">WebSockets</h4>
<p>WebSockets provide a full-duplex channel between the browser and the server. This means both sides can send events at any time, enabling instant updates without polling.</p>
<p><strong>Example:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> socket = <span class="hljs-keyword">new</span> WebSocket(<span class="hljs-string">'wss://example.com/socket'</span>);

socket.addEventListener(<span class="hljs-string">'open'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Connected to server'</span>);
  socket.send(<span class="hljs-built_in">JSON</span>.stringify({ event: <span class="hljs-string">'user:joined'</span>, name: <span class="hljs-string">'Alice'</span> }));
});

socket.addEventListener(<span class="hljs-string">'message'</span>, <span class="hljs-function">(<span class="hljs-params">msg</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> data = <span class="hljs-built_in">JSON</span>.parse(msg.data);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'New event from server:'</span>, data);
});
</code></pre>
<p>Use cases:</p>
<ul>
<li><p>Real-time chat applications</p>
</li>
<li><p>Live dashboards</p>
</li>
<li><p>Online multiplayer games</p>
</li>
</ul>
<h4 id="heading-server-sent-events-sse">Server-Sent Events (SSE)</h4>
<p>SSE is a simpler alternative when you only need one-way communication – from server to client – using standard HTTP connections.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> source = <span class="hljs-keyword">new</span> EventSource(<span class="hljs-string">'/events'</span>);

source.addEventListener(<span class="hljs-string">'update'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> data = <span class="hljs-built_in">JSON</span>.parse(e.data);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Received update:'</span>, data);
});
</code></pre>
<p>SSE is ideal for live notifications, monitoring dashboards, and continuous data feeds.</p>
<h3 id="heading-summary-3">Summary</h3>
<p>The frontend world has always been event-driven – from DOM interactions to modern component frameworks and real-time connections.</p>
<p>By treating the UI as a system that <strong>reacts to events rather than polling for changes</strong>, we build interfaces that are more responsive, more modular, and easier to extend and integrate with event-driven back ends.</p>
<p>Whether you use <code>CustomEvent</code>, <code>mitt</code>, WebSockets, or SSE, the principle is the same: emit events, listen for changes, and let your app respond asynchronously.</p>
<p>In the next section, we’ll explore how these same principles extend into Event Sourcing and CQRS (Command Query Responsibility Segregation) – advanced architectural patterns that persist and reconstruct system state entirely through events.</p>
<h2 id="heading-7-event-sourcing-and-cqrs-command-query-responsibility-segregation">7. Event Sourcing and CQRS (Command Query Responsibility Segregation)</h2>
<p>Up to this point, we’ve explored events as <strong>transient messages</strong> that trigger behavior – signals passed between components or services. But in more advanced architectures, events can also become the source of truth for the system’s state itself.</p>
<p>This is where <strong>Event Sourcing</strong> and <strong>CQRS</strong> come into play.</p>
<p>These patterns are fundamental in systems that require auditability, replayability, and scalable state reconstruction, such as banking platforms, e-commerce systems, and workflow engines.</p>
<h3 id="heading-event-sourcing-the-core-idea">Event Sourcing: The Core Idea</h3>
<p>In traditional architectures, a system stores only the current state: for example, a database row representing the latest balance of a user’s account.</p>
<p>In Event Sourcing, the system instead stores a series of events that led to that state. Each event represents a historical change, such as <code>AccountCreated</code>, <code>FundsDeposited</code>, or <code>FundsWithdrawn</code>.</p>
<p>When you need the current state, you don’t query a static record – you <strong>replay</strong> all relevant events in sequence.</p>
<p><strong>Traditional Model:</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Account</td><td>Balance</td></tr>
</thead>
<tbody>
<tr>
<td>#001</td><td>$500</td></tr>
</tbody>
</table>
</div><p><strong>Event-Sourced Model:</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Timestamp</td><td>Event</td><td>Data</td></tr>
</thead>
<tbody>
<tr>
<td>10:00 AM</td><td>AccountCreated</td><td>{ id: 1, owner: 'Alice' }</td></tr>
<tr>
<td>10:05 AM</td><td>FundsDeposited</td><td>{ id: 1, amount: 300 }</td></tr>
<tr>
<td>10:10 AM</td><td>FundsDeposited</td><td>{ id: 1, amount: 200 }</td></tr>
</tbody>
</table>
</div><p>To calculate the balance, you replay the events:</p>
<pre><code class="lang-typescript"><span class="hljs-number">0</span> + <span class="hljs-number">300</span> + <span class="hljs-number">200</span> = $<span class="hljs-number">500</span>
</code></pre>
<p>This approach provides:</p>
<ul>
<li><p>🧾 <strong>Full audit history</strong>: every state change is recorded</p>
</li>
<li><p>🔁 <strong>Replayability</strong>: rebuild state after crashes or schema changes</p>
</li>
<li><p>🧩 <strong>Temporal queries</strong>: know what the system looked like at any point in time</p>
</li>
</ul>
<h3 id="heading-example-reconstructing-state-from-events">Example: Reconstructing State from Events</h3>
<p>Let’s illustrate with a simple JavaScript implementation.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> events = [
  { <span class="hljs-keyword">type</span>: <span class="hljs-string">'AccountCreated'</span>, data: { id: <span class="hljs-number">1</span>, owner: <span class="hljs-string">'Alice'</span> } },
  { <span class="hljs-keyword">type</span>: <span class="hljs-string">'FundsDeposited'</span>, data: { id: <span class="hljs-number">1</span>, amount: <span class="hljs-number">300</span> } },
  { <span class="hljs-keyword">type</span>: <span class="hljs-string">'FundsDeposited'</span>, data: { id: <span class="hljs-number">1</span>, amount: <span class="hljs-number">200</span> } },
  { <span class="hljs-keyword">type</span>: <span class="hljs-string">'FundsWithdrawn'</span>, data: { id: <span class="hljs-number">1</span>, amount: <span class="hljs-number">100</span> } }
];

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rebuildAccount</span>(<span class="hljs-params">events</span>) </span>{
  <span class="hljs-keyword">let</span> balance = <span class="hljs-number">0</span>;

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> event <span class="hljs-keyword">of</span> events) {
    <span class="hljs-keyword">switch</span> (event.type) {
      <span class="hljs-keyword">case</span> <span class="hljs-string">'FundsDeposited'</span>:
        balance += event.data.amount;
        <span class="hljs-keyword">break</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-string">'FundsWithdrawn'</span>:
        balance -= event.data.amount;
        <span class="hljs-keyword">break</span>;
    }
  }

  <span class="hljs-keyword">return</span> balance;
}

<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Current balance:'</span>, rebuildAccount(events)); <span class="hljs-comment">// 400</span>
</code></pre>
<p>Here, we never stored a static “balance” field. Instead, we <strong>reconstructed</strong> it from the sequence of past events – the same way a ledger works in accounting.</p>
<p>This technique is powerful for debugging, auditing, or migrating systems: you can replay all events in a new environment to rebuild state exactly as it was.</p>
<h3 id="heading-cqrs-command-query-responsibility-segregation">CQRS: Command Query Responsibility Segregation</h3>
<p><strong>CQRS (Command Query Responsibility Segregation)</strong> is a complementary pattern often used with Event Sourcing.<br>It separates the model for writing data (commands) from the model for reading data (queries).</p>
<ul>
<li><p><strong>Commands</strong> modify system state by producing events (<code>OrderPlaced</code>, <code>PaymentProcessed</code>).</p>
</li>
<li><p><strong>Queries</strong> read data optimized for retrieval (for example, a denormalized “view” of orders).</p>
</li>
</ul>
<p>This separation improves scalability and performance because the read and write sides can evolve independently – even use different databases.</p>
<p><strong>Simplified diagram:</strong></p>
<pre><code class="lang-typescript">[User Action]
      │
      ▼
 ┌────────────┐
 │ Command API│  ---&gt;  emits ---&gt;  [Event Store]
 └────────────┘                      │
                                    ▼
                        ┌────────────────────┐
                        │  Read Model / View │
                        │ (e.g., MongoDB)    │
                        └────────────────────┘
</code></pre>
<p><strong>Example (conceptual):</strong></p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">placeOrder</span>(<span class="hljs-params">order</span>) </span>{
  <span class="hljs-comment">// Write model</span>
  eventStore.push({ <span class="hljs-keyword">type</span>: <span class="hljs-string">'OrderPlaced'</span>, data: order });
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getOrdersView</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Read model</span>
  <span class="hljs-keyword">return</span> eventStore
    .filter(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> e.type === <span class="hljs-string">'OrderPlaced'</span>)
    .map(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> e.data);
}
</code></pre>
<p>Here, the <strong>event store</strong> acts as the single source of truth, while <strong>query views</strong> can be rebuilt or optimized as needed.</p>
<h3 id="heading-difference-between-event-sourcing-and-pubsub">Difference Between Event Sourcing and Pub/Sub</h3>
<p>It’s common to confuse Event Sourcing with simple event-driven messaging, but they solve different problems:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Aspect</td><td>Pub/Sub</td><td>Event Sourcing</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Purpose</strong></td><td>Asynchronous communication</td><td>Persistent state representation</td></tr>
<tr>
<td><strong>Event lifetime</strong></td><td>Temporary (in transit)</td><td>Permanent (stored)</td></tr>
<tr>
<td><strong>Consumer type</strong></td><td>Services that react</td><td>Systems that rebuild state</td></tr>
<tr>
<td><strong>Example</strong></td><td>Send email when order created</td><td>Reconstruct order history</td></tr>
</tbody>
</table>
</div><p>You can – and often should – use both together: an event-sourced service emits domain events to notify other systems.</p>
<h3 id="heading-when-to-use-event-sourcing-and-cqrs">When to Use Event Sourcing and CQRS</h3>
<p><strong>Use when:</strong></p>
<ul>
<li><p>You need a <strong>complete audit trail</strong> or historical reconstruction.</p>
</li>
<li><p>The business domain is complex and event-driven by nature (finance, logistics, IoT).</p>
</li>
<li><p>The system requires <strong>high resilience</strong> and state recoverability.</p>
</li>
</ul>
<p><strong>Avoid when:</strong></p>
<ul>
<li><p>You’re building a small, CRUD-oriented app with limited complexity.</p>
</li>
<li><p>You don’t need event replay or full history, as it adds storage and operational overhead.</p>
</li>
<li><p>Your team lacks experience managing distributed consistency and event evolution.</p>
</li>
</ul>
<h3 id="heading-summary-4">Summary</h3>
<p>Event Sourcing and CQRS extend event-driven design to the data layer. Instead of only reacting to events, your system persists them and uses them as the foundation for rebuilding, auditing, and scaling.</p>
<p>This approach transforms your architecture from a static data store into a living timeline, where every change is captured as part of an ongoing story of the system’s behavior.</p>
<p>In the next section, we’ll analyze the benefits and challenges of event-driven architectures. We’ll explore why they scale so effectively, but also why debugging and observability can be tricky in large distributed environments.</p>
<h2 id="heading-8-benefits-and-challenges">8. Benefits and Challenges</h2>
<p>Event-driven architectures offer remarkable scalability, resilience, and flexibility, qualities that make them a cornerstone of modern distributed systems. But these benefits come with trade-offs: debugging becomes more complex, data consistency is harder to guarantee, and operational visibility requires specialized tooling.</p>
<p>In this section, we’ll examine both sides — why EDAs are so powerful and what challenges teams face when implementing them.</p>
<h3 id="heading-benefits-of-eda">Benefits of EDA</h3>
<h4 id="heading-1-scalability-and-responsiveness">1. Scalability and Responsiveness</h4>
<p>Event-driven systems naturally handle high concurrency. Because components react to events asynchronously, they can process workloads in parallel without blocking one another.</p>
<p>For example, in a retail platform:</p>
<ul>
<li><p>The <strong>Order Service</strong> publishes an event.</p>
</li>
<li><p>The <strong>Inventory</strong>, <strong>Billing</strong>, and <strong>Notification</strong> services consume it concurrently.</p>
</li>
</ul>
<p>This decoupling allows systems to scale horizontally, adding new consumers or instances without affecting existing ones.</p>
<p>Also, when combined with brokers like Kafka or RabbitMQ, EDAs can handle massive throughput while maintaining order and reliability.</p>
<h4 id="heading-2-loose-coupling-and-extensibility">2. Loose Coupling and Extensibility</h4>
<p>In a traditional system, integrating new functionality often requires editing existing components. In an event-driven system, new consumers simply subscribe to existing events.</p>
<p>For instance, adding a new Analytics Service that listens for <code>order:created</code> events requires:</p>
<ul>
<li><p>No changes to the Order Service</p>
</li>
<li><p>No disruption to other consumers</p>
</li>
<li><p>No coordination between teams</p>
</li>
</ul>
<p>This makes event-driven systems extensible by design, which is invaluable for large organizations with multiple teams or evolving business logic.</p>
<h4 id="heading-3-resilience-and-fault-isolation">3. Resilience and Fault Isolation</h4>
<p>Since communication is asynchronous, if one service fails, others can continue working. Events are buffered in the broker and delivered later.</p>
<p>This prevents cascading failures typical of tightly coupled, request-response systems. For example, if the Notification Service is down, orders can still be processed, and notifications will be sent once it recovers.</p>
<p>Many brokers also provide durable queues and retries, ensuring no event is lost even under heavy load or downtime.</p>
<h4 id="heading-4-real-time-and-reactive-experiences">4. Real-Time and Reactive Experiences</h4>
<p>Event-driven systems power real-time applications, from chat apps and IoT platforms to fraud detection systems and live analytics dashboards.</p>
<p>Because events represent changes as they happen, they enable instant updates, alerts, and responsive UIs. When combined with technologies like WebSockets, Server-Sent Events, or GraphQL Subscriptions, the same model extends seamlessly to the frontend.</p>
<h4 id="heading-5-auditability-and-traceability">5. Auditability and Traceability</h4>
<p>When paired with Event Sourcing, EDAs provide a complete audit trail of everything that has happened in the system. This is crucial for domains like finance, healthcare, or logistics, where compliance and historical accuracy are mandatory.</p>
<h3 id="heading-challenges-of-eda"><strong>Challenges of EDA</strong></h3>
<h4 id="heading-1-debugging-and-tracing">1. Debugging and Tracing</h4>
<p>Unlike synchronous systems, where a stack trace shows the full call path, event-driven systems are <strong>non-linear</strong>. An event may pass through multiple services, queues, and transformations before triggering an outcome.</p>
<p>This makes it difficult to answer questions like:</p>
<blockquote>
<p>“Why did this event trigger twice?”<br>“Where did this data originate?”<br>“Which services consumed this message?”</p>
</blockquote>
<p>To mitigate this, teams rely on <strong>distributed tracing</strong> tools such as:</p>
<ul>
<li><p>OpenTelemetry</p>
</li>
<li><p>Jaeger</p>
</li>
<li><p>Zipkin</p>
</li>
<li><p>AWS X-Ray</p>
</li>
<li><p>Kafka UI / Conduktor (for message inspection)</p>
</li>
</ul>
<p>Embedding trace IDs in event metadata is a best practice that allows cross-service correlation of events.</p>
<h4 id="heading-2-data-consistency">2. Data Consistency</h4>
<p>Because events are asynchronous, maintaining strict <strong>transactional consistency</strong> is challenging. For example, when an <code>OrderPlaced</code> event triggers multiple actions, those actions may complete at different times – or even fail independently.</p>
<p>To manage this, developers often use:</p>
<ul>
<li><p><strong>Idempotent event handlers</strong> (safe to re-run)</p>
</li>
<li><p><strong>Outbox pattern</strong> (ensuring events are emitted only after successful database commits)</p>
</li>
<li><p><strong>Saga pattern</strong> (for distributed transactions and compensating actions)</p>
</li>
</ul>
<p>These patterns add robustness but also increase system complexity.</p>
<h4 id="heading-3-message-duplication-and-ordering">3. Message Duplication and Ordering</h4>
<p>In distributed systems, you must assume:</p>
<ul>
<li><p>Events may arrive twice (due to retries)</p>
</li>
<li><p>Events may arrive out of order</p>
</li>
</ul>
<p>Because of this, consumers need to be designed for <a target="_blank" href="https://www.freecodecamp.org/news/idempotence-explained/">idempotency</a> and order independence. Many event stores or brokers (like Kafka) provide partitioning and offsets to preserve partial ordering, but global order is rarely guaranteed.</p>
<h4 id="heading-4-operational-complexity">4. Operational Complexity</h4>
<p>While adding a message broker improves decoupling, it also introduces new infrastructure to manage:</p>
<ul>
<li><p>Brokers and topics</p>
</li>
<li><p>Retention policies</p>
</li>
<li><p>Consumer groups</p>
</li>
<li><p>Dead-letter queues (for failed messages)</p>
</li>
</ul>
<p>Monitoring and maintaining these systems requires DevOps expertise and mature observability practices.</p>
<h4 id="heading-5-team-and-mental-model-shift">5. Team and Mental Model Shift</h4>
<p>Event-driven systems require developers to think differently:</p>
<ul>
<li><p>Systems become <strong>reactive</strong>, not procedural.</p>
</li>
<li><p>Data flows are <strong>eventual</strong>, not immediate.</p>
</li>
<li><p>Debugging requires <strong>system-wide visibility</strong>, not local inspection.</p>
</li>
</ul>
<p>For teams used to request-response logic, this transition can be difficult, requiring training, discipline, and careful design reviews.</p>
<h3 id="heading-summary-5">Summary</h3>
<p>Event-driven architectures offer:</p>
<ul>
<li><p>⚙️ Scalability</p>
</li>
<li><p>🧩 Extensibility</p>
</li>
<li><p>🔁 Resilience</p>
</li>
<li><p>⚡ Real-time capabilities</p>
</li>
</ul>
<p>But they demand:</p>
<ul>
<li><p>🧠 Rethinking data flow</p>
</li>
<li><p>🔍 Better observability</p>
</li>
<li><p>🧰 Advanced tooling</p>
</li>
</ul>
<p>When implemented carefully, EDAs unlock new levels of system flexibility and business agility, but success depends on balancing their power with strong governance, well-defined event contracts, and team alignment.</p>
<p>In the next section, we’ll look at <strong>real-world use cases</strong>, examining how leading industries like fintech, e-commerce, and IoT leverage event-driven architectures to achieve scale, responsiveness, and reliability.</p>
<h2 id="heading-9-real-world-use-cases">9. Real-World Use Cases</h2>
<p>Event-driven architectures are not just theoretical patterns. They power many of the systems we use every day. From instant payments to social networks, EDAs provide the backbone for handling real-time data, asynchronous workflows, and massive scalability.</p>
<p>Below are some of the most common and impactful use cases across different industries.</p>
<h3 id="heading-1-financial-and-banking-systems">1. Financial and Banking Systems</h3>
<p>Financial institutions rely heavily on asynchronous, reliable event flows to process millions of operations safely and in real time.</p>
<h4 id="heading-typical-events">Typical Events</h4>
<ul>
<li><p><code>TransactionInitiated</code></p>
</li>
<li><p><code>FundsDeposited</code></p>
</li>
<li><p><code>PaymentProcessed</code></p>
</li>
<li><p><code>FraudAlertTriggered</code></p>
</li>
</ul>
<h4 id="heading-how-it-works">How It Works</h4>
<p>When a user initiates a payment:</p>
<ol>
<li><p>The Payment Service emits a <code>PaymentInitiated</code> event.</p>
</li>
<li><p>The Fraud Detection Service subscribes to it, analyzing risk in parallel.</p>
</li>
<li><p>The Ledger Service records the transaction asynchronously.</p>
</li>
<li><p>The Notification Service sends confirmations.</p>
</li>
</ol>
<p>Each component operates independently, and failures or slow responses in one don’t block others.</p>
<h4 id="heading-benefits">Benefits</h4>
<ul>
<li><p>Real-time fraud detection</p>
</li>
<li><p>Parallel transaction processing</p>
</li>
<li><p>Clear audit trail for compliance (with Event Sourcing)</p>
</li>
</ul>
<p><strong>Example:</strong> Modern payment systems (like Revolut, Stripe, and PayPal) use event-driven microservices to orchestrate transactions securely and at scale.</p>
<h3 id="heading-2-e-commerce-platforms">2. E-commerce Platforms</h3>
<p>E-commerce systems are naturally event-driven. Every customer action generates events that ripple across subsystems.</p>
<h4 id="heading-typical-events-1">Typical Events</h4>
<ul>
<li><p><code>OrderCreated</code></p>
</li>
<li><p><code>ItemAddedToCart</code></p>
</li>
<li><p><code>InventoryUpdated</code></p>
</li>
<li><p><code>ShipmentDispatched</code></p>
</li>
</ul>
<h4 id="heading-event-flow-example">Event Flow Example</h4>
<p>When a user places an order:</p>
<ol>
<li><p>The Order Service emits <code>OrderCreated</code>.</p>
</li>
<li><p>Inventory Service reserves stock.</p>
</li>
<li><p>Billing Service processes the payment.</p>
</li>
<li><p>Shipping Service schedules delivery.</p>
</li>
<li><p>Analytics Service records metrics.</p>
</li>
</ol>
<p>Each step occurs asynchronously, allowing thousands of orders to be processed concurrently.</p>
<h4 id="heading-benefits-1">Benefits</h4>
<ul>
<li><p>High scalability during peak sales (for example, Black Friday)</p>
</li>
<li><p>Fault isolation between modules</p>
</li>
<li><p>Easy integration of new services (for example, loyalty or recommendation engines)</p>
</li>
</ul>
<p><strong>Example:</strong> Amazon and Shopify both use event-based pipelines for order management, tracking, and analytics.</p>
<h3 id="heading-3-iot-and-sensor-networks">3. IoT and Sensor Networks</h3>
<p>In IoT ecosystems, thousands or millions of devices constantly emit data. Event-driven architectures are essential for ingesting, processing, and reacting to these streams efficiently.</p>
<h4 id="heading-typical-events-2">Typical Events</h4>
<ul>
<li><p><code>TemperatureMeasured</code></p>
</li>
<li><p><code>DeviceConnected</code></p>
</li>
<li><p><code>MotionDetected</code></p>
</li>
<li><p><code>BatteryLow</code></p>
</li>
</ul>
<h4 id="heading-event-flow-example-1">Event Flow Example</h4>
<ol>
<li><p>Devices publish sensor data to a message broker (like MQTT, Kafka, or AWS IoT Core).</p>
</li>
<li><p>The Processing Service filters and enriches data.</p>
</li>
<li><p>Alert Services emit notifications if thresholds are crossed.</p>
</li>
<li><p>Analytics Pipelines store aggregated data for insights.</p>
</li>
</ol>
<h4 id="heading-benefits-2">Benefits</h4>
<ul>
<li><p>Real-time monitoring</p>
</li>
<li><p>Predictive maintenance (based on event patterns)</p>
</li>
<li><p>Scalable ingestion from thousands of sources</p>
</li>
</ul>
<p><strong>Example:</strong> Smart cities and connected vehicles use event-driven systems to react to sensor data in milliseconds, adjusting traffic lights, tracking fleets, or monitoring energy grids.</p>
<h3 id="heading-4-real-time-analytics-and-monitoring">4. Real-Time Analytics and Monitoring</h3>
<p>Modern analytics systems depend on <strong>stream processing</strong>, continuously ingesting and analyzing events to derive insights instantly.</p>
<h4 id="heading-typical-events-3">Typical Events</h4>
<ul>
<li><p><code>PageViewed</code></p>
</li>
<li><p><code>UserLoggedIn</code></p>
</li>
<li><p><code>MetricUpdated</code></p>
</li>
</ul>
<h4 id="heading-event-flow-example-2">Event Flow Example</h4>
<ol>
<li><p>Applications emit user interaction events to a message queue.</p>
</li>
<li><p>A Stream Processor (like Apache Flink or Kafka Streams) aggregates events in real time.</p>
</li>
<li><p>Dashboards and alerting systems consume processed results via WebSockets or APIs.</p>
</li>
</ol>
<h4 id="heading-benefits-3">Benefits</h4>
<ul>
<li><p>Live metrics and dashboards</p>
</li>
<li><p>Early anomaly detection</p>
</li>
<li><p>Continuous feedback loops for ML models</p>
</li>
</ul>
<p><strong>Example:</strong> Netflix uses event-driven data pipelines (built on Kafka) to monitor playback quality and deliver adaptive streaming experiences in real time.</p>
<h3 id="heading-5-social-networks-and-messaging-apps">5. Social Networks and Messaging Apps</h3>
<p>Social platforms are fundamentally <strong>event-driven systems</strong>. Every post, like, message, or comment is an event that triggers updates across multiple systems.</p>
<h4 id="heading-typical-events-4">Typical Events</h4>
<ul>
<li><p><code>PostCreated</code></p>
</li>
<li><p><code>MessageSent</code></p>
</li>
<li><p><code>UserMentioned</code></p>
</li>
<li><p><code>NotificationDelivered</code></p>
</li>
</ul>
<h4 id="heading-event-flow-example-3">Event Flow Example</h4>
<p>When a user sends a message:</p>
<ol>
<li><p>The Chat Service emits <code>MessageSent</code>.</p>
</li>
<li><p>The Notification Service alerts the recipient.</p>
</li>
<li><p>The Search Index Service updates conversations.</p>
</li>
<li><p>The Analytics Service logs engagement metrics.</p>
</li>
</ol>
<h4 id="heading-benefits-4">Benefits</h4>
<ul>
<li><p>Instant notifications and updates</p>
</li>
<li><p>Asynchronous scalability across millions of users</p>
</li>
<li><p>Modular and evolvable product features</p>
</li>
</ul>
<p><strong>Example:</strong> Slack, WhatsApp, and Facebook Messenger rely on distributed event buses to coordinate billions of message and presence events per day.</p>
<h3 id="heading-6-workflow-automation-and-orchestration">6. Workflow Automation and Orchestration</h3>
<p>Workflow systems such as document approvals, CI/CD pipelines, or business processes are often built around events.</p>
<h4 id="heading-typical-events-5">Typical Events</h4>
<ul>
<li><p><code>TaskCreated</code></p>
</li>
<li><p><code>TaskCompleted</code></p>
</li>
<li><p><code>ApprovalGranted</code></p>
</li>
<li><p><code>PipelineDeployed</code></p>
</li>
</ul>
<h4 id="heading-how-it-works-1">How It Works</h4>
<p>Each action in a workflow triggers the next step through events, allowing flexible orchestration without hardcoding dependencies. This makes it easy to reconfigure or extend workflows dynamically.</p>
<p><strong>Example:</strong> GitHub Actions and Zapier use event-driven models to execute workflows automatically based on triggers (for example, a commit, file upload, or webhook).</p>
<h3 id="heading-summary-6">Summary</h3>
<p>Event-driven architectures power some of the most demanding digital systems in existence. Across industries, they provide:</p>
<ul>
<li><p>⚙️ <strong>Scalable infrastructure</strong> for handling massive event streams</p>
</li>
<li><p>⏱ <strong>Real-time responsiveness</strong> to user and system actions</p>
</li>
<li><p>🧩 <strong>Modularity and evolution</strong> as systems grow by subscribing to new events</p>
</li>
</ul>
<p>Whether in fintech, IoT, e-commerce, or analytics, EDAs have proven to be a flexible, future-proof foundation for building systems that react intelligently to change.</p>
<p>In the final section, we’ll synthesize the lessons learned, summarizing best practices, common pitfalls, and key takeaways for adopting event-driven architectures successfully in modern JavaScript ecosystems.</p>
<h2 id="heading-10-best-practices-and-conclusions">10. Best Practices and Conclusions</h2>
<p>Event-driven architectures offer a flexible, scalable, and future-proof foundation for modern software systems. But their power comes with complexity: events are easy to emit but hard to manage at scale without discipline and consistency.</p>
<p>This final section distills practical best practices for designing and operating event-driven systems effectively, followed by a summary reflection on when and how to adopt this architecture.</p>
<h3 id="heading-1-version-and-validate-events">1. Version and Validate Events</h3>
<p>Events evolve over time as your system grows. Adding or changing fields can break consumers if versions aren’t managed carefully.</p>
<p><strong>Best practices:</strong></p>
<ul>
<li><p>Use explicit versioning in event names or schemas (for example, <code>order:created.v2</code>).</p>
</li>
<li><p>Validate event payloads using JSON Schema or tools like <code>ajv</code> or <code>Zod</code>.</p>
</li>
<li><p>Maintain a central event catalog or schema registry shared by all services.</p>
</li>
</ul>
<p>This ensures backward compatibility and reduces surprises when consumers update at different times.</p>
<h3 id="heading-2-design-for-idempotency">2. Design for Idempotency</h3>
<p>In distributed systems, <strong>duplicate messages</strong> are inevitable – retries, network hiccups, or failovers can cause events to be processed multiple times.</p>
<p>Make consumers idempotent, meaning they can handle the same event repeatedly without unintended side effects.</p>
<p>For example:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (!processedEvents.has(event.id)) {
  process(event);
  processedEvents.add(event.id);
}
</code></pre>
<p>Always include a unique event ID and check for duplicates before applying changes.</p>
<h3 id="heading-3-keep-events-meaningful-and-self-contained">3. Keep Events Meaningful and Self-Contained</h3>
<p>Each event should represent a <strong>domain-level change</strong>, not just a technical signal. Avoid overly generic messages like <code>"update"</code> or <code>"dataChanged"</code>, as they make debugging and evolution difficult.</p>
<p>Good events:</p>
<ul>
<li><p>Describe <strong>what happened</strong> (not what to do).</p>
</li>
<li><p>Include <strong>enough context</strong> for consumers to act independently.</p>
</li>
<li><p>Avoid exposing internal database models directly.</p>
</li>
</ul>
<p>Example:</p>
<pre><code class="lang-typescript">{
  <span class="hljs-string">"event"</span>: <span class="hljs-string">"user:email:updated"</span>,
  <span class="hljs-string">"data"</span>: { <span class="hljs-string">"userId"</span>: <span class="hljs-number">123</span>, <span class="hljs-string">"oldEmail"</span>: <span class="hljs-string">"a@x.com"</span>, <span class="hljs-string">"newEmail"</span>: <span class="hljs-string">"b@x.com"</span> }
}
</code></pre>
<p>This provides clear, business-oriented semantics.</p>
<h3 id="heading-4-implement-robust-error-handling-and-dead-letter-queues">4. Implement Robust Error Handling and Dead-Letter Queues</h3>
<p>Not every event will be processed successfully. Network failures, schema mismatches, or transient service outages are inevitable.</p>
<p><strong>Mitigation strategies:</strong></p>
<ul>
<li><p>Use <strong>retry policies</strong> with exponential backoff.</p>
</li>
<li><p>Send failed messages to a <strong>dead-letter queue (DLQ)</strong> for inspection.</p>
</li>
<li><p>Build <strong>alerting and monitoring</strong> on DLQ metrics to detect recurring issues.</p>
</li>
</ul>
<p>This ensures resilience and prevents message loss.</p>
<h3 id="heading-5-ensure-observability-and-traceability">5. Ensure Observability and Traceability</h3>
<p>Debugging asynchronous flows requires visibility. Embed tracing and correlation data into your events:</p>
<pre><code class="lang-typescript">{
  <span class="hljs-string">"event"</span>: <span class="hljs-string">"payment:processed"</span>,
  <span class="hljs-string">"eventId"</span>: <span class="hljs-string">"9b7f...c0"</span>,
  <span class="hljs-string">"traceId"</span>: <span class="hljs-string">"c74d...d9"</span>,
  <span class="hljs-string">"timestamp"</span>: <span class="hljs-string">"2025-11-03T13:45:00Z"</span>
}
</code></pre>
<p>Integrate with tools like:</p>
<ul>
<li><p>OpenTelemetry for distributed tracing</p>
</li>
<li><p>Jaeger or Zipkin for visualization</p>
</li>
<li><p>Kafka UI, Redpanda Console, or Conduktor for message inspection</p>
</li>
</ul>
<p>This allows you to reconstruct event lifecycles across services, which is critical for debugging, compliance, and performance tuning.</p>
<h3 id="heading-6-use-patterns-for-reliability">6. Use Patterns for Reliability</h3>
<p>Certain design patterns make large-scale event-driven systems more reliable:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Pattern</td><td>Purpose</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Outbox Pattern</strong></td><td>Ensures events are emitted only after DB transactions succeed</td></tr>
<tr>
<td><strong>Saga Pattern</strong></td><td>Coordinates distributed transactions with compensating actions</td></tr>
<tr>
<td><strong>Event Choreography</strong></td><td>Lets services react naturally without central orchestration</td></tr>
<tr>
<td><strong>Event Carried State Transfer</strong></td><td>Includes enough data in events for consumers to act independently</td></tr>
</tbody>
</table>
</div><p>Applying these patterns reduces race conditions and improves data consistency.</p>
<h3 id="heading-7-choose-the-right-broker-for-the-job">7. Choose the Right Broker for the Job</h3>
<p>Different brokers serve different use cases:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Broker</td><td>Strength</td></tr>
</thead>
<tbody>
<tr>
<td><strong>RabbitMQ</strong></td><td>Simple, reliable queues; easy to use for small systems</td></tr>
<tr>
<td><strong>Kafka</strong></td><td>High throughput, event persistence, replayability</td></tr>
<tr>
<td><strong>Redis Streams</strong></td><td>Lightweight, in-memory stream processing</td></tr>
<tr>
<td><strong>NATS / Pulsar</strong></td><td>Low-latency, cloud-native messaging for microservices</td></tr>
</tbody>
</table>
</div><p>Your choice depends on throughput, durability, and delivery guarantees.</p>
<h3 id="heading-8-balance-event-driven-and-request-driven-approaches">8. Balance Event-Driven and Request-Driven Approaches</h3>
<p>Event-driven systems excel in asynchronous workflows, but not everything should be event-driven.</p>
<p>Use <strong>synchronous APIs</strong> for immediate, transactional actions (for example, authentication, user profile lookup). And use <strong>events</strong> for background or decoupled processes (for example, analytics, notifications, async updates).</p>
<p>Combining both models yields the best balance of responsiveness and reliability.</p>
<h3 id="heading-9-educate-and-align-the-team">9. Educate and Align the Team</h3>
<p>Architecture is as much about people as it is about technology. Ensure developers share a common understanding of event naming conventions, schema versioning policies, error handling and retry rules, and ownership of producer and consumer responsibilities.</p>
<p>Without alignment, even the best tools lead to inconsistent, brittle systems.</p>
<h3 id="heading-10-start-small-then-evolve">10. Start Small, Then Evolve</h3>
<p>You don’t need Kafka clusters or event sourcing to begin. Start small:</p>
<ul>
<li><p>Use Node.js <code>EventEmitter</code> or a simple in-memory bus for decoupling modules.</p>
</li>
<li><p>Gradually evolve toward distributed brokers as complexity increases.</p>
</li>
</ul>
<p>The key is incremental adoption – building understanding before scaling infrastructure.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Event-driven architectures fundamentally change how we design software. By focusing on what happens rather than what to do next, systems become more adaptable, reactive, and aligned with real-world processes.</p>
<p>In JavaScript – a language born from events – this paradigm feels especially natural. From browser interactions to Node.js microservices, event-driven thinking unifies the frontend and backend under a single principle: <strong>react to change</strong>.</p>
<p>When used wisely, EDA is not just a design pattern – it’s an architectural mindset that empowers systems to evolve continuously, communicate fluidly, and stay resilient in the face of complexity.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Connect, Read, and Process Sensor Data on Microcontrollers – A Beginner's Guide ]]>
                </title>
                <description>
                    <![CDATA[ In today’s world, computers are ubiquitous and generally serve two primary purposes. The first is general-purpose computing, where they handle a wide range of tasks, including running diverse applications and programs. Examples include laptops, deskt... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/connect-read-process-sensor-data-on-microcontrollers-for-beginners/</link>
                <guid isPermaLink="false">67d45997c9e7f2d42bb1c540</guid>
                
                    <category>
                        <![CDATA[ embedded systems ]]>
                    </category>
                
                    <category>
                        <![CDATA[ microcontroller ]]>
                    </category>
                
                    <category>
                        <![CDATA[ embedded software ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ADC ]]>
                    </category>
                
                    <category>
                        <![CDATA[ I2C ]]>
                    </category>
                
                    <category>
                        <![CDATA[ real-time data processing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Signal Processing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ sensors ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Electronics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ hardware ]]>
                    </category>
                
                    <category>
                        <![CDATA[ electrical engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Soham Banerjee ]]>
                </dc:creator>
                <pubDate>Fri, 14 Mar 2025 16:30:15 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741902732575/fd41a2d5-ed4f-445d-b186-936625837c8d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In today’s world, computers are ubiquitous and generally serve two primary purposes.</p>
<p>The first is general-purpose computing, where they handle a wide range of tasks, including running diverse applications and programs. Examples include laptops, desktops, servers, and supercomputers.</p>
<p>The second is embedded systems, which are specialized computers designed for specific functions. Commonly found in devices such as thermostats, refrigerators, cars, and other smart appliances, they rely on sensors to collect environmental data and execute their tasks efficiently.</p>
<h3 id="heading-the-role-of-sensors"><strong>The Role of Sensors</strong></h3>
<p>Sensors play a critical role in both types of computing. In embedded systems, sensors gather environmental data to help devices like autonomous vehicles, home appliances, and industrial machines perform tasks. In general-purpose computers, sensors primarily monitor internal conditions such as temperature and voltage, ensuring safe operation and preventing issues like overheating or electrical faults.</p>
<p>As Artificial Intelligence (AI) and the Internet of Things (IoT) evolve, sensors have become indispensable for gathering real-world data to support intelligent decision-making. Embedded systems leverage sensors to perceive their environment, transforming raw data into actionable insights that power automation and improve efficiency across industries.</p>
<p>This means that understanding sensor interfacing and designing robust sensor-driven software has become a vital skill for engineers and hobbyists alike.</p>
<p>Whether you're a beginner or experienced engineer, this guide will help you build a solid understanding of sensor interfacing software.</p>
<h2 id="heading-what-youll-learn-and-article-scope"><strong>What You’ll Learn and Article Scope</strong></h2>
<p>In this article, you’ll learn how to connect sensors to microcontrollers (MCUs) and design sensor software pipelines that turn raw data into meaningful, usable information. You’ll also explore practical techniques for processing sensor data accurately and efficiently in embedded systems.</p>
<p>Here’s a breakdown of what we’ll cover:</p>
<ul>
<li><p>What sensors are and how they work – An introduction to sensors, common types, and how sensor pipelines help process sensor data.</p>
</li>
<li><p>Key sensor characteristics – Important parameters like sensitivity, accuracy, precision, range, drift, and response time to help you choose the right sensor for your project.</p>
</li>
<li><p>How to interface sensors with microcontrollers – Hardware connections and communication protocols like SPI, I²C, and GPIO that allow microcontrollers to read sensor data.</p>
</li>
<li><p>Software architecture for sensor data – A high-level overview of the software pipeline that processes sensor data, including drivers, ADC support, scaling, calibration, and post-processing.</p>
</li>
<li><p>Detailed design of pipeline components – A closer look at each step in the pipeline, focusing on scaling raw data, calibrating sensors, and applying filters to clean up noisy signals.</p>
</li>
<li><p>Practical tips for power management – Best practices for handling power efficiently using low-power modes, FIFO buffers, and DMA when working with sensor data in embedded systems.</p>
</li>
</ul>
<p>By the end of this article, you’ll know how to design and implement a complete sensor data pipeline for an embedded system, from reading raw sensor data to preparing it for real-world use in intelligent, connected devices.</p>
<p><strong>Note</strong>: Advanced data processing, high-resolution ADCs, and hardware circuit design for sensors are outside the scope of this article.</p>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>To get the most out of this article, you should have:</p>
<ol>
<li><p>Basic knowledge of microcontrollers: Understanding of common peripherals like ADCs (Analog-to-Digital Converters), SPI (Serial Peripheral Interface), I2C (Inter-Integrated Circuit) and GPIO (General Purpose Input/Output). If you’re new to these protocols, <a target="_blank" href="https://www.parlezvoustech.com/en/comparaison-protocoles-communication-i2c-spi-uart/">this article provides a great overview</a>.</p>
</li>
<li><p>Basic knowledge of electronics: Familiarity with circuits and signals, including analog and digital interfaces.</p>
</li>
<li><p>Programming in C: Familiarity in embedded software development, including driver development.</p>
</li>
<li><p>(Optional) Basic knowledge of sensors: Understanding different types of sensors (like temperature, pressure, motion) is helpful but not required.</p>
</li>
</ol>
<p>Also, this article assumes the following:</p>
<ul>
<li><p>You are working with a microcontroller equipped with the peripherals needed for sensor integration. The details of microcontroller peripherals can be found in a <a target="_blank" href="https://pdf.xab3.ro/manual/reference-manual-for-stm32f405415-stm32f407417-stm32f427437-and-stm32f429439-mcus-100">reference manual for example for an STM32F4</a> series microcontroller will have all the details :</p>
</li>
<li><p>You are familiar with compilers, debuggers, and IDEs used in embedded systems. Some common tools include:</p>
<ul>
<li><p>Compilers: <a target="_blank" href="https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads">GCC</a>, <a target="_blank" href="https://developer.arm.com/documentation/dui0773/l/Introducing-the-Toolchain/Toolchain-overview?lang=en">Clang</a>,</p>
</li>
<li><p>Debuggers: <a target="_blank" href="https://sourceware.org/gdb/">GDB</a>, <a target="_blank" href="https://lldb.llvm.org/use/tutorial.html">LLDB</a></p>
</li>
<li><p>IDEs: <a target="_blank" href="https://code.visualstudio.com">Visual Studio Code</a> (VSCode) is a popular choice, especially with extensions for embedded development and debugging.</p>
</li>
</ul>
</li>
<li><p>You aim to build reliable, sensor-driven embedded systems, capable of collecting and processing real-world data efficiently.</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-a-sensor-and-sensor-pipeline">What is a Sensor and Sensor Pipeline?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sensor-characteristics">Sensor Characteristics</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-interface-with-a-microcontroller">How to Interface with a Microcontroller</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-software-architecture">Software Architecture</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-high-level-overview-of-components">High-Level Overview of Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-accessing-data-from-the-sensor">Accessing Data from the Sensor</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sensor-power-management">Sensor Power Management</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-detailed-design-of-components">Detailed Design of Components</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-sensor-driver">1. Sensor Driver</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-adc-support">2. ADC Support</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-scaling">3. Scaling</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-calibration">4. Calibration</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-data-post-processing">5. Data Post-Processing</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-a-sensor-and-sensor-pipeline"><strong>What is a Sensor and Sensor Pipeline?</strong></h2>
<p>A sensor detects changes in physical properties such as temperature, pressure, or light and converts them into electrical signals that can be measured or interpreted. For example, a thermistor is a type of resistor whose resistance changes with temperature. As the temperature varies, the resistance of the thermistor changes, altering the voltage across it. The system then interprets this voltage change to determine the temperature.</p>
<p>To better understand sensors, consider the natural sensors in the human body: the eyes, ears, skin, nose, and tongue. These natural sensors constantly send signals about the environment to the brain for processing. Different regions of the brain interpret these signals and use the information to drive actions and responses. Just like the brain processes signals from natural sensors, a microcontroller processes signals from electronic sensors using a sensor pipeline.</p>
<p>Sensors come in many types, each designed to detect specific physical properties. Some sensors have a sensing element that changes its properties in response to conditions like heat, light, or pressure. Examples include thermistors, infrared receivers, and photodiodes.</p>
<p>For detecting movement, such as acceleration and rotation, MEMS (Microelectromechanical Systems) sensors—like accelerometers and gyroscopes—are widely used.</p>
<p>To measure distance, sensors like sonars, ultrasonic sensors, and radars are common. These are just a few examples of the many types of sensors available.</p>
<p>Beyond the types of physical properties they detect, sensors also differ in their levels of integration. Some sensors are raw sensors, consisting only of a sensing element and a transducer with simple leads for direct connection to an external circuit.</p>
<p>Others, known as smart sensors, include additional components such as an ADC (analog-to-digital converter) and onboard processing capabilities, enabling them to handle more of the data processing independently.</p>
<p>The choice between a raw sensor and a smart sensor depends on your application requirements, including factors like cost, size, and the processing load on the interfacing microcontroller.</p>
<p>Returning to our human analogy, consider how vision works as a sensor pipeline. When light enters our eyes, photoreceptor cells (rods and cones) in the retina act as sensing elements, converting the light into electrical signals. These signals travel via the optic nerve to the brain’s visual cortex, where they undergo processing to form a recognizable image. The brain then interprets this information and initiates a response, like smiling when you see a beautiful scenery.</p>
<p>Similarly, a sensor pipeline for an embedded system can be defined as shown in the picture below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738828676916/75137176-c9ba-432d-bf44-bb3da093e18d.png" alt="Figure 1: A Sensor Pipeline showing analogue to digital conversion, calibration, filtering, and then processing." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Each of these steps may have different requirements based on the application. Creating a requirements document for the sensor is helpful when selecting the appropriate sensor and configuring the pipeline.</p>
<h2 id="heading-sensor-characteristics"><strong>Sensor Characteristics</strong></h2>
<p>Before you dive into the blocks of the sensor pipeline, let’s review some important characteristics of a sensor.</p>
<h3 id="heading-sensitivity"><strong>Sensitivity</strong></h3>
<p>Sensitivity is the ability of a sensor to detect small changes in the physical property it’s designed to measure.</p>
<p>Sensitivity can vary based on factors like manufacturing processes, cost, and the design of the sensing element.</p>
<p>Sensors designed for a specific property often come in different sensitivity levels, allowing users to select an appropriate sensitivity based on the application requirements.</p>
<h3 id="heading-accuracy"><strong>Accuracy</strong></h3>
<p>Accuracy is the degree to which a sensor’s measurement matches the true value of the physical property it’s measuring. Testing a sensor’s accuracy typically requires comparing its readings to those of a reference instrument.</p>
<p>A sensor may have gain and offset errors—issues that calibration can help correct. Calibration adjusts for these systematic errors, which are often due to manufacturing tolerances or design factors.</p>
<p>Once calibrated, the sensor’s output can be verified against a reference to confirm its accuracy. The required level of accuracy should be determined based on the application’s needs.</p>
<h3 id="heading-precision"><strong>Precision</strong></h3>
<p>Precision refers to the consistency or repeatability of a sensor's measurements, regardless of how close those measurements are to the true value. It indicates the sensor's ability to produce the same output under identical conditions and how finely it can resolve and report values.</p>
<p>For example, if the true temperature of an object is 12.53°C:</p>
<ul>
<li><p>A precise sensor will consistently measure values like 12.52°C, 12.53°C, or 12.54°C, even if those values are slightly offset from the true temperature.</p>
</li>
<li><p>A highly accurate sensor, on the other hand, will measure values close to 12.53°C but may lack precision if those readings vary widely (e.g., 12.50°C, 12.53°C, and 12.56°C).</p>
</li>
</ul>
<p>For applications requiring exact measurements, a sensor with both high accuracy (closeness to the true value) and high precision (low variability) is essential. This is especially important in distinguishing small differences, such as between 12.5°C and 12.53°C.</p>
<p>In contrast, applications with less stringent requirements might use sensors with broader tolerances, such as ±1°C, which are sufficient for general monitoring purposes.</p>
<h3 id="heading-range"><strong>Range</strong></h3>
<p>The range of a sensor refers to the span between the maximum and minimum values of the physical property it can measure while maintaining its specified precision and accuracy. A sensor's operating range may extend beyond its measurement range, but the measurement range defines the limits within which the sensor reliably adheres to its specified sensitivity, accuracy, and response time.</p>
<h3 id="heading-drift"><strong>Drift</strong></h3>
<p>Drift is when a sensor's output changes over time due to conditions like temperature or humidity. Components within the sensor, including the sensing element, may be sensitive to these conditions, leading to gradual shifts in measurements.</p>
<p>For example, many components are affected by temperature and humidity changes, which can alter sensor readings. Also, sensors with internal oscillators may experience time-based drift, impacting accuracy.</p>
<p>Regular calibration with an accurate external reference (such as a precise clock) can help correct for drift and maintain reliable measurements. For certain applications, selecting a sensor with acceptable drift characteristics is crucial.</p>
<h3 id="heading-response-time"><strong>Response Time</strong></h3>
<p>Response time is the duration a sensor takes to detect and reflect a change in the measured physical property. For example, if the temperature rises by 5°C, the response time indicates how long the temperature sensor takes to reflect this change in its output.</p>
<p>Response time depends on the sensor’s design, manufacturing quality, and internal components, such as the ADC (Analog-to-Digital Converter), averaging circuits, and filters within the sensor pipeline.</p>
<p>All the parameters mentioned above are thoroughly documented in the sensor’s data-sheet. In practice, it’s a good idea to create a sensor requirements document for each specific application, detailing these key parameters as a baseline for sensor selection.</p>
<p>Now that you’ve examined the key characteristics of sensors, let’s explore how you can connect them to a microcontroller for real-world applications.</p>
<h2 id="heading-how-to-interface-with-a-microcontroller"><strong>How to Interface with a Microcontroller</strong></h2>
<h3 id="heading-choosing-a-communication-protocol">Choosing a Communication Protocol</h3>
<p>Another essential aspect of sensor requirements is specifying the communication interface between the sensor and the MCU or processor in the system. It’s important to understand how the sensor will be interfaced based on its output signal type and the available pins on the microcontroller.</p>
<p>For instance, certain sensors may connect directly to an analog or digital input pin on a microcontroller. A raw sensor, such as a temperature sensor, typically connects to an analog input pin, which is then read by the microcontroller’s internal ADC (Analog-to-Digital Converter).</p>
<p>In contrast, a digital-output sensor connects to a digital GPIO (General Purpose Input/Output) pin. For instance, speed sensors generate square waves with variable pulse widths to indicate speed. These signals are usually connected to a GPIO pin configured as an external interrupt or timer capture input, allowing the microcontroller to measure pulse width accurately.</p>
<p>A smart sensor, on the other hand, often supports communication protocols like SPI (Serial Peripheral Interface) or I2C (Inter-Integrated Circuit). These interfaces enable the microcontroller to configure the sensor, check its status, and retrieve data through register reads and writes.</p>
<p>Choosing the appropriate communication protocol for interfacing a sensor depends on the available pins in the system and the specific requirements of the application.</p>
<p><strong>Tip</strong>: When working with protocols like I²C or SPI, using tools such as <a target="_blank" href="https://www.saleae.com">Saleae</a> logic analyzers can greatly simplify debugging and validation. Logic analyzers capture and visualize communication signals, and tools like Saleae offer built-in protocol interpreters to help you decode sensor communication in real time. This can be especially helpful when troubleshooting configuration issues, timing problems, or communication errors during sensor interfacing.</p>
<p>Figure 2 below shows an example of a microcontroller connected to 4 sensors having different interfaces.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738828730915/25e62db6-a583-427a-bd77-c61c33990cdf.png" alt="Figure 2: A microcontroller interfacing with different sensors using different communication interfaces." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-determining-power-requirements">Determining Power Requirements</h3>
<p>Power requirements are another key consideration when interfacing a sensor. Sensors may operate at different voltages (for example, 3.3V or 5V), so ensuring the microcontroller can accommodate these levels is essential. Level converters can bridge voltage mismatches, ensuring compatibility between the sensor and microcontroller voltage levels.</p>
<p>Timing and sampling requirements must also be evaluated, especially for sensors generating high-frequency data. Configuring external interrupts on GPIO pins can ensure timely data capture, while techniques like using DMA can streamline data transfer for sensors sampling at high frequencies without CPU involvement.</p>
<p>Now that you’ve learned about communication protocols and hardware connections, let’s focus on designing the software architecture that acquires, processes, and prepares sensor data for use. Designing effective software is crucial for obtaining clean, reliable data from the sensor.</p>
<h2 id="heading-software-architecture"><strong>Software Architecture</strong></h2>
<p>Now that we’ve chosen the sensor and communication protocol, let’s design the software architecture for the sensor pipeline. This software runs on the microcontroller connected to the sensor and processes raw data to make it clean and usable.</p>
<p>While application-level data processing is beyond the scope of this article, let’s focus on interfacing with the sensor and preparing the data for application use.</p>
<p>The sensor processing pipeline can be broken into the following components:</p>
<ol>
<li><p>Sensor Driver</p>
</li>
<li><p>Analog-to-Digital Conversion (ADC) Support</p>
</li>
<li><p>Scaling</p>
</li>
<li><p>Calibration</p>
</li>
<li><p>Data Post-Processing</p>
</li>
</ol>
<p>Let’s examine a high-level overview of these components for both smart and raw sensors.</p>
<h3 id="heading-high-level-overview-of-components"><strong>High-Level Overview of Components</strong></h3>
<ol>
<li><p><strong>Sensor Driver</strong></p>
<ol>
<li><p>Smart sensors: The driver configures the sensor, manages power, and handles read and write operations to the sensor registers over a communication protocol like SPI, I2C.</p>
</li>
<li><p>Raw sensors: The driver may only control GPIOs for power management, as raw sensors typically lack registers.</p>
</li>
</ol>
</li>
<li><p><strong>Analog-to-Digital Conversion (ADC) Support</strong></p>
<ol>
<li><p>Smart sensors: Include an onboard ADC, which is configured through the sensor driver.</p>
</li>
<li><p>Raw sensors: Requires an external ADC, an ADC driver implemented in software to configure the ADC, initiate conversions, and retrieve data.</p>
</li>
</ol>
</li>
<li><p><strong>Scaling</strong>: Scaling is necessary for both smart and raw sensors. It converts digital counts after the analog to digital conversion into meaningful physical quantities using formulas provided in the sensor data sheet. For example, a temperature sensor will use a formula to convert digital counts to degree Celsius.</p>
</li>
<li><p><strong>Calibration</strong>: Once the measured physical quantity is obtained, calibration adjusts the value by applying offsets, gains, or both to correct errors. This process ensures the sensor output aligns with reference values across its entire measurement range. A detailed discussion of the calibration process will follow in the next section.</p>
</li>
<li><p><strong>Data Post-Processing</strong>: Post-processing techniques, such as filtering are applied to improve data quality and reduce noise. Common filters such as low-pass or high-pass filters can remove unwanted frequency components.</p>
</li>
</ol>
<h3 id="heading-accessing-data-from-the-sensor"><strong>Accessing Data from the Sensor</strong></h3>
<p>The method of accessing data depends on the whether it’s a raw sensor or a smart sensor. Smart sensors will have onboard ADCs and FIFOs. Before delving into how data is accessed, it’s important to first understand sampling frequency.</p>
<h4 id="heading-sampling-frequency">Sampling Frequency:</h4>
<p>The frequency of taking a measurement from the sensor must follow the <a target="_blank" href="https://www.allaboutcircuits.com/technical-articles/nyquist-shannon-theorem-understanding-sampled-systems/">Nyquist-Shannon sampling theorem</a>. It states that the sampling rate must be twice the highest frequency component of the signal to be measured to accurately reconstruct the measured data.</p>
<p>The sampling frequency defines how often the sensor captures data, which affects how the data is accessed. Depending on whether the sensor is a raw sensor or a smart sensor, the approach to handling this sampled data varies.</p>
<p><strong>Smart Sensors:</strong></p>
<ol>
<li><p>Data register: The sensor writes sampled data directly into a register based on the set sample frequency updated during setup. The microcontroller reads this data register based on a data conversion completion interrupt.</p>
</li>
<li><p>FIFObBuffer: Some sensors include FIFO (First-In, First-Out) buffers to store multiple data points. When enabled, the FIFO updates at the configured sampling frequency and trigger interrupts when it becomes full or reaches a predefined level.<br> The benefits of FIFO include:</p>
<ol>
<li><p>Power efficiency: The MCU can process data in batches, reducing CPU overhead and allowing it to enter low-power mode during data collection.</p>
</li>
<li><p>Sampling and processing rate matching: FIFO buffers help reconcile differences between the sensor’s sampling rate and the MCU’s data processing rate.</p>
</li>
<li><p>For MCUs with Direct Memory Access (DMA), data transfer from the sensor to MCU memory can occur without CPU intervention, further reducing power consumption.</p>
</li>
</ol>
</li>
</ol>
<p><strong>Raw Sensors:</strong></p>
<p>For raw sensors, the MCU triggers ADC conversions at the sampling frequency, often using a timer interrupt. Data is read upon the ADC conversion complete interrupt, allowing the MCU to sleep during conversions and between samples to save power.</p>
<h3 id="heading-sensor-power-management"><strong>Sensor Power Management</strong></h3>
<p>Power management is critical for energy-sensitive applications. Strategies include:</p>
<ol>
<li><p>Low-power modes: Many sensors support low-power modes configurable through sensor registers.</p>
</li>
<li><p>GPIO-controlled power cycling (Duty-Cycling): For sensors without built-in low-power modes, the microcontroller can toggle the sensor’s power line using a GPIO pin, reducing power consumption further. Figure 3 below shows the diagram of a raw temperature sensor whose power is controlled using a GPIO from the MCU. For example, a temperature sensor in sleep mode can be activated only when temperature readings are required.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739042040654/1f2d4bbd-f15a-417a-9c79-3b93384e95bd.png" alt="Figure 3: Raw Temperature Sensor Interfacing a MCU" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The above techniques ensure efficient use of power while maintaining the required data sampling rate and sensor responsiveness.</p>
<p>With the high-level architecture in mind, we’ll now dive into the detailed design of each pipeline component.</p>
<h2 id="heading-detailed-design-of-components"><strong>Detailed Design of Components</strong></h2>
<p>In this section, you’ll delve into the key components of the sensor pipeline outlined in the Software Architecture section.</p>
<h3 id="heading-1-sensor-driver"><strong>1. Sensor Driver</strong></h3>
<p>The sensor driver is responsible for managing communication, configuration, power, and data acquisition for both smart and raw sensors.</p>
<h4 id="heading-smart-sensor-driver">Smart Sensor Driver:</h4>
<ol>
<li><p>Communication driver: Generic I2C or SPI drivers on the MCU can be adapted using wrapper functions to handle sensor-specific requirements, such as 1-byte, 2-byte, or 4-byte transfers.</p>
</li>
<li><p>Configuration: Typical tasks include setting the sampling rate, configuring interrupts, managing FIFO buffers, and, if needed, clock settings.</p>
</li>
<li><p>Power management: APIs should allow higher software layers to transition sensors between power modes by writing to specific registers or controlling GPIO lines for sensors without built-in power modes.</p>
</li>
</ol>
<h4 id="heading-raw-sensor-driver">Raw Sensor Driver:</h4>
<p>For raw sensors, the driver primarily manages power, often through GPIO-controlled toggling.</p>
<h3 id="heading-2-adc-support"><strong>2. ADC Support</strong></h3>
<p>ADC support is required only for raw sensors. In this article, we’re focusing on SAR ADCs, which are commonly embedded in microcontrollers.</p>
<h4 id="heading-how-sar-adcs-work">How SAR ADCs Work?</h4>
<p>A SAR ADC converts an analog signal to a digital value over multiple clock cycles, with the number of cycles equal to its bit resolution (for example, 10 cycles for a 10-bit ADC).</p>
<h4 id="heading-key-terms-related-to-adcs">Key terms related to ADCs:</h4>
<ol>
<li><p>Reference Voltage (VRef): Represents the maximum voltage the ADC can measure. Analog signals exceeding this limit must be scaled down.</p>
</li>
<li><p>Resolution: Determines the smallest detectable voltage change. For example, a 10-bit ADC with a 3.3V VRef has a resolution of 3.22 mV</p>
</li>
</ol>
<p>$$V_{\text{Res}} = V_{\text{Ref}} /2^{10}$$</p><p>The ADC result is stored in a data register, which can then be scaled to meaningful physical units.</p>
<h3 id="heading-3-scaling"><strong>3. Scaling</strong></h3>
<p>Scaling converts ADC counts into meaningful physical values, such as temperature (°C) or acceleration (g) depending on the sensor type. Sensor datasheets typically provide the necessary formulas or lookup tables.</p>
<p>For example, the method to convert a voltage measured by a raw temperature sensor to temperature value is shown below:</p>
<p>$$V_{\text{Measured}} = Counts_{\text{ADC}} / 2^{10} * V_{\text{Ref}} \quad \text{(Get V_Measured from ADC Counts)}$$</p><p>$$Temperature_{\text{Measured}} = V_{\text{Measured}} * T_{\text{C/mV}} \quad \text{(Get Temperature physical value)}$$</p><p>Similarly, a 3-axis accelerometer maps counts on the X, Y, and Z axes to acceleration values in g or milli-g.</p>
<h3 id="heading-4-calibration"><strong>4. Calibration</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738829686302/bfa643dc-5e01-4b24-b885-b682acdb11cb.png" alt="Figure 4a: Calibration with gain &amp; offset | Figure 4b: Calibration with fixed offset" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The figure above on the left (4a) is showing Calibration with gain and offset, while the figure above on the right (4b) is showing calibration with fixed offset.</p>
<p>$$x_{\text{calibrated}} = Gain * x_{\text{raw}} + Offset \quad \text{(Figure 4a - Linear Calibration)}$$</p><p>$$x_{\text{calibrated}} = x_{\text{raw}} + Offset \quad \text{(Figure 4b - Fixed offset Calibration)}$$</p><p>Calibration ensures the sensor’s output aligns with reference measurements, correcting for errors introduced by design, materials, or manufacturing.</p>
<h4 id="heading-types-of-errors">Types of Errors:</h4>
<ol>
<li><p>Offset error: A constant deviation of the sensor’s output from the true reference value, regardless of input magnitude.</p>
</li>
<li><p>Gain error: A proportional error where the sensor’s output scale deviates from the expected value, causing the output to increase or decrease incorrectly relative to the input.</p>
</li>
</ol>
<h4 id="heading-calibration-methods">Calibration Methods:</h4>
<ol>
<li><p>2/3-Point calibration: This type of calibration may involve either applying a fixed offset to the raw value or applying both gain and offset. Figure 4a illustrates an example of a gain/offset calibration, while Figure 4b depicts offset calibration. In both figures, the y-axis represents the reference value measured by an accurate instrument, while the x-axis represents the raw value measured by the sensor after ADC.</p>
</li>
<li><p>N-Point calibration: Involves multiple points for more complex, non-linear error correction.</p>
</li>
</ol>
<h4 id="heading-implementation">Implementation:</h4>
<ol>
<li><p>Calibration points shall cover the sensor’s entire measurement range for accuracy.</p>
</li>
<li><p>Parameters like gain and offset once estimated shall be stored in a non-volatile memory in the system for persistence to be used across power cycles.</p>
</li>
</ol>
<h3 id="heading-5-data-post-processing"><strong>5. Data Post-Processing</strong></h3>
<p>Post-processing covered in this section talks about removing noise and unwanted signal components, which improves data reliability.</p>
<h4 id="heading-filtering">Filtering</h4>
<p>Filtering is the process of removing unwanted frequency components from a signal to improve data quality. There are several different types of filters:</p>
<ul>
<li><p>Low-Pass Filters: Allows low-frequency signals to pass while attenuating high-frequency noise.</p>
</li>
<li><p>High-Pass Filters: Allows high-frequency signals to pass while attenuating low-frequency noise. (for example, gravitational acceleration in accelerometer data).</p>
</li>
<li><p>Band-Pass Filters: Retains only signals within a specific frequency range, removing both lower and higher frequencies outside the desired band.</p>
</li>
</ul>
<p>These filters are often implemented as FIR (Finite Impulse Response) or IIR (Infinite Impulse Response) filters. IIR filters are easy to implement and computationally efficient while FIR filters are computationally intensive but have better control over the frequency response.</p>
<p>Here, we will explore a simple low-pass filter known as the Exponential Moving Average (EMA), a type of IIR filter. A moving average filter is a mathematical technique that smooths short-term fluctuations while highlighting longer-term trends.</p>
<p>Unlike other moving average filters, EMA does not require maintaining a buffer, making it more memory-efficient. It is also more responsive to data changes while still providing smoothing, making it well-suited for real-time filtering. EMA assigns greater weight to recent data samples than older ones, allowing it to adapt quickly to changes in sensor readings.</p>
<p>EMA can be calculated like this:</p>
<p>$$EMA_{\text{t}} = \alpha * x_{\text{t}} + (1 - \alpha) * EMA_{\text{t - 1}}$$</p><p>$$\alpha = 2 / (N + 1) \quad \text{(Smoothening Factor, N - filter window size)}$$</p><p>$$EMA_{\text{t}} \quad \text{(Exponential Moving Average in current iteration)}$$</p><p>$$x_{\text{t}} \quad \text{(New Data Sample in Current Iteration)}$$</p><p>$$EMA_{\text{t - 1}} \quad \text{(Exponential Moving Average in the last iteration)}$$</p><p>Now that we understand the Exponential Moving Average (EMA) filter, here are two key factors to consider when tuning it for an application:</p>
<ul>
<li><p>Smoothing vs. Responsiveness: A higher smoothing factor (closer to 1, smaller filter window size) gives more weight to recent data, making the filter more responsive to changes but less effective at noise reduction. A lower smoothing factor (closer to 0, larger filter window size) provides better noise reduction but reacts more slowly to data changes.</p>
</li>
<li><p>Application-Specific Tuning: The smoothing factor should be chosen based on the sampling rate, sensor sensitivity, and application requirements. Real-time systems often require a balance between quick responsiveness and stable output.</p>
</li>
</ul>
<p>Here’s a code sample for EMA:</p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdio.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdint.h&gt;</span></span>

<span class="hljs-comment">// Exponential Moving Average (EMA) filter implementation</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> FILTER_WINDOW 5</span>

<span class="hljs-comment">// Function to calculate EMA</span>
<span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">calculateEMA</span><span class="hljs-params">(<span class="hljs-keyword">float</span> ema, <span class="hljs-keyword">float</span> new_value, <span class="hljs-keyword">float</span> alpha)</span> </span>{
    <span class="hljs-keyword">return</span> (alpha * new_value) + (<span class="hljs-number">1</span> - alpha) * ema;
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">float</span> sensorReadings[] = {<span class="hljs-number">26.0</span>, <span class="hljs-number">27.5</span>, <span class="hljs-number">28.2</span>, <span class="hljs-number">27.0</span>, <span class="hljs-number">26.8</span>, <span class="hljs-number">26.5</span>, <span class="hljs-number">27.2</span>};
    <span class="hljs-keyword">int</span> numReadings = <span class="hljs-keyword">sizeof</span>(sensorReadings) / <span class="hljs-keyword">sizeof</span>(sensorReadings[<span class="hljs-number">0</span>]);

    <span class="hljs-keyword">float</span> alpha = <span class="hljs-number">2.0f</span> / (FILTER_WINDOW + <span class="hljs-number">1</span>); <span class="hljs-comment">// Standard EMA formula</span>
    <span class="hljs-keyword">float</span> ema = sensorReadings[<span class="hljs-number">0</span>];  <span class="hljs-comment">// Initialize EMA with the first reading</span>

    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"EMA Filtered Sensor Data:\n"</span>);

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; numReadings; i++) {
        ema = calculateEMA(ema, sensorReadings[i], alpha);
        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Reading %d: Raw = %.2f, EMA = %.2f\n"</span>, i + <span class="hljs-number">1</span>, sensorReadings[i], ema);
    }

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>In summary, sensors are the backbone of modern smart devices, bridging the gap between the physical world and digital systems. From consumer electronics to industrial automation and medical devices, they enable devices to perceive and interact with their environments.</p>
<p>Understanding how sensors work, the components of their data pipeline, and their integration with microcontrollers is essential for engineers and hobbyists alike. By designing effective pipelines, developers can ensure accurate, clean, and reliable data, enabling systems to meet performance and power efficiency goals.</p>
<p>If you have questions or want to talk more about this topic, feel free to reach out on <a target="_blank" href="https://x.com/sohamstars">Twitter</a> or <a target="_blank" href="https://x.com/sohamstars">Lin</a><a target="_blank" href="https://www.linkedin.com/in/sohambanerjee2/">kedIn</a>. Always happy to connect.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Software Design Basics: Key Phases and Best Practices ]]>
                </title>
                <description>
                    <![CDATA[ Coding has become one of the most common tasks in modern society. With computers now central to almost every field, more people are designing algorithms and writing code to solve various problems. From healthcare to finance, robust software systems p... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-software-design-basics/</link>
                <guid isPermaLink="false">67cb6446f54b40e1e9144db0</guid>
                
                    <category>
                        <![CDATA[ software design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TDD (Test-driven development) ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Coding Best Practices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Programming Blogs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Soham Banerjee ]]>
                </dc:creator>
                <pubDate>Fri, 07 Mar 2025 21:25:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741188275855/9858518f-38c0-4e3b-8be1-7c56b68c77a7.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Coding has become one of the most common tasks in modern society. With computers now central to almost every field, more people are designing algorithms and writing code to solve various problems.</p>
<p>From healthcare to finance, robust software systems power our daily operations, making good software design essential to avoid inefficiencies and bottlenecks. This involves not just writing code but also designing systems that are easy to scale, maintain, and debug, while allowing others to contribute effectively.</p>
<p>Inefficient or ineffective software design can lead to significant issues, like scope creep, miscommunication within teams, project delays, resource misallocation, and complex systems that are difficult to maintain or understand. Without a strong design, teams often accumulate technical debt, which hinders long-term progress and increases maintenance costs.</p>
<p>This article will introduce you to key software design elements that will help you and your team address these challenges and guide you in building efficient, scalable systems. By understanding and applying these elements correctly, you can set up a project for both short-term and long-term success.</p>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>I’ll explain these concepts through examples, but a basic understanding of programming in any language is required for this article (knowledge of Python will be especially beneficial).</p>
<h2 id="heading-scope"><strong>Scope</strong></h2>
<p>The article will introduce key software design elements and explain them using an example. While I won’t provide a full software design for the example problem, I will include enough details to effectively illustrate each design element.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-overview-of-key-software-design-elements">Overview of Key Software Design Elements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-a-walkthrough-of-the-software-design-process">A Walkthrough of the Software Design Process</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-problem-statement">Problem Statement</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-cases">Use Cases</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-requirements">Requirements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-high-level-system-architecture">High Level System Architecture</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-detailed-software-design-and-component-breakdown">Detailed Software Design and Component Breakdown</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion-the-value-of-thoughtful-software-design">Conclusion: The Value of Thoughtful Software Design</a></p>
</li>
</ul>
<h2 id="heading-overview-of-key-software-design-elements"><strong>Overview of Key Software Design Elements</strong></h2>
<p>To fully understand the benefits of the software design process, you’ll need to understand some key elements and their scope.</p>
<p>Once you have a good grasp of these, the next step is to define them for the specific problem at hand. Accurately defining these elements reduces risks and simplifies the implementation phase.</p>
<p>Doing this groundwork before implementation helps prevent late discoveries, minimizes the need for rewriting, and makes sure that the design can handle constraints and corner cases.</p>
<p>Now let’s briefly go over the key elements of the software design process:</p>
<ol>
<li><p><strong>Creating a problem statement</strong>: This step involves creating a clear and concise description of the problem that needs to be solved, along with its scope. The scope is essential because it focuses on the exact problem to be addressed and includes assumptions that must be considered during design.</p>
</li>
<li><p><strong>Identifying use cases</strong>: This step outlines all possible user interactions with the software to achieve the desired outcome. It is a critical input to the architecture, as it helps create a design that addresses both general and edge-case use cases.</p>
</li>
<li><p><strong>Stating requirements</strong>: This step defines the expectations of the software, such as its limitations, behaviors, and capabilities for different use cases.</p>
</li>
<li><p><strong>Designing the architecture</strong>: This step provides a high-level structure of the software design, focusing on how to meet the requirements. The architecture typically includes components, how they interact, and how data flows through the system.</p>
</li>
<li><p><strong>Drafting a detailed design</strong>: This step refines the high-level architecture into detailed, component-specific designs, ready for implementation.</p>
</li>
</ol>
<p>In addition to these core elements, there are two important factors you need to consider throughout the design phase.</p>
<p>First, you’ll need to identify and state any assumptions you have. Assumptions can be present at any stage in the design process. Making correct assumptions increases the likelihood of success, improves focus, and reduces complexity in the design.</p>
<p>Second, you’ll need to create good documentation. Documentation is one of the most important elements in the software design process. It’s essential to document each stage as you go along. Documentation serves as the only formal record of the software design and is invaluable for presentations to management, for onboarding new team members, and for anyone returning to the project after a break. It saves valuable time and ensures continuity, as we often overestimate our own memory.</p>
<p>The figure below provides a visual summary of the key software design elements discussed in this section.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738540359869/2ee49614-84b1-439a-ae7e-af637c0f34dd.png?auto=compress,format&amp;format=webp" alt="Figure 1: Key software design elements" width="600" height="400" loading="lazy"></p>
<p>Next, we’ll apply these key software design elements to a practical example, demonstrating how each element contributes to building a robust and scalable system.</p>
<h2 id="heading-a-walkthrough-of-the-software-design-process"><strong>A Walkthrough of the Software Design Process</strong></h2>
<p>In any well-structured software project, clearly defining the problem is the first crucial step before diving into design and implementation. A well-defined problem ensures that the software meets user needs, remains maintainable, and scales effectively over time.</p>
<p>For this walkthrough, we will focus on designing a financial expense categorization system that processes and analyzes transaction data. This system is a part of a larger financial management solution and needs to be easy to debug, maintain, and scale.</p>
<h3 id="heading-problem-statement"><strong>Problem Statement</strong></h3>
<p>The problem statement provides a high-level goal for the software that we’ll design.</p>
<p>For this example, here’s our statement: Design a software solution that categorizes monthly expenses and generates a report from a list of transactions.</p>
<h4 id="heading-define-the-scope"><strong>Define the scope</strong></h4>
<p>Defining the scope clarifies the smaller tasks that must be accomplished to meet the high-level goal. It outlines the focus of the software design and includes some assumptions.</p>
<p>Includes:</p>
<ol>
<li><p>Implementing a parser to process a list of transactions provided as input.</p>
</li>
<li><p>Filtering transactions for a given month.</p>
</li>
<li><p>Analyzing, categorizing, and generating a report for each expense category.</p>
</li>
</ol>
<p>Excludes:</p>
<p>Performance and memory optimization (excluded due to the limited scope of this article). While performance and memory optimizations are not the primary focus here, it’s important to keep future scalability in mind. Small design choices made now, such as selecting data structures, can help avoid significant refactoring later when the system grows.</p>
<p>Assumptions:</p>
<ol>
<li><p>The list of transactions will be provided as a CSV file in the following format:<br> Columns: "Date, Description, Amount, Type, Category Label".</p>
</li>
<li><p>Expense categories will be provided as input through a JSON file.</p>
</li>
<li><p>The software will run in a shell environment, and inputs will be taken as command-line arguments.</p>
</li>
</ol>
<p>Now that the scope is clear, let’s examine how users will interact with the system through various use cases.</p>
<h3 id="heading-use-cases"><strong>Use Cases</strong></h3>
<p>Use cases define how users will interact with the system to accomplish specific goals. Identifying accurate and valid use cases is critical to creating comprehensive requirements. Failing to capture enough use cases can lead to a design that is incomplete and lacks robustness. This may result in the need for redesigns, which increases time and resource consumption.</p>
<p>On the other hand, identifying too many use cases without considering their feasibility can lead to overly complex designs that are difficult to maintain and implement in the short term.</p>
<p>For our specific problem, the user will need to provide the following inputs while running the software in a shell:</p>
<ol>
<li><p>A CSV file containing a list of transactions.</p>
</li>
<li><p>A month number.</p>
</li>
<li><p>A JSON file containing expense categories.</p>
</li>
</ol>
<p>We need to consider all possible ways the user can interact with the script to achieve the desired outcome. For each of the three inputs, there are two possibilities: valid input or invalid input. This gives us 8 potential use cases (2 possibilities per input: valid and invalid). It's important to define what constitutes valid and invalid inputs for this problem:</p>
<ul>
<li><p>CSV File: Valid if it is in the format described in Assumption 1 (columns: "Date, Description, Amount, Type, Category Label").</p>
</li>
<li><p>Month Number: Valid if the value is between 1 and 12.</p>
</li>
<li><p>JSON File: Valid if it contains expense categories in the correct JSON format.</p>
</li>
</ul>
<p>An input is invalid if it doesn't meet these definitions or if the input is absent.</p>
<p>It’s also crucial to consider the correlation between inputs when evaluating the feasibility of certain use cases, as they may interact with each other in unforeseen ways. Based on these use cases, we can now define the specific requirements that the system must meet.</p>
<h3 id="heading-requirements"><strong>Requirements</strong></h3>
<p>Now, let’s define the expected behaviors, limitations, and capabilities for each use case. Requirements serve as the foundation for architecture, specifications, and implementation. Based on our problem statement, the software will need to accomplish the following tasks:</p>
<ol>
<li><p>The script shall take three inputs: a CSV file of transactions, a month number, and a JSON file of expense categories.</p>
</li>
<li><p>The script shall verify all inputs.</p>
</li>
<li><p>The script shall throw an error and exit if the CSV file cannot be opened or if it does not match the format in Assumption 1.</p>
</li>
<li><p>The script shall throw an error and exit if the JSON file cannot be opened.</p>
</li>
<li><p>The script shall throw an error if the month number is not between 1 and 12.</p>
</li>
<li><p>The script shall parse each transaction and load it into a data structure.</p>
</li>
<li><p>The script shall filter transactions by the specified month.</p>
</li>
<li><p>The script shall load the expense categories from the JSON file into a data structure.</p>
</li>
<li><p>The script shall categorize transactions based on the category label provided in the CSV file.</p>
</li>
<li><p>The script shall throw an exception if a category label in the CSV file is not present in the expense categories.</p>
</li>
<li><p>The script shall use a categorizing function to assign transactions to categories from the JSON file.</p>
</li>
<li><p>A class shall encapsulate categorized transactions, providing APIs to modify or access them.</p>
</li>
<li><p>The script shall support statistics calculation and report generation for categorized transactions.</p>
</li>
</ol>
<p>With the requirements in place, we can now design a high-level architecture to meet those needs.</p>
<h3 id="heading-high-level-system-architecture"><strong>High Level System Architecture</strong></h3>
<p>In this stage, we will design the system at a high level, much like creating a master plan. Architecture involves organizing the software's functions into distinct components, illustrating how they interact, and mapping the flow of control and data through the system. While designing the architecture in this tutorial, we’ll incorporate good design principles.</p>
<p>For this example, the high-level requirements include:</p>
<ol>
<li><p>Loading inputs and verifying them.</p>
</li>
<li><p>Applying time-based filtering.</p>
</li>
<li><p>Categorizing transactions based on category labels and descriptions.</p>
</li>
<li><p>Managing categorized transactions in a finance registry.</p>
</li>
<li><p>Generating reports from the categorized data.</p>
</li>
</ol>
<p>One important component of software architecture is telemetry. Telemetry gathers data on the software's behavior, which is invaluable for debugging and performance assessment in real-world environments.</p>
<p>For smaller systems, simpler logging mechanisms may be sufficient to track basic errors and monitor performance. The decision to implement telemetry should depend on the complexity of the system and operational requirements.</p>
<p>Since telemetry provides such a helpful feedback loop for improving the design in future iterations, we’ll add it to the list of components here.</p>
<p>We’ll build our system architecture around a Test-Driven Development (TDD) approach. We’ll design each component with testing in mind to ensure it meets our requirements.</p>
<p>Just keep in mind that while TDD is a strong practice for ensuring code quality, it may not be the best fit for all projects. In scenarios where you need rapid prototyping or exploratory development, testing might be prioritized after initial iterations. Balancing between TDD and other methodologies depends on the project context and team preferences.</p>
<p>Our architecture will follow a modular structure, meaning the system will be divided into self-contained components. Each component will be responsible for specific functionality, making the system easier to test, maintain, and scale.</p>
<p>To achieve this, the architecture will emphasize loose coupling between components. Each component will interact with others through well-defined interfaces or APIs, ensuring minimal dependencies. We’ll abstract and encapsulate internal implementation details, exposing only the necessary information for interaction. Also, each component will handle its own errors and exceptions to ensure robustness and fault isolation.</p>
<p>But it is also important to consider a centralized error-handling strategy in some cases. Centralizing error handling can reduce redundancy, improve consistency, and make maintenance easier. The choice between local and centralized error handling should depend on the system's complexity and how components interact. This will contribute to the overall scalability and maintainability of the system.</p>
<p>Below is a summary of each component's functionality in this architecture:</p>
<ul>
<li><p>Load and verify input: This component will take the CSV file, JSON file, and month number as input, verify their validity, and load the data into structures.</p>
</li>
<li><p>Time-based filter: This component will filter transactions based on the input month and store the filtered transactions in a data structure.</p>
</li>
<li><p>Label-based categorization: This component will categorize transactions based on the category label in the CSV file.</p>
</li>
<li><p>Description-based categorization: This component will categorize transactions using an algorithm based on the transaction description.</p>
</li>
<li><p>Finance registry: This component will store all categorized transactions for further processing. It isolates the post-processing of categorized transactions from the categorization process and provides methods for updating or retrieving datasets.</p>
</li>
<li><p>Report generation: This component will generate expense reports from the categorized transaction data.</p>
</li>
<li><p>Telemetry: This component will monitor the performance of other components. It will track the flow of transactions, ensuring that all transactions are categorized either by label or description. Additional parameters can be added as needed to monitor specific functionalities.</p>
</li>
</ul>
<p>The diagram below demonstrates the flow of data through these components:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738540585066/6236b867-8c57-4a04-b5ea-4f9dd7f1fef3.png?auto=compress,format&amp;format=webp" alt="Figure 2: Flow of data through various components defined in the architecture" width="600" height="400" loading="lazy"></p>
<h3 id="heading-detailed-software-design-and-component-breakdown"><strong>Detailed Software Design and Component Breakdown</strong></h3>
<p>While we won't cover the full system design, this section will highlight key components and their specifications. For this example, I will assume the role of both the designer and implementer of the software.</p>
<p>Software design and specifications depend on several factors, including the designer's knowledge, skill set, available time, and resources. We’ll define some of the design details for the system, starting with the choice of the implementation language.</p>
<p>Choosing the right language is based on several important factors:</p>
<ol>
<li><p>The language must meet the software requirements.</p>
</li>
<li><p>It should be stable, and have strong support from an active developer community.</p>
</li>
<li><p>Additional considerations include performance (speed and memory), scalability (ability to grow with future requirements), and platform support (ability to run on all major operating systems).</p>
</li>
</ol>
<p>If you’re the one implementing this design, you’ll need to be familiar with and confident using that programming language. For this project, I chose Python because it meets all the project requirements, has a robust developer community for support, it’s stable, and I’m confident in using it to complete the implementation successfully.</p>
<h4 id="heading-data-structures"><strong>Data Structures</strong></h4>
<p>Now, let’s look at the fundamental data structures that we’ll use in the design. We need to load the contents of the CSV file into a data structure for further analysis and processing. In Python, the Pandas DataFrame from the Pandas library is ideal for analyzing and processing tables, so we will use it to store the transactions.</p>
<p>For generating report, we will encapsulate categorized transactions along with relevant statistics, such as the total number of transactions, mean amount, and maximum amount, within a dedicated dataset class. This approach ensures a clear separation of concerns, where the dataset class manages data processing, while the reporting component focuses on presentation.</p>
<p>By structuring the system this way, we enhance reusability, maintainability, and scalability, making it easier to extend and modify in the future.</p>
<p>This dataset class will include:</p>
<ul>
<li><p>Member variables: category name, category description, a Pandas DataFrame for transactions, total number of transactions, mean amount, and max amount of transactions.</p>
</li>
<li><p>Member functions: set/get DataFrame, save dataset to CSV (useful for debugging).</p>
</li>
</ul>
<p>Here’s an example of a Dataset class in Python for structured data management and processing:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd  <span class="hljs-comment"># Import Pandas for data handling</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Dataset</span>:</span>
    <span class="hljs-string">"""
    A class representing a structured dataset with a name, predefined keys, 
    and a Pandas DataFrame.
    """</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, name, keys</span>):</span>
        <span class="hljs-string">"""
        Initializes the Dataset object.

        Parameters:
        name (str): The name of the dataset.
        keys (list): A list of expected column names for the dataset.

        Attributes:
        self.name (str): Stores the dataset name as a string.
        self.keys (list): Stores the expected column names for data organization.
        self.mean_amt (float): Tracks the mean (average) transaction amount.
        self.max_amt (float): Tracks the maximum transaction amount.
        self.count (int): Stores the total number of transactions in the dataset.
        self.dataframe (pd.DataFrame): A Pandas DataFrame initialized with the specified column names.
        """</span>
        self.name = str(name)  <span class="hljs-comment"># Convert and store dataset name as a string</span>
        self.keys = keys  <span class="hljs-comment"># Store expected column names for consistency</span>
        self.mean_amt = <span class="hljs-number">0</span>  <span class="hljs-comment"># Initialize mean transaction amount to zero</span>
        self.max_amt = <span class="hljs-number">0</span>  <span class="hljs-comment"># Initialize max transaction amount to zero</span>
        self.count = <span class="hljs-number">0</span>  <span class="hljs-comment"># Initialize transaction count to zero</span>
        self.dataframe = pd.DataFrame(columns=keys)  <span class="hljs-comment"># Initialize empty DataFrame with predefined columns</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">getName</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Returns the name of the dataset.

        Returns:
        str: The name of the dataset.
        """</span>
        <span class="hljs-keyword">return</span> self.name  <span class="hljs-comment"># Fixed: Removed incorrect parentheses</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">getValue</span>(<span class="hljs-params">self, key</span>):</span>
        <span class="hljs-string">"""
        Retrieves a specific column from the DataFrame.

        Parameters:
        key (str): The column name to retrieve.

        Returns:
        pandas.Series or None: The column data if the key exists, otherwise None.
        """</span>
        <span class="hljs-keyword">if</span> key <span class="hljs-keyword">in</span> self.dataframe.columns:
            <span class="hljs-keyword">return</span> self.dataframe[key]
        <span class="hljs-keyword">else</span>:
            print(<span class="hljs-string">f"Warning: Key '<span class="hljs-subst">{key}</span>' not found in DataFrame."</span>)
            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>  <span class="hljs-comment"># Prevents KeyError</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">getKeys</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Returns the list of expected keys (column names) of the dataset.

        Returns:
        list: The keys defining the dataset.
        """</span>
        <span class="hljs-keyword">return</span> self.keys

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">setDataFrame</span>(<span class="hljs-params">self, dataframe</span>):</span>
        <span class="hljs-string">"""
        Sets the dataset's DataFrame while ensuring it contains only expected keys.

        Parameters:
        dataframe (pandas.DataFrame): The DataFrame to assign to the dataset.
        """</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> isinstance(dataframe, pd.DataFrame):
            <span class="hljs-keyword">raise</span> TypeError(<span class="hljs-string">"Provided data is not a valid pandas DataFrame."</span>)

        <span class="hljs-comment"># Ensure only the expected columns are included</span>
        self.dataframe = dataframe[self.keys].copy() <span class="hljs-keyword">if</span> set(self.keys).issubset(dataframe.columns) <span class="hljs-keyword">else</span> dataframe.copy()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">getDataFrame</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Returns the DataFrame associated with the dataset.

        Returns:
        pandas.DataFrame: The dataset's DataFrame.
        """</span>
        <span class="hljs-keyword">return</span> self.dataframe

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">save_to_csv</span>(<span class="hljs-params">self, file_name</span>):</span>
        <span class="hljs-string">"""
        Saves the dataset's DataFrame to a CSV file.

        Parameters:
        file_name (str): The name of the CSV file to save.
        """</span>
        self.dataframe.to_csv(file_name, mode=<span class="hljs-string">'w'</span>, index=<span class="hljs-literal">False</span>)  <span class="hljs-comment"># Save the DataFrame to CSV</span>
</code></pre>
<p>In the previous section, we outlined the high-level system architecture, detailing the core components and their interactions. Now, let’s dive into the detailed design of some of the individual components, specifying how we’ll implement each one and how it’ll function within the system. We’ll also break down the components to explain how they work together to process the input and generate the report.</p>
<p>Below, you can see the flow diagram for the software, illustrating the interaction between the core components and the flow of data through the system.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739209441033/60142953-c1f4-4146-b64e-c042039e1ef6.png?auto=compress,format&amp;format=webp" alt="Figure 3 Software Flow Diagram" width="600" height="400" loading="lazy"></p>
<h4 id="heading-category-label-based-filtering-component"><strong>Category Label-Based Filtering Component</strong></h4>
<p>The Category Label-Based Filtering Component classifies transactions by matching their "Category Label" with predefined expense categories from a JSON file. Transactions with valid category labels are stored in the finance registry, while unmatched ones remain for further processing.</p>
<ul>
<li><p>Input: DataFrame of time-filtered transactions, expense categories from JSON.</p>
</li>
<li><p>Libraries used: Pandas DataFrame.</p>
</li>
<li><p>Software design: Filters transactions based on the "Category Label" column and assigns them to corresponding categories. Transactions that cannot be categorized remain for further processing.</p>
</li>
<li><p>Output: DataFrame of remaining transactions with empty values in the "Category Label" field.</p>
</li>
<li><p>Component tests: Validate handling of valid, invalid, and missing category labels.</p>
</li>
</ul>
<h4 id="heading-finance-registry-component"><strong>Finance Registry Component</strong></h4>
<p>The Finance Registry Component manages categorized transactions by storing them as datasets for each expense category. It maintains a structured collection of DataFrames, each containing transactions and summary statistics such as total count, max amount, and mean amount.</p>
<ul>
<li><p>Input: Expense categories from JSON.</p>
</li>
<li><p>Libraries used: Pandas DataFrame.</p>
</li>
<li><p>Software design: Implements a class that organizes datasets for all expense categories, providing methods to set and retrieve DataFrames.</p>
</li>
<li><p>Component tests: Validate dataset creation, ensuring correct storage and retrieval of categorized transactions.</p>
</li>
</ul>
<p>Here’s a simple and efficient Finance Registry implementation in Python for managing categorized financial datasets:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> Dataset <span class="hljs-keyword">import</span> Dataset
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd  <span class="hljs-comment"># Ensure Pandas is imported if used elsewhere</span>

<span class="hljs-comment"># Define column structure for datasets</span>
KEYS = (<span class="hljs-string">"Date"</span>, <span class="hljs-string">"Description"</span>, <span class="hljs-string">"Amount"</span>, <span class="hljs-string">"Transaction Type"</span>, <span class="hljs-string">"Category"</span>, <span class="hljs-string">"Account Name"</span>, <span class="hljs-string">"Labels"</span>, <span class="hljs-string">"Notes"</span>)

<span class="hljs-comment"># Define dataset names for different financial categories</span>
EXAMPLE_DATASET_NAMES = (<span class="hljs-string">"Investment"</span>, <span class="hljs-string">"Expense"</span>, <span class="hljs-string">"Savings"</span>)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FinanceRegistry</span>:</span>
    <span class="hljs-string">"""
    A class to manage categorized financial datasets, including investment, expense, and savings datasets.
    This registry allows structured access to transaction data and maintains aggregated financial metrics.
    """</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Initializes the FinanceRegistry object.

        Attributes:
        self.example_dataset (dict): A dictionary storing Dataset objects for financial datasets.
        """</span>
        self.example_dataset = {name: Dataset(name, KEYS) <span class="hljs-keyword">for</span> name <span class="hljs-keyword">in</span> EXAMPLE_DATASET_NAMES}  <span class="hljs-comment"># Create datasets for categories</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">setExampleDatasetToRegistry</span>(<span class="hljs-params">self, name, dataframe</span>):</span>
        <span class="hljs-string">"""
        Merges a new dataframe into the existing dataset for a given financial category.

        Parameters:
        name (str): The category name (e.g., "Investment", "Expense", or "Savings").
        dataframe (pd.DataFrame): The new data to be added.

        If the dataset already contains data, it concatenates the new dataframe to the existing one.

        Raises:
        ValueError: If the provided name is not a valid dataset category.
        """</span>
        <span class="hljs-keyword">if</span> name <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> self.example_dataset:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Invalid dataset name: '<span class="hljs-subst">{name}</span>'. Expected one of <span class="hljs-subst">{EXAMPLE_DATASET_NAMES}</span>"</span>)

        df = self.example_dataset[name].getDataFrame()  <span class="hljs-comment"># Get existing dataset</span>

        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> dataframe.empty:  <span class="hljs-comment"># Ensure the new dataframe is not empty</span>
            dataframe = pd.concat([df, dataframe], axis=<span class="hljs-number">0</span>, ignore_index=<span class="hljs-literal">True</span>)  <span class="hljs-comment"># Append new data</span>

        self.example_dataset[name].setDataFrame(dataframe)  <span class="hljs-comment"># Update dataset in registry</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">getExampleDatasetFromRegistry</span>(<span class="hljs-params">self, name</span>):</span>
        <span class="hljs-string">"""
        Retrieves the dataset for a given financial category.

        Parameters:
        name (str): The category name (e.g., "Investment", "Expense", or "Savings").

        Returns:
        Dataset: The dataset corresponding to the given name.

        Raises:
        ValueError: If the provided name is not a valid dataset category.
        """</span>
        <span class="hljs-keyword">if</span> name <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> self.example_dataset:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Invalid dataset name: '<span class="hljs-subst">{name}</span>'. Expected one of <span class="hljs-subst">{EXAMPLE_DATASET_NAMES}</span>"</span>)

        <span class="hljs-keyword">return</span> self.example_dataset[name]
</code></pre>
<p>The diagram below illustrates how the Finance Registry organizes these datasets for further processing in the Report Generation component.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739209411075/7a772e4f-9687-4c96-8995-10a70e27a36d.png?auto=compress,format&amp;format=webp" alt="Figure 4 Finance Registry datasets for expense category" width="600" height="400" loading="lazy"></p>
<h4 id="heading-report-generation-component"><strong>Report Generation Component</strong></h4>
<p>The Report Generation Component processes categorized transaction datasets from the finance registry and generates summary statistics. It calculates key financial metrics such as maximum amount, mean amount, and total transaction count. It also provides functionality to display categorized transactions in a structured format within the shell.</p>
<ul>
<li><p>Input: Datasets of categorized transactions from the finance registry.</p>
</li>
<li><p>Libraries used: Numpy for calculations, Tabulate for formatted shell output (if needed).</p>
</li>
<li><p>Software design: Implements a class with methods to compute financial statistics and display transaction summaries per expense category.</p>
</li>
<li><p>Component tests: Validate correct calculation of mean, max, and total transactions, and ensure accurate display of categorized datasets in the shell.</p>
</li>
</ul>
<p>Here’s a function to compute transaction statistics, including mean, max, and count, from a dataset in the report generation component:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> Dataset <span class="hljs-keyword">import</span> Dataset
<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">calculateStats</span>(<span class="hljs-params">dataset</span>):</span>
    <span class="hljs-string">"""
    Computes statistical metrics for a given dataset.

    Parameters:
    dataset: The dataset containing transaction data.

    Updates:
    - dataset.mean: Mean transaction amount.
    - dataset.max: Maximum transaction amount.
    - dataset.count: Number of transactions.
    """</span>

    <span class="hljs-comment"># Return early if the dataset has no transactions</span>
    <span class="hljs-keyword">if</span> dataset.dataframe.empty:
        <span class="hljs-keyword">return</span>

    <span class="hljs-comment"># Extract transaction amounts as a list</span>
    tx_amount_list = dataset.dataframe[<span class="hljs-string">'Amount'</span>].astype(float).round(<span class="hljs-number">2</span>).tolist()

    <span class="hljs-comment"># Adjust transaction amounts based on "Transaction Type"</span>
    <span class="hljs-keyword">for</span> i, tx_type <span class="hljs-keyword">in</span> enumerate(dataset.dataframe[<span class="hljs-string">'Transaction Type'</span>]):
        <span class="hljs-keyword">if</span> tx_type == <span class="hljs-string">'debit'</span>:
            tx_amount_list[i] *= <span class="hljs-number">-1</span>  <span class="hljs-comment"># Convert debit transactions to negative values</span>

    <span class="hljs-comment"># Compute statistical metrics</span>
    dataset.mean = round(np.mean(tx_amount_list), <span class="hljs-number">2</span>)
    dataset.max = max(tx_amount_list)
    dataset.count = len(tx_amount_list)
</code></pre>
<p>This concludes the design section, where we explored key software design elements with a practical example. The next step, implementation, is beyond the scope of this article. But it's crucial to recognize that new challenges often emerge during development, requiring updates to requirements, architecture, and specifications.</p>
<p>The purpose of this article is not to provide a full implementation, but to teach you some basic software design principles through an example. The focus is on understanding how to structure software, define clear requirements, and create scalable architectures, all before writing code.</p>
<p>By following a structured design process, you can shift complex problem-solving from implementation to the architecture phase, where you can explore solutions more effectively using flowcharts, block diagrams, and documentation. This makes the development process more organized, efficient, and maintainable, a crucial skill for real-world software engineering.</p>
<p>If you're learning to code, remember that good design is just as important as writing code itself!</p>
<h2 id="heading-conclusion-the-value-of-thoughtful-software-design"><strong>Conclusion: The Value of Thoughtful Software Design</strong></h2>
<p>With well-defined problem statements, scope, requirements, specifications, and design, even complex problems can be solved and maintained in a sustainable way.</p>
<p>The steps we went through in this article can help you break down any problem, regardless of its complexity, into smaller, actionable tasks that you and your team can efficiently tackle.</p>
<p>Without proper planning, projects are often plagued by scope creep, wasted time and resources, miscommunication between teams, overly complicated designs, technical debt, and frequent redesigns.<br>Good design is often simple design, but achieving simplicity is difficult without thorough planning.</p>
<p>Approaching each problem with the mindset of defining a Problem Statement, Scope, Use Cases, Requirements, Architecture, and Specifications helps cultivate a strong software design mindset. This mindset is crucial for developing software that is scalable, maintainable, and high quality.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Multi-Module Projects in Spring Boot for Scalable Microservices ]]>
                </title>
                <description>
                    <![CDATA[ As software applications grow in complexity, managing scalability, modularity, and clarity becomes essential. Spring Boot’s multi-module structure allows you to manage different parts of the application independently, which lets your team develop, te... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-multi-module-projects-in-spring-boot-for-scalable-microservices/</link>
                <guid isPermaLink="false">6733855c0e235bf7a79c5c4f</guid>
                
                    <category>
                        <![CDATA[ Spring Boot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Java ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Microservices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ maven ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Backend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ scalability ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Birkaran Sachdev ]]>
                </dc:creator>
                <pubDate>Tue, 12 Nov 2024 16:42:04 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/uyfohHiTxho/upload/716c6610c336976df67b833912170336.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>As software applications grow in complexity, managing scalability, modularity, and clarity becomes essential.</p>
<p>Spring Boot’s multi-module structure allows you to manage different parts of the application independently, which lets your team develop, test, and deploy components separately. This structure keeps code organized and modular, making it useful for both microservices and large monolithic systems.</p>
<p>In this tutorial, you’ll build a multi-module Spring Boot project, with each module dedicated to a specific responsibility. You’ll learn how to set up modules, configure inter-module communication, handle errors, implement JWT-based security, and deploy using Docker.</p>
<p><strong>Prerequisites</strong>:</p>
<ul>
<li><p>Basic knowledge of Spring Boot and Maven.</p>
</li>
<li><p>Familiarity with Docker and CI/CD concepts (optional but helpful).</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-1-why-multi-module-projects">Why Multi-Module Projects?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-project-structure-and-architecture">Project Structure and Architecture</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-how-to-set-up-the-parent-project">How to Set Up the Parent Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-how-to-create-the-modules">How to Create the Modules</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-inter-module-communication">Inter-Module Communication</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-6-common-pitfalls-and-solutions">Common Pitfalls and Solutions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-7-testing-strategy">Testing Strategy</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-8-error-handling-and-logging">Error Handling and Logging</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-9-security-and-jwt-integration">Security and JWT Integration</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-10-deployment-with-docker-and-cicd">Deployment with Docker and CI/CD</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-11-best-practices-and-advanced-use-cases">Best Practices and Advanced Use Cases</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-12-conclusion-and-key-takeaways">Conclusion and Key Takeaways</a></p>
</li>
</ol>
<h2 id="heading-1-why-multi-module-projects">1. Why Multi-Module Projects?</h2>
<p>In single-module projects, components are often tightly coupled, making it difficult to scale and manage complex codebases. A multi-module structure offers several advantages:</p>
<ul>
<li><p><strong>Modularity</strong>: Each module is dedicated to a specific task, such as User Management or Inventory, simplifying management and troubleshooting.</p>
</li>
<li><p><strong>Team Scalability</strong>: Teams can work independently on different modules, minimizing conflicts and enhancing productivity.</p>
</li>
<li><p><strong>Flexible Deployment</strong>: Modules can be deployed or updated independently, which is particularly beneficial for microservices or large applications with numerous features.</p>
</li>
</ul>
<h3 id="heading-real-world-example"><strong>Real-World Example</strong></h3>
<p>Consider a large e-commerce application. Its architecture can be divided into distinct modules:</p>
<ul>
<li><p><strong>Customer Management</strong>: Responsible for handling customer profiles, preferences, and authentication.</p>
</li>
<li><p><strong>Product Management</strong>: Focuses on managing product details, stock, and pricing.</p>
</li>
<li><p><strong>Order Processing</strong>: Manages orders, payments, and order tracking.</p>
</li>
<li><p><strong>Inventory Management</strong>: Oversees stock levels and supplier orders.</p>
</li>
</ul>
<h3 id="heading-case-study-netflix"><strong>Case Study: Netflix</strong></h3>
<p>To illustrate these benefits, let's examine how Netflix employs a multi-module architecture.</p>
<p>Netflix is a leading example of a company that effectively uses this approach through its microservices architecture. Each microservice at Netflix is dedicated to a specific function, such as user authentication, content recommendations, or streaming services.</p>
<p>This modular structure enables Netflix to scale its operations efficiently, deploy updates independently, and maintain high availability and performance. By decoupling services, Netflix can manage millions of users and deliver content seamlessly worldwide, ensuring a robust and flexible system that supports its vast and dynamic platform.</p>
<p>This architecture not only enhances scalability but also improves fault isolation, allowing Netflix to innovate rapidly and respond effectively to user demands.</p>
<h2 id="heading-2-project-structure-and-architecture">2. Project Structure and Architecture</h2>
<p>Now let’s get back to our example project. Your multi-module Spring Boot project will use five key modules. Here’s the layout:</p>
<pre><code class="lang-plaintext">codespring-boot-multi-module/
 ├── common/               # Shared utilities and constants
 ├── domain/               # Domain entities
 ├── repository/           # Data access layer (DAL)
 ├── service/              # Business logic
 └── web/                  # Main Spring Boot application and controllers
</code></pre>
<p>Each module has a specific role:</p>
<ul>
<li><p><code>common</code>: Stores shared utilities, constants, and configuration files used across other modules.</p>
</li>
<li><p><code>domain</code>: Contains data models for your application.</p>
</li>
<li><p><code>repository</code>: Manages database operations.</p>
</li>
<li><p><code>service</code>: Encapsulates business logic.</p>
</li>
<li><p><code>web</code>: Defines REST API endpoints and serves as the application’s entry point.</p>
</li>
</ul>
<p>This structure aligns with <strong>separation of concerns</strong> principles, where each layer is independent and handles its own logic.</p>
<p>The diagram below illustrates the various modules:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730873719792/adfc3689-26ae-477a-9850-75070a777e5e.png" alt="Diagram showing a software architecture with five modules: Web, Service, Repository, Domain, and Common, connected by arrows indicating relationships." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-3-how-to-set-up-the-parent-project">3. How to Set Up the Parent Project</h2>
<h3 id="heading-step-1-create-the-root-project">Step 1: Create the Root Project</h3>
<p>Let’s run these commands to create the Maven parent project:</p>
<pre><code class="lang-bash">mvn archetype:generate -DgroupId=com.example -DartifactId=spring-boot-multi-module -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=<span class="hljs-literal">false</span>
<span class="hljs-built_in">cd</span> spring-boot-multi-module
</code></pre>
<h3 id="heading-step-2-configure-the-parent-pomxml">Step 2: Configure the Parent <code>pom.xml</code></h3>
<p>In the <code>pom.xml</code>, let’s define our dependencies and modules:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0"</span> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0 http://www.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-multi-module<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.0-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">packaging</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">packaging</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">modules</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">module</span>&gt;</span>common<span class="hljs-tag">&lt;/<span class="hljs-name">module</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">module</span>&gt;</span>domain<span class="hljs-tag">&lt;/<span class="hljs-name">module</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">module</span>&gt;</span>repository<span class="hljs-tag">&lt;/<span class="hljs-name">module</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">module</span>&gt;</span>service<span class="hljs-tag">&lt;/<span class="hljs-name">module</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">module</span>&gt;</span>web<span class="hljs-tag">&lt;/<span class="hljs-name">module</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">modules</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">java.version</span>&gt;</span>11<span class="hljs-tag">&lt;/<span class="hljs-name">java.version</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">spring.boot.version</span>&gt;</span>2.5.4<span class="hljs-tag">&lt;/<span class="hljs-name">spring.boot.version</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dependencyManagement</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-dependencies<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>${spring.boot.version}<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>import<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencyManagement</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span>
</code></pre>
<p>This <code>pom.xml</code> file centralizes dependencies and configurations, making it easier to manage shared settings across modules.</p>
<h2 id="heading-4-how-to-create-the-modules">4. How to Create the Modules</h2>
<h3 id="heading-common-module">Common Module</h3>
<p>Let’s create a <strong>common</strong> module to define shared utilities like date formatters. Create this module and add a sample utility class:</p>
<pre><code class="lang-bash">mvn archetype:generate -DgroupId=com.example.common -DartifactId=common -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=<span class="hljs-literal">false</span>
</code></pre>
<p><strong>Date Formatter Utility:</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.example.common;

<span class="hljs-keyword">import</span> java.time.LocalDate;
<span class="hljs-keyword">import</span> java.time.format.DateTimeFormatter;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DateUtils</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">formatDate</span><span class="hljs-params">(LocalDate date)</span> </span>{
        <span class="hljs-keyword">return</span> date.format(DateTimeFormatter.ofPattern(<span class="hljs-string">"yyyy-MM-dd"</span>));
    }
}
</code></pre>
<h3 id="heading-domain-module">Domain Module</h3>
<p>In the <strong>domain</strong> module, you will define your data models.</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.example.domain;

<span class="hljs-keyword">import</span> javax.persistence.Entity;
<span class="hljs-keyword">import</span> javax.persistence.Id;

<span class="hljs-meta">@Entity</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> </span>{
    <span class="hljs-meta">@Id</span>
    <span class="hljs-keyword">private</span> Long id;
    <span class="hljs-keyword">private</span> String name;

    <span class="hljs-comment">// Getters and Setters</span>
}
</code></pre>
<h3 id="heading-repository-module">Repository Module</h3>
<p>Let’s create the <strong>repository</strong> module to manage data access. Here’s a basic repository interface:</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.example.repository;

<span class="hljs-keyword">import</span> com.example.domain.User;
<span class="hljs-keyword">import</span> org.springframework.data.jpa.repository.JpaRepository;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">UserRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JpaRepository</span>&lt;<span class="hljs-title">User</span>, <span class="hljs-title">Long</span>&gt; </span>{}
</code></pre>
<h3 id="heading-service-module">Service Module</h3>
<p>Let’s create the <strong>service</strong> module to hold your business logic. Here’s an example service class:</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.example.service;

<span class="hljs-keyword">import</span> com.example.domain.User;
<span class="hljs-keyword">import</span> com.example.repository.UserRepository;
<span class="hljs-keyword">import</span> org.springframework.beans.factory.annotation.Autowired;
<span class="hljs-keyword">import</span> org.springframework.stereotype.Service;

<span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> </span>{

    <span class="hljs-meta">@Autowired</span>
    <span class="hljs-keyword">private</span> UserRepository userRepository;

    <span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">getUserById</span><span class="hljs-params">(Long id)</span> </span>{
        <span class="hljs-keyword">return</span> userRepository.findById(id).orElse(<span class="hljs-keyword">null</span>);
    }
}
</code></pre>
<h3 id="heading-web-module">Web Module</h3>
<p>The <strong>web</strong> module serves as the REST API layer.</p>
<pre><code class="lang-java"><span class="hljs-meta">@RestController</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserController</span> </span>{

    <span class="hljs-meta">@Autowired</span>
    <span class="hljs-keyword">private</span> UserService userService;

    <span class="hljs-meta">@GetMapping("/users/{id}")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">getUserById</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable</span> Long id)</span> </span>{
        <span class="hljs-keyword">return</span> userService.getUserById(id);
    }
}
</code></pre>
<h2 id="heading-5-inter-module-communication">5. Inter-Module Communication</h2>
<p>To avoid direct dependencies, you can use <strong>REST APIs</strong> or <strong>message brokers</strong> (like Kafka) for inter-module communication. This ensures loose coupling and allows each module to communicate independently.</p>
<p>The diagram below demonstrates how modules communicate with each other:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730874358819/89d7f058-d074-4b1d-bbb7-81a7bdcb868e.png" alt="Flowchart showing the interaction between modules in the software architecture: Web Module handles API endpoints and returns responses, Service Module executes business logic, Repository Module accesses data and returns processed data, and connects to a Database." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The diagram illustrates how different system components communicate to handle requests efficiently.</p>
<p>The <strong>Web Module</strong> processes incoming API requests and forwards them to the <strong>Service Module</strong>, which contains the business logic. The <strong>Service Module</strong> then interacts with the <strong>Repository Module</strong> to fetch or update data in the <strong>Database</strong>. This layered approach ensures that each module operates independently, promoting flexibility and easier maintenance.</p>
<p><strong>Example Using Feign Client</strong>:</p>
<p>In the context of inter-module communication, using tools like <strong>Feign Clients</strong> is a powerful way to achieve loose coupling between services.</p>
<p>The Feign client allows one module to seamlessly communicate with another through REST API calls, without requiring direct dependencies. This approach fits perfectly within the layered architecture described earlier, where the <strong>Service Module</strong> can fetch data from other services or microservices using Feign clients, rather than directly accessing databases or hard-coding HTTP requests.</p>
<p>This not only simplifies the code but also improves scalability and maintainability by isolating service dependencies.</p>
<pre><code class="lang-java"><span class="hljs-meta">@FeignClient(name = "userServiceClient", url = "http://localhost:8081")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">UserServiceClient</span> </span>{
    <span class="hljs-meta">@GetMapping("/users/{id}")</span>
    <span class="hljs-function">User <span class="hljs-title">getUserById</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable("id")</span> Long id)</span></span>;
}
</code></pre>
<h2 id="heading-6-common-pitfalls-and-solutions">6. Common Pitfalls and Solutions</h2>
<p>When implementing a multi-module architecture, you may encounter several challenges. Here are some common pitfalls and their solutions:</p>
<ul>
<li><p><strong>Circular Dependencies</strong>: Modules may inadvertently depend on each other, creating a circular dependency that complicates builds and deployments.</p>
<ul>
<li><strong>Solution</strong>: Carefully design module interfaces and use dependency management tools to detect and resolve circular dependencies early in the development process.</li>
</ul>
</li>
<li><p><strong>Over-Engineering</strong>: There's a risk of creating too many modules, leading to unnecessary complexity.</p>
<ul>
<li><strong>Solution</strong>: Start with a minimal set of modules and only split further when there's a clear need, ensuring each module has a distinct responsibility.</li>
</ul>
</li>
<li><p><strong>Inconsistent Configurations</strong>: Managing configurations across multiple modules can lead to inconsistencies.</p>
<ul>
<li><strong>Solution</strong>: Use centralized configuration management tools, such as Spring Cloud Config, to maintain consistency across modules.</li>
</ul>
</li>
<li><p><strong>Communication Overhead</strong>: Inter-module communication can introduce latency and complexity.</p>
<ul>
<li><strong>Solution</strong>: Optimize communication by using efficient protocols and consider asynchronous messaging where appropriate to reduce latency.</li>
</ul>
</li>
<li><p><strong>Testing Complexity</strong>: Testing a multi-module project can be more complex due to the interactions between modules.</p>
<ul>
<li><strong>Solution</strong>: Implement a robust testing strategy that includes unit tests for individual modules and integration tests for inter-module interactions.</li>
</ul>
</li>
</ul>
<p>By being aware of these pitfalls and applying these solutions, you can effectively manage the complexities of a multi-module architecture and ensure a smooth development process.</p>
<h2 id="heading-7-testing-strategy-and-configuration">7. Testing Strategy and Configuration</h2>
<p>Testing each module independently and as a unit is critical in multi-module setups.</p>
<h3 id="heading-unit-tests">Unit Tests</h3>
<p>Here, we’ll use JUnit and Mockito for performing unit tests:</p>
<pre><code class="lang-java"><span class="hljs-meta">@RunWith(MockitoJUnitRunner.class)</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserServiceTest</span> </span>{

    <span class="hljs-meta">@Mock</span>
    <span class="hljs-keyword">private</span> UserRepository userRepository;

    <span class="hljs-meta">@InjectMocks</span>
    <span class="hljs-keyword">private</span> UserService userService;

    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">testGetUserById</span><span class="hljs-params">()</span> </span>{
        User user = <span class="hljs-keyword">new</span> User();
        user.setId(<span class="hljs-number">1L</span>);
        user.setName(<span class="hljs-string">"John"</span>);

        Mockito.when(userRepository.findById(<span class="hljs-number">1L</span>)).thenReturn(Optional.of(user));

        User result = userService.getUserById(<span class="hljs-number">1L</span>);
        assertEquals(<span class="hljs-string">"John"</span>, result.getName());
    }
}
</code></pre>
<h3 id="heading-integration-tests">Integration Tests</h3>
<p>And we’ll use Testcontainers with an in-memory database for integration tests:</p>
<pre><code class="lang-java"><span class="hljs-meta">@Testcontainers</span>
<span class="hljs-meta">@ExtendWith(SpringExtension.class)</span>
<span class="hljs-meta">@SpringBootTest</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserServiceIntegrationTest</span> </span>{

    <span class="hljs-meta">@Container</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> PostgreSQLContainer&lt;?&gt; postgresqlContainer = <span class="hljs-keyword">new</span> PostgreSQLContainer&lt;&gt;(<span class="hljs-string">"postgres:latest"</span>);

    <span class="hljs-meta">@Autowired</span>
    <span class="hljs-keyword">private</span> UserService userService;

    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">testFindById</span><span class="hljs-params">()</span> </span>{
        User user = userService.getUserById(<span class="hljs-number">1L</span>);
        assertNotNull(user);
    }
}
</code></pre>
<h2 id="heading-8-error-handling-and-logging">8. Error Handling and Logging</h2>
<p>Error handling and logging ensure a robust and debuggable application.</p>
<h3 id="heading-error-handling">Error Handling</h3>
<p>In this section, we'll explore how to handle errors gracefully in your Spring Boot application using a <strong>global exception handler</strong>. By using <code>@ControllerAdvice</code>, we'll set up a centralized way to catch and respond to errors, keeping our code clean and our responses consistent.</p>
<pre><code class="lang-java"><span class="hljs-meta">@ControllerAdvice</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GlobalExceptionHandler</span> </span>{

    <span class="hljs-meta">@ExceptionHandler(UserNotFoundException.class)</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity&lt;String&gt; <span class="hljs-title">handleUserNotFoundException</span><span class="hljs-params">(UserNotFoundException ex)</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ResponseEntity&lt;&gt;(<span class="hljs-string">"User not found"</span>, HttpStatus.NOT_FOUND);
    }
}
</code></pre>
<p>In the code example above, we define a <code>GlobalExceptionHandler</code> that catches any <code>UserNotFoundException</code> and returns a friendly message like "User not found" with a status of <code>404</code>. This way, you don’t have to handle this exception in every controller—you’ve got it covered in one place!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730874789550/feed75e6-f92c-4102-b3c3-a01f189f3cd7.png" alt="Flowchart depicting a sequence of interactions between a Client, Web Module, Global Error Handler, and Logger. The process involves handling requests, processing them, and managing exceptions. If an exception occurs, it is handled and logged, followed by an error response. If no exception occurs, a successful response is returned." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Now, let’s take a look at the diagram. Here’s how it all flows: when a client sends a request to our <strong>Web Module</strong>, if everything goes smoothly, you'll get a successful response. But if something goes wrong, like a user not being found, the error will be caught by our <strong>Global Error Handler</strong>. This handler logs the issue and returns a clean, structured response to the client.</p>
<p>This approach ensures that users get clear error messages while keeping your app’s internals hidden and secure.</p>
<h3 id="heading-logging">Logging</h3>
<p>Structured logging in each module improves traceability and debugging. You can use a centralized logging system like Logback and include correlation IDs to trace requests.</p>
<h2 id="heading-9-security-and-jwt-integration">9. Security and JWT Integration</h2>
<p>In this section, we’re going to set up <strong>JSON Web Tokens (JWT)</strong> to secure our endpoints and control access based on user roles. We'll configure this in the <code>SecurityConfig</code> class, which will help us enforce who can access what parts of our application.</p>
<pre><code class="lang-java"><span class="hljs-meta">@EnableWebSecurity</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SecurityConfig</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">WebSecurityConfigurerAdapter</span> </span>{

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">configure</span><span class="hljs-params">(HttpSecurity http)</span> <span class="hljs-keyword">throws</span> Exception </span>{
        http.authorizeRequests()
            .antMatchers(<span class="hljs-string">"/admin/**"</span>).hasRole(<span class="hljs-string">"ADMIN"</span>)
            .antMatchers(<span class="hljs-string">"/user/**"</span>).hasAnyRole(<span class="hljs-string">"USER"</span>, <span class="hljs-string">"ADMIN"</span>)
            .anyRequest().authenticated()
            .and()
            .oauth2ResourceServer().jwt();
    }
}
</code></pre>
<p>In the code example above, you can see how we’ve defined access rules:</p>
<ul>
<li><p>The <code>/admin/**</code> endpoints are restricted to users with the <code>ADMIN</code> role.</p>
</li>
<li><p>The <code>/user/**</code> endpoints can be accessed by users with either the <code>USER</code> or <code>ADMIN</code> roles.</p>
</li>
<li><p>Any other requests will require the user to be authenticated.</p>
</li>
</ul>
<p>Next, we set up our application to validate incoming tokens using <code>.oauth2ResourceServer().jwt();</code>. This ensures that only requests with a valid token can access our secured endpoints.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730875088355/b0502e8e-88e4-4aab-bf52-81360892ffbb.png" alt="Diagram depicting a sequence of interactions among five components: Client, Web Module, Security Filter, Service, and Repository. Arrows represent steps for token authentication and accessing a service, including requests, validations, data fetching, and responses, with outcomes for both valid and invalid tokens." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Now, let’s walk through the diagram. When a client sends a request to access a resource, the <strong>Security Filter</strong> first checks if the provided JWT token is valid. If the token is valid, the request proceeds to the <strong>Service Module</strong> to fetch or process the data. If not, access is denied right away, and the client receives an error response.</p>
<p>This flow ensures that only authenticated users can access sensitive resources, keeping our application secure.</p>
<h2 id="heading-10-deployment-with-docker-and-cicd">10. Deployment with Docker and CI/CD</h2>
<p>In this section, we'll containerize each module using <strong>Docker</strong> to make our application easier to deploy and run consistently across different environments. We’ll also set up a <strong>CI/CD pipeline</strong> using GitHub Actions (but you can use Jenkins too if you prefer). Automating this process ensures that any changes you push are automatically built, tested, and deployed.</p>
<h3 id="heading-step-1-containerizing-with-docker">Step 1: Containerizing with Docker</h3>
<p>We start by creating a <strong>Dockerfile</strong> for the <strong>Web Module:</strong></p>
<pre><code class="lang-java">FROM openjdk:<span class="hljs-number">11</span>-jre-slim
COPY target/web-<span class="hljs-number">1.0</span>-SNAPSHOT.jar app.jar
ENTRYPOINT [<span class="hljs-string">"java"</span>, <span class="hljs-string">"-jar"</span>, <span class="hljs-string">"/app.jar"</span>]
</code></pre>
<p>Here, we’re using a lightweight version of Java 11 to keep our image size small. We copy the compiled <code>.jar</code> file into the container and set it up to run when the container starts.</p>
<h3 id="heading-step-2-using-docker-compose-for-multi-module-deployment">Step 2: Using Docker Compose for Multi-Module Deployment</h3>
<p>Now, we'll use a <strong>Docker Compose</strong> file to orchestrate multiple modules together:</p>
<pre><code class="lang-java">version: <span class="hljs-string">'3'</span>
services:
  web:
    build: ./web
    ports:
      - <span class="hljs-string">"8080:8080"</span>
  service:
    build: ./service
    ports:
      - <span class="hljs-string">"8081:8081"</span>
</code></pre>
<p>With this setup, we can run both the <strong>Web Module</strong> and the <strong>Service Module</strong> at the same time, making it easy to spin up the entire application with a single command. Each service is built separately from its own directory, and we expose the necessary ports to access them.</p>
<h3 id="heading-cicd-example-with-github-actions">CI/CD Example with GitHub Actions</h3>
<pre><code class="lang-java">name: CI Pipeline

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout<span class="hljs-meta">@v2</span>
    - name: Set up JDK <span class="hljs-number">11</span>
      uses: actions/setup-java<span class="hljs-meta">@v2</span>
      with:
        java-version: <span class="hljs-string">'11'</span>
    - name: Build with Maven
      run: mvn clean install
</code></pre>
<p>This pipeline automatically kicks in whenever you push new code or create a pull request. It checks out your code, sets up Java, and runs a Maven build to ensure everything is working correctly.</p>
<h2 id="heading-11-best-practices-and-advanced-use-cases">11. Best Practices and Advanced Use Cases</h2>
<p>The following best practices ensure maintainability and scalability.</p>
<h3 id="heading-best-practices">Best Practices</h3>
<ul>
<li><p><strong>Avoid Circular Dependencies</strong>: Ensure modules don’t have circular references to avoid build issues.</p>
</li>
<li><p><strong>Separate Concerns Clearly</strong>: Each module should focus on one responsibility.</p>
</li>
<li><p><strong>Centralized Configurations</strong>: Manage configurations centrally for consistent setups.</p>
</li>
</ul>
<h3 id="heading-advanced-use-cases">Advanced Use Cases</h3>
<ol>
<li><p><strong>Asynchronous Messaging with Kafka</strong>: Use Kafka for decoupled communication between services. Modules can publish and subscribe to events asynchronously.</p>
</li>
<li><p><strong>REST Client with Feign</strong>: Use Feign to call services within modules. Define a Feign client interface for communication.</p>
</li>
<li><p><strong>Caching for Performance</strong>: Use Spring Cache in the service module for optimizing data retrieval.</p>
</li>
</ol>
<h2 id="heading-conclusion-and-key-takeaways">Conclusion and Key Takeaways</h2>
<p>A multi-module Spring Boot project provides modularity, scalability, and ease of maintenance.</p>
<p>In this tutorial, you learned to set up modules, manage inter-module communication, handle errors, add security, and deploy with Docker.</p>
<p>Following best practices and using advanced techniques like messaging and caching will further optimize your multi-module architecture for production use.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What are the SOLID Principles in C#? Explained With Code Examples ]]>
                </title>
                <description>
                    <![CDATA[ The SOLID Principles are five software design principles that help you to write high quality, flexible, maintainable, reusable, testable, and readable software. If you plan to work with object-oriented software, it is crucial to understand these five... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-are-the-solid-principles-in-csharp/</link>
                <guid isPermaLink="false">671a62b68210490d9d68e83f</guid>
                
                    <category>
                        <![CDATA[ Object Oriented Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ C# ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ design patterns ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Danny ]]>
                </dc:creator>
                <pubDate>Thu, 24 Oct 2024 15:07:34 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1729777076695/7d956373-1835-4823-9a6a-d2d232cd64d5.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The SOLID Principles are five software design principles that help you to write high quality, flexible, maintainable, reusable, testable, and readable software. If you plan to work with object-oriented software, it is crucial to understand these five principles.</p>
<p>The SOLID Principles were introduced by a software engineer named Robert C. Martin (also known as "Uncle Bob") in the early 2000s. Uncle Bob’s goal was to promote good software design practices, particularly in object-oriented programming (OOP), by addressing common problems developers face as software systems grow in size and complexity.</p>
<p>Here are the five SOLID principles:</p>
<ul>
<li><p><strong>S</strong>: <a class="post-section-overview" href="#heading-single-responsibility-principle-srp">Single Responsibility Principle (SRP)</a></p>
</li>
<li><p><strong>O</strong>: <a class="post-section-overview" href="#heading-open-closed-principle-ocp">Open-closed Principle (OCP)</a></p>
</li>
<li><p><strong>L</strong>: <a class="post-section-overview" href="#heading-liskov-substitution-principle-lsp">Liskov Substitution Principle (LSP)</a></p>
</li>
<li><p><strong>I</strong>: <a class="post-section-overview" href="#heading-interface-segregation-principle-isp">Interface Segregation Principle (ISP)</a></p>
</li>
<li><p><strong>D</strong>: <a class="post-section-overview" href="#heading-dependency-inversion-principle-dip">Dependency Inversion Principle (DIP)</a></p>
</li>
</ul>
<p>By following these principles, you can create software designs that are easier to understand, maintain, and extend, leading to higher-quality software that is more robust and adaptable to change.</p>
<p>In this article, to demonstrate each principle, I’ll first show you a bad code example in C# that violates the principle. We will then discuss the issues this bad code causes, and then solve those issues by refactoring the code to satisfy the principle.</p>
<p>First up we have the…</p>
<h2 id="heading-single-responsibility-principle-srp-in-c">Single Responsibility Principle (SRP) in C</h2>
<blockquote>
<p>A class should have only one reason to change, meaning that it should have only one responsibility or purpose.</p>
</blockquote>
<p>This principle encourages you to create classes that are more focussed and perform one single well-defined task, rather than multiple tasks. Breaking up classes into smaller, more focused units makes code easier to understand, maintain, and test.</p>
<p><strong>An example that violates the SRP:</strong></p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">User</span>
{
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Username { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Email { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Register</span>(<span class="hljs-params"></span>)</span>
 {
   <span class="hljs-comment">// Register user logic, e.g. save to database...</span>

   <span class="hljs-comment">// Send email notification</span>
   EmailSender emailSender = <span class="hljs-keyword">new</span> EmailSender();
   emailSender.SendEmail(<span class="hljs-string">"Welcome to our platform!"</span>, Email);
 }
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">EmailSender</span>
{
 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SendEmail</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> message, <span class="hljs-keyword">string</span> recipient</span>)</span>
 {
   <span class="hljs-comment">// Email sending logic</span>
   Console.WriteLine(<span class="hljs-string">$"Sending email to <span class="hljs-subst">{recipient}</span>: <span class="hljs-subst">{message}</span>"</span>);
 }
}
</code></pre>
<p>In this example, the <code>User</code> class manages user data (username and email), and contains logic for registering a user. This violates the SRP because the class has more than one reason to change. It could change due to:</p>
<ul>
<li><p>Modifications in user data management – for example adding more fields, such as <code>firstName</code>, <code>gender</code>, <code>hobbies</code>.</p>
</li>
<li><p>Modifications to the logic of registering a user, for example we may choose to fetch a user from the database by their username rather than their email.</p>
</li>
</ul>
<p>To adhere to the Single Responsibility Principle, we should separate these responsibilities into separate classes. </p>
<p><strong>Refactoring the code to satisfy the SRP</strong>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">User</span>
{
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Username { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Email { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">EmailSender</span>
{
 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SendEmail</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> message, <span class="hljs-keyword">string</span> recipient</span>)</span>
 {
   <span class="hljs-comment">// Email sending logic</span>
   Console.WriteLine(<span class="hljs-string">$"Sending email to <span class="hljs-subst">{recipient}</span>: <span class="hljs-subst">{message}</span>"</span>);
 }
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span>
{
 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">RegisterUser</span>(<span class="hljs-params">User user</span>)</span>
 {
   <span class="hljs-comment">// Register user logic...</span>

   EmailSender emailSender = <span class="hljs-keyword">new</span> EmailSender();
   emailSender.SendEmail(<span class="hljs-string">"Welcome to our platform!"</span>, user.Email);
 }
}
</code></pre>
<p>In the refactored code, the <code>User</code> class is responsible solely for representing user data. The <code>UserService</code> class now handles user registration, separating concerns related to user data management from user registration logic. The <code>UserService</code> class is responsible only for the business logic of registering a user.</p>
<p>This separation of responsibilities adheres to the Single Responsibility Principle, making the code easier to understand, maintain, and extend.</p>
<h2 id="heading-openclosed-principle-ocp-in-c">Open/Closed Principle (OCP) in C</h2>
<blockquote>
<p>Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.</p>
</blockquote>
<p>This principle promotes the idea that existing code should be able to be extended with new functionality without modifying its source code. It encourages the use of abstraction and polymorphism to achieve this goal, allowing for code to be easily extended through inheritance or composition.</p>
<p>(By the way, if you don’t understand these fundamental OOP concepts, such as abstraction, polymorphism, inheritance and composition — then check out my book, <a target="_blank" href="https://www.amazon.com/Mastering-Design-Patterns-Beginner-Friendly-Principles/dp/B0DB6MLYYZ">Mastering Design Patterns in C#: A Beginner-Friendly Guide, Including OOP and SOLID Principles on Amazon</a> or <a target="_blank" href="https://doabledanny.gumroad.com/l/ennyj?layout=profile">Gumroad</a>.)</p>
<p>Let's consider an example of a <code>Shape</code> class hierarchy that calculates the area of different geometric shapes. Initially, this violates the Open/Closed Principle because adding a new shape requires modifying the existing code:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> ShapeType
{
 Circle,
 Rectangle
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Shape</span>
{
 <span class="hljs-keyword">public</span> ShapeType Type { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Radius { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Length { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Width { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">CalculateArea</span>(<span class="hljs-params"></span>)</span>
 {
   <span class="hljs-keyword">switch</span> (Type)
   {
     <span class="hljs-keyword">case</span> ShapeType.Circle:
       <span class="hljs-keyword">return</span> Math.PI * Math.Pow(Radius, <span class="hljs-number">2</span>);
     <span class="hljs-keyword">case</span> ShapeType.Rectangle:
       <span class="hljs-keyword">return</span> Length * Width;
     <span class="hljs-keyword">default</span>:
       <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidOperationException(<span class="hljs-string">"Unsupported shape type."</span>);
   }
 }
}
</code></pre>
<p>In this example, the <code>Shape</code> class has a method, <code>CalculateArea()</code>, that calculates the area based on the type of shape. Adding a new shape, such as a triangle, would require modifying the existing <code>Shape</code> class, violating the OCP.</p>
<p>To adhere to the Open/Closed Principle, we should design the system in a way that allows for extension without modification. Let's refactor the code using inheritance and polymorphism:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Shape</span>
{
 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">double</span> <span class="hljs-title">CalculateArea</span>(<span class="hljs-params"></span>)</span>;
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Circle</span> : <span class="hljs-title">Shape</span>
{
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Radius { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">double</span> <span class="hljs-title">CalculateArea</span>(<span class="hljs-params"></span>)</span>
 {
   <span class="hljs-keyword">return</span> Math.PI * Math.Pow(Radius, <span class="hljs-number">2</span>);
 }
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Rectangle</span> : <span class="hljs-title">Shape</span>
{
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Length { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Width { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">double</span> <span class="hljs-title">CalculateArea</span>(<span class="hljs-params"></span>)</span>
 {
   <span class="hljs-keyword">return</span> Length * Width;
 }
}
</code></pre>
<p>In this refactored code, we define an abstract <code>Shape</code> class with an abstract <code>CalculateArea()</code> method. Concrete shape classes (<code>Circle</code> and <code>Rectangle</code>) inherit from the <code>Shape</code> class and provide their own implementations of <code>CalculateArea()</code>.</p>
<p>Adding a new shape, such as a triangle, would involve creating a new class – <em>extending</em> the codebase – that inherits from <code>Shape</code> and implements <code>CalculateArea()</code>, without <em>modifying</em> existing code. This adheres to the OCP by allowing for extension without modification.</p>
<p>Being able to add functionality without modifying existing code means that we don’t have to worry as much about breaking existing working code and introducing bugs.</p>
<p>Following the OCP encourages us to design our software so that we add new features only by adding new code. This helps us to build loosely-coupled, maintainable software.</p>
<h2 id="heading-liskov-substitution-principle-lsp-in-c">Liskov Substitution Principle (LSP) in C</h2>
<blockquote>
<p>Objects of a superclass should be replaceable with objects of its subclass without affecting the correctness of the program.</p>
</blockquote>
<p>This principle ensures that inheritance hierarchies are well-designed and that subclasses adhere to the contracts defined by their superclasses.</p>
<p>Violations of the LSP can lead to unexpected behavior or errors when substituting objects, making code harder to reason about and maintain.</p>
<p>Let's consider an example involving a <code>Rectangle</code> class and a <code>Square</code> class, which inherit from a common <code>Shape</code> class. Initially, we'll violate the LSP by not adhering to the behavior expected from these classes. Then, we'll fix it to ensure that the principle is respected.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Shape</span>
{
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">double</span> Area { <span class="hljs-keyword">get</span>; }
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Rectangle</span> : <span class="hljs-title">Shape</span>
{
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">virtual</span> <span class="hljs-keyword">double</span> Width { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

 <span class="hljs-keyword">public</span> <span class="hljs-keyword">virtual</span> <span class="hljs-keyword">double</span> Height { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

 <span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">double</span> Area =&gt; Width * Height;
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Square</span> : <span class="hljs-title">Rectangle</span>
{
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">double</span> Width
 {
   <span class="hljs-keyword">get</span> =&gt; <span class="hljs-keyword">base</span>.Width;
   <span class="hljs-keyword">set</span> =&gt; <span class="hljs-keyword">base</span>.Width = <span class="hljs-keyword">base</span>.Height = <span class="hljs-keyword">value</span>;
 }

 <span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">double</span> Height
 {
   <span class="hljs-keyword">get</span> =&gt; <span class="hljs-keyword">base</span>.Height;
   <span class="hljs-keyword">set</span> =&gt; <span class="hljs-keyword">base</span>.Height = <span class="hljs-keyword">base</span>.Width = <span class="hljs-keyword">value</span>;
 }
}
</code></pre>
<p>Now, let’s test out if <code>Rectangle</code> calculates its area correctly:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Program.cs</span>

<span class="hljs-keyword">var</span> rect = <span class="hljs-keyword">new</span> Rectangle();
rect.Height = <span class="hljs-number">10</span>;
rect.Width = <span class="hljs-number">5</span>;

System.Console.WriteLine(<span class="hljs-string">"Expected area = 10 * 5 = 50."</span>);

System.Console.WriteLine(<span class="hljs-string">"Calculated area = "</span> + rect.Area);
</code></pre>
<p>Running the program:</p>
<pre><code class="lang-plaintext">Expected area = 10 * 5 = 50.

Calculated area = 50
</code></pre>
<p>Perfect!</p>
<p>Now, in our program, the <code>Square</code> class inherits from, or extends, the <code>Rectangle</code> class, because, mathematically, a square is just a special type of rectangle, where its height equals its width. Because of this, we decided that <code>Square</code> should extend <code>Rectangle</code> – it’s like saying “a square <em>is a</em> (special type of) rectangle”.</p>
<p>But look what happens if we substitute the <code>Rectangle</code> class for the <code>Square</code> class:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> rect = <span class="hljs-keyword">new</span> Square();
rect.Height = <span class="hljs-number">10</span>;
rect.Width = <span class="hljs-number">5</span>;

System.Console.WriteLine(<span class="hljs-string">"Expected area = 10 * 5 = 50."</span>);

System.Console.WriteLine(<span class="hljs-string">"Calculated area = "</span> + rect.Area);
</code></pre>
<pre><code class="lang-plaintext">Expected area = 10 * 5 = 50.

Calculated area = 25
</code></pre>
<p>Oh dear, LSP has been violated: we replaced the object of a superclass (<code>Rectangle</code>) with an object of its subclass (<code>Square</code>), and it affected the correctness of our program. By modeling <code>Square</code> as a subclass of <code>Rectangle</code>, and allowing width and height to be independently set, we violate the LSP. When setting the width and height of a <code>Square</code>, it should retain its squareness, but our implementation allows for inconsistency.</p>
<p>Let’s fix this to satisfy LSP:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Shape</span>
{
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">double</span> Area { <span class="hljs-keyword">get</span>; }
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Rectangle</span> : <span class="hljs-title">Shape</span>
{
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Width { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

 <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Height { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

 <span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">double</span> Area =&gt; Width * Height;
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Square</span> : <span class="hljs-title">Shape</span>
{
 <span class="hljs-keyword">private</span> <span class="hljs-keyword">double</span> sideLength;

 <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> SideLength
 {
   <span class="hljs-keyword">get</span> =&gt; sideLength;
   <span class="hljs-keyword">set</span>
   {
     sideLength = <span class="hljs-keyword">value</span>;
   }
 }

 <span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">double</span> Area =&gt; sideLength * sideLength;
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-comment">// Program.cs</span>

Shape rectangle = <span class="hljs-keyword">new</span> Rectangle { Width = <span class="hljs-number">5</span>, Height = <span class="hljs-number">4</span> };

Console.WriteLine(<span class="hljs-string">$"Area of the rectangle: <span class="hljs-subst">{rectangle.Area}</span>"</span>);

Shape square = <span class="hljs-keyword">new</span> Square { SideLength = <span class="hljs-number">5</span> };

Console.WriteLine(<span class="hljs-string">$"Area of the square: <span class="hljs-subst">{square.Area}</span>"</span>);
</code></pre>
<p>In this corrected example, we redesign the <code>Square</code> class to directly set the side length. Now, a <code>Square</code> is correctly modeled as a subclass of <code>Shape</code>, and it adheres to the Liskov Substitution Principle.</p>
<p>How does this satisfy LSP? Well, we have a superclass, <code>Shape</code>, and subclasses <code>Rectangle</code> and <code>Square</code>. Both <code>Rectangle</code> and <code>Square</code> maintain the correct expected behavior of a <code>Shape</code> — we can substitute a square for a rectangle and the area will still be calculated correctly.</p>
<h2 id="heading-interface-segregation-principle-isp-in-c">Interface Segregation Principle (ISP) in C</h2>
<blockquote>
<p>Clients should not be forced to depend on interfaces they do not use.</p>
</blockquote>
<p>This principle encourages the creation of fine-grained interfaces that contain only the methods required by the clients that use them. It helps to prevent the creation of "fat" interfaces that force clients to implement unnecessary methods, leading to cleaner and more maintainable code.</p>
<p>Let's consider an example involving 2D and 3D shapes, initially violating the ISP.</p>
<p><strong>Violating ISP:</strong></p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IShape</span>
{
 <span class="hljs-function"><span class="hljs-keyword">double</span> <span class="hljs-title">Area</span>(<span class="hljs-params"></span>)</span>;
 <span class="hljs-function"><span class="hljs-keyword">double</span> <span class="hljs-title">Volume</span>(<span class="hljs-params"></span>)</span>; <span class="hljs-comment">// problem: 2D shapes don't have volume!</span>
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Circle</span> : <span class="hljs-title">IShape</span>
{
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Radius { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">Area</span>(<span class="hljs-params"></span>)</span>
 {
   <span class="hljs-keyword">return</span> Math.PI * Math.Pow(Radius, <span class="hljs-number">2</span>);
 }

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">Volume</span>(<span class="hljs-params"></span>)</span>
 {
   <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidOperationException(<span class="hljs-string">"Volume not applicable for 2D shapes."</span>);
 }
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Sphere</span> : <span class="hljs-title">IShape</span>
{
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Radius { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">Area</span>(<span class="hljs-params"></span>)</span>
 {
   <span class="hljs-keyword">return</span> <span class="hljs-number">4</span> * Math.PI * Math.Pow(Radius, <span class="hljs-number">2</span>);
 }

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">Volume</span>(<span class="hljs-params"></span>)</span>
 {
   <span class="hljs-keyword">return</span> (<span class="hljs-number">4.0</span> / <span class="hljs-number">3.0</span>) * Math.PI * Math.Pow(Radius, <span class="hljs-number">3</span>);
 }
}
</code></pre>
<p>In this example, we have an <code>IShape</code> interface representing both 2D and 3D shapes. However, the <code>Volume()</code> method is problematic for 2D shapes, like <code>Circle</code> and <code>Rectangle</code>, because they don't have volume. This violates the ISP because clients (classes using the <code>IShape</code> interface) may be forced to depend on methods they do not need.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> circle = <span class="hljs-keyword">new</span> Circle();
circle.Radius = <span class="hljs-number">10</span>;

System.Console.WriteLine(circle.Area());
System.Console.WriteLine(circle.Volume()); <span class="hljs-comment">// My text editor doesn't flag a problem...</span>

<span class="hljs-keyword">var</span> sphere = <span class="hljs-keyword">new</span> Sphere();
sphere.Radius = <span class="hljs-number">10</span>;

System.Console.WriteLine(sphere.Area());
System.Console.WriteLine(sphere.Volume());
</code></pre>
<p>Usually, if I try to call a method on an object that doesn’t exist, VS Code will tell me that I’m making a mistake. But above, when I call <code>circle.Volume()</code>, VS code is like “no problem”. And VS code is correct, because the <code>IShape</code> interface forces <code>Circle</code> to implement a <code>Volume()</code> method, even though circles don’t have volume.</p>
<p>It’s easy to see how violating ISP can introduce bugs into a program – above, everything looks fine, until we run the program and an exception gets thrown.</p>
<p><strong>Fixing ISP</strong></p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IShape2D</span>
{
 <span class="hljs-function"><span class="hljs-keyword">double</span> <span class="hljs-title">Area</span>(<span class="hljs-params"></span>)</span>;
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IShape3D</span>
{
 <span class="hljs-function"><span class="hljs-keyword">double</span> <span class="hljs-title">Area</span>(<span class="hljs-params"></span>)</span>;
 <span class="hljs-function"><span class="hljs-keyword">double</span> <span class="hljs-title">Volume</span>(<span class="hljs-params"></span>)</span>;
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Circle</span> : <span class="hljs-title">IShape2D</span>
{
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Radius { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">Area</span>(<span class="hljs-params"></span>)</span>
 {
   <span class="hljs-keyword">return</span> Math.PI * Math.Pow(Radius, <span class="hljs-number">2</span>);
 }
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Sphere</span> : <span class="hljs-title">IShape3D</span>
{
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Radius { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">Area</span>(<span class="hljs-params"></span>)</span>
 {
   <span class="hljs-keyword">return</span> <span class="hljs-number">4</span> * Math.PI * Math.Pow(Radius, <span class="hljs-number">2</span>);
 }

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">Volume</span>(<span class="hljs-params"></span>)</span>
 {
   <span class="hljs-keyword">return</span> (<span class="hljs-number">4.0</span> / <span class="hljs-number">3.0</span>) * Math.PI * Math.Pow(Radius, <span class="hljs-number">3</span>);
 }
}
</code></pre>
<p>In the fixed example, we've <em>segregated</em> the <code>IShape</code> interface into two smaller, more focused interfaces: <code>IShape2D</code> and <code>IShape3D</code>. Each shape class now implements only the interface that is relevant to its functionality.</p>
<p>This adheres to the Interface Segregation Principle by ensuring that clients are not forced to depend on methods that they do not use. Clients can now depend only on the interfaces that they need, promoting better code reuse and flexibility.</p>
<p>Next up, the fifth and final SOLID principle…</p>
<h2 id="heading-dependency-inversion-principle-dip-in-c">Dependency Inversion Principle (DIP) in C</h2>
<blockquote>
<p>High-level modules should not depend on low-level modules. Both should depend on abstractions.</p>
</blockquote>
<p>Dependency Inversion is the strategy of depending upon interfaces or abstract classes rather than upon concrete classes. This principle promotes decoupling between modules and promotes the use of interfaces or abstract classes to define dependencies, allowing for more flexible and testable code.</p>
<p>Let's start with an example violating the DIP and then we’ll correct it.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Engine</span> <span class="hljs-comment">// Engine is our "low-level" module</span>
{
 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Start</span>(<span class="hljs-params"></span>)</span>
 {
   System.Console.WriteLine(<span class="hljs-string">"Engine started."</span>);
 }
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Car</span> <span class="hljs-comment">// Car is our "high-level" module</span>
{
 <span class="hljs-keyword">private</span> Engine engine;

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Car</span>(<span class="hljs-params"></span>)</span>
 {
   <span class="hljs-keyword">this</span>.engine = <span class="hljs-keyword">new</span> Engine(); <span class="hljs-comment">// Direct dependency on concrete Engine class</span>
 }

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">StartCar</span>(<span class="hljs-params"></span>)</span>
 {
   engine.Start();
   System.Console.WriteLine(<span class="hljs-string">"Car started."</span>);
 }
}
</code></pre>
<p>In this example:</p>
<ul>
<li><p>The <code>Car</code> class directly creates an instance of the <code>Engine</code> class, leading to a tight coupling between Car and Engine.</p>
</li>
<li><p>If the <code>Engine</code> class changes, it may affect the <code>Car</code> class, violating the Dependency Inversion Principle.</p>
</li>
</ul>
<p>The UML diagram below shows that <code>Car</code> depends on <code>Engine</code>:</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdbd6IZ4TfDmGCPIjsJJHTtEw7_WBxCP-H1cSme78Ze7qJq0fG6tQzNo7A-IbgnnruAZwMBhjuJoozA0rzA9RM35Pu9vWpk4I2Hp6Szk7Ns_kTFbu2oIIfHQa9ceBembsjN8EUlZLkQuB863NyzeeSn7qY?key=p75tPpeumlH4kqsSJuxz6w" alt="AD_4nXdbd6IZ4TfDmGCPIjsJJHTtEw7_WBxCP-H1cSme78Ze7qJq0fG6tQzNo7A-IbgnnruAZwMBhjuJoozA0rzA9RM35Pu9vWpk4I2Hp6Szk7Ns_kTFbu2oIIfHQa9ceBembsjN8EUlZLkQuB863NyzeeSn7qY?key=p75tPpeumlH4kqsSJuxz6w" width="600" height="400" loading="lazy"></p>
<p>But what do we mean by “high level” and “low level” classes?</p>
<p><strong>High-Level Class</strong>: The high-level class is typically the one that represents the main functionality or business logic of the application. It orchestrates the interaction between various components and is often more abstract in nature.</p>
<p>In this example, the <code>Car</code> class can be considered the high-level class. It represents the main functionality related to starting the car and driving it. The <code>Car</code> class is concerned with the overall behavior of the car, such as controlling its movement.</p>
<p><strong>Low-Level Class</strong>: The low-level class is usually one that provides specific functionality or services that are used by the high-level class. It typically deals with implementation details and is more concrete in nature.</p>
<p>In this example, the <code>Engine</code> class can be considered the low-level class. It provides the specific functionality related to starting the engine. The <code>Engine</code> class encapsulates the details of how the engine operates, such as ignition and combustion.</p>
<p>So in summary, the <code>Car</code> class is the high-level class, representing the main functionality of the application related to the car's behavior.</p>
<p>The <code>Engine</code> class is the low-level class, providing specific functionality related to the operation of the engine, which is used by the Car class.</p>
<p><strong>Fixing DIP:</strong></p>
<p>To adhere to the Dependency Inversion Principle, we introduce an abstraction (interface) between <code>Car</code> and <code>Engine</code>, allowing <code>Car</code> to depend on an abstraction instead of a concrete implementation.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IEngine</span>
{
 <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">Start</span>(<span class="hljs-params"></span>)</span>;
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Engine</span> : <span class="hljs-title">IEngine</span>
{
 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Start</span>(<span class="hljs-params"></span>)</span>
 {
   System.Console.WriteLine(<span class="hljs-string">"Engine started."</span>);
 }
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Car</span>
{
 <span class="hljs-keyword">private</span> IEngine engine;

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Car</span>(<span class="hljs-params">IEngine engine</span>)</span>
 {
   <span class="hljs-keyword">this</span>.engine = engine;
 }

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">StartCar</span>(<span class="hljs-params"></span>)</span>
 {
   engine.Start();
   System.Console.WriteLine(<span class="hljs-string">"Car started."</span>);
 }
}
</code></pre>
<p>We can now <em>inject</em> any type of engine into <code>Car</code> implementations:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> engine = <span class="hljs-keyword">new</span> Engine(); <span class="hljs-comment">// concrete implementation to be "injected" into the car</span>

<span class="hljs-keyword">var</span> car = <span class="hljs-keyword">new</span> Car(engine);

car.StartCar();
</code></pre>
<p>From the UML diagram below, we can see that both objects now depend on the abstraction level of the interface. <code>Engine</code> has inverted its dependency on <code>Car</code>.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXf2Wes5g5HoeLNGoo4weo-gO6AVdVJ1WbRZxUfTEXIFROup8qCeUiQ8l5CsrwXkC5I1_0i3Q5DyzN5wpXSgjol2_RNFysFKpjMyj4SdEI2lFOplOs-uCUxZGEWE9fI4sFzMKfQOvOx33HKViFcXoqTVmi2s2FcLvCobCCZAvA?key=hfr-bV5v9p97pXexBFuY1A" alt="AD_4nXf2Wes5g5HoeLNGoo4weo-gO6AVdVJ1WbRZxUfTEXIFROup8qCeUiQ8l5CsrwXkC5I1_0i3Q5DyzN5wpXSgjol2_RNFysFKpjMyj4SdEI2lFOplOs-uCUxZGEWE9fI4sFzMKfQOvOx33HKViFcXoqTVmi2s2FcLvCobCCZAvA?key=hfr-bV5v9p97pXexBFuY1A" width="600" height="400" loading="lazy"></p>
<p>In this corrected example:</p>
<ol>
<li><p>We define an interface <code>IEngine</code> representing the behavior of an engine.</p>
</li>
<li><p>The <code>Engine</code> class implements the <code>IEngine</code> interface.</p>
</li>
<li><p>The <code>Car</code> class now depends on the <code>IEngine</code> interface instead of the concrete <code>Engine</code> class.</p>
</li>
<li><p>Dependency injection is used to inject the <code>IEngine</code> implementation into the <code>Car</code> class, promoting loose coupling. Now, if we want to give a car a different type of engine, for example a <code>FastEngine</code>, we can inject that in instead. </p>
</li>
<li><p>Now, if the implementation of the engine changes, it won't affect the <code>Car</code> class as long as it adheres to the <code>IEngine</code> interface.</p>
</li>
</ol>
<p>Dependency Injection (DI) offers several advantages in software development:</p>
<ul>
<li><p><strong>Decoupling</strong>: DI promotes loose coupling between components by removing direct dependencies. Components rely on abstractions rather than concrete implementations, making them more independent and easier to maintain.</p>
</li>
<li><p><strong>Testability</strong>: Dependency injection simplifies unit testing by allowing components to be easily replaced with mock or stub implementations during testing. This enables isolated testing of individual components without relying on their dependencies.</p>
</li>
<li><p><strong>Flexibility</strong>: DI provides flexibility in configuring and swapping dependencies at runtime. It allows different implementations of dependencies to be used interchangeably without modifying the client code, facilitating runtime customization and extensibility.</p>
</li>
<li><p><strong>Readability and Maintainability</strong>: By explicitly specifying dependencies in the constructor or method parameters, DI improves code readability and makes the codebase easier to understand. It also reduces the risk of hidden dependencies, leading to more maintainable and understandable code.</p>
</li>
<li><p><strong>Reusability</strong>: DI promotes component reusability by decoupling them from their specific contexts or environments. Components can be designed to be independent of the application framework or platform, making them more portable and reusable in different projects or scenarios.</p>
</li>
<li><p><strong>Scalability</strong>: DI simplifies the management of dependencies in large-scale applications by providing a standardised approach for dependency resolution. It helps prevent dependency hell and makes it easier to manage and scale complex systems.</p>
</li>
</ul>
<p>Overall, dependency injection enhances modularity, testability, and maintainability of software systems, contributing to improved software quality and developer productivity.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Congratulations – you now understand the extremely important SOLID principles. These principles are going to save you a lot of headaches during your software development career, and guide you towards creating beautiful, maintainable, flexible, testable software.</p>
<p>If you’d like to take your software development skills to the next level and learn:</p>
<ul>
<li><p>OOP principles: encapsulation, abstraction, inheritance, polymorphism, coupling, composition, composition vs inheritance, fragile base class problem.</p>
</li>
<li><p>All 23 design patterns (“The Gang of Four Design Patterns”) with real world examples.</p>
</li>
<li><p>Unified Modeling Language (UML): the standard way to model classes and the relationships between them.</p>
</li>
</ul>
<p>Then check out my book:</p>
<p><a target="_blank" href="https://www.amazon.com/Mastering-Design-Patterns-Beginner-Friendly-Principles/dp/B0DBZGQZMZ">Mastering Design Patterns in C#: A Beginner-Friendly Guide, Including OOP and SOLID Principles on Amazon</a> (also available on <a target="_blank" href="https://doabledanny.gumroad.com/l/ennyj">Gumroad</a>).</p>
<p>Hopefully this article helps you to become a better OOP software developer!</p>
<p>Thanks for reading,</p>
<p><a target="_blank" href="https://www.youtube.com/channel/UC0URylW_U4i26wN231yRqvA">Danny</a> 😎</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create Software Architecture Diagrams Using the C4 Model ]]>
                </title>
                <description>
                    <![CDATA[ As a developer, you'll likely work on a complex project at some point where deciphering the codebase feels like reading a whole novel. Engineers are code wizards, but even the best get lost in sprawling code. The challenge is that architecture diagra... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-software-architecture-diagrams-using-the-c4-model/</link>
                <guid isPermaLink="false">66c636dc83227f02672177db</guid>
                
                    <category>
                        <![CDATA[ Software Engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ design patterns ]]>
                    </category>
                
                    <category>
                        <![CDATA[ C4 Model ]]>
                    </category>
                
                    <category>
                        <![CDATA[ diagrams ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Alex Pliutau ]]>
                </dc:creator>
                <pubDate>Wed, 21 Aug 2024 18:50:04 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1724187048778/7d2821c6-c0c9-4d03-999f-37022388210c.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>As a developer, you'll likely work on a complex project at some point where deciphering the codebase feels like reading a whole novel. Engineers are code wizards, but even the best get lost in sprawling code.</p>
<p>The challenge is that architecture diagrams – if they even exist – are often outdated relics from a bygone era.</p>
<p>This is why creating and maintaining effective and clear diagrams should be effortless. Up-to-date visuals ensure everyone stays on the same page, eliminating confusion and wasted time.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a target="_blank" href="heading-what-is-the-c4-model">What is the C4 Model?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-level-1-context">Level 1: Context</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-level-2-containers">Level 2: Containers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-level-3-components">Level 3: Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-level-4-code">Level 4: Code</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-supplementary-diagrams">Supplementary Diagrams</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-diagrams-as-code">Diagrams as Code</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-automate-rendering-in-your-ci">Automate Rendering in Your CI</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-resources">Resources</a></p>
</li>
</ul>
<h2 id="heading-what-is-the-c4-model">What is the C4 Model?</h2>
<p>The <a target="_blank" href="https://c4model.com/">C4 model</a> was created as a way to help software development teams describe and communicate software architecture.</p>
<p>C4 stands for “Context, Containers, Components, and Code”. Those are the four levels that should be enough to describe a complex system.</p>
<p>The best way to explain the concept is to think about how we use Google Maps. When we are exploring an area in Google Maps, we will often start zoomed out to help us get context. Once we find the rough area we are interested in we can zoom in to get a little more detail.</p>
<h3 id="heading-level-1-context">Level 1: Context</h3>
<p>This level is the most zoomed out. It's a bird’s eye view of the system in the greater context of the world. The diagram concentrates on actors and systems.</p>
<p>For the examples below, we will use a simple Task Management Software System to demonstrate all these 4 levels.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d096cb9-3058-4bbd-9c69-6a41361e4d3b_1600x2000.png" alt="Diagram showing the context Level" width="600" height="400" loading="lazy"></p>
<p>This diagram portrays the Task Management Software System's interactions with external systems and the different user groups that utilize it. We can see that the Task Management software relies on two external systems: Email and Calendar, and two types of actors (users) use it: Customer and Admin User.</p>
<h3 id="heading-level-2-containers">Level 2: Containers</h3>
<p>The containers level is a more detailed view of your system (don’t confuse C4 containers with Docker containers).</p>
<p>It reveals how various functional units like applications and databases work together and distribute responsibilities.</p>
<p>This diagram also highlights the key technologies employed and showcases the communication flow between these containers. It presents a simplified, technology-centric view of the system's core components and their interactions.</p>
<p>If you have Microservice architecture, then each Microservice would be a container.</p>
<p>Examples of containers are:</p>
<ul>
<li><p>Single page application</p>
</li>
<li><p>Web server</p>
</li>
<li><p>Serverless function</p>
</li>
<li><p>Database</p>
</li>
<li><p>API</p>
</li>
<li><p>Message buses</p>
</li>
</ul>
<p>And so on.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb614c4e5-4fbd-4e10-8682-c3e67ec72f2d_3028x2691.png" alt="Diagram showing the Containers Level" width="600" height="400" loading="lazy"></p>
<p>This level delves into the internal composition of the Task Management Software System. It showcases that our Task Management software system consists of containers such as User Web UI, Admin Web UI, API and a Database. API is also the container that is connected to external systems, for example to send emails or create events in calendar.</p>
<h3 id="heading-level-3-components">Level 3: Components</h3>
<p>The next level of zoom is components. This shows the major structural building blocks of your application, and is often a conceptual view of the application. The term component is loose here. It could represent a controller or a service containing business logic.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55564aaa-d404-4322-a6f3-9674326410d5_1995x1900.png" alt="Diagram showing the Components Level" width="600" height="400" loading="lazy"></p>
<p>This diagram focuses on the internal structure of the API container within the Task Management Software System. It reveals that the API container houses crucial functionalities like CRUD operations (Create, Read, Update, Delete) for data manipulation and user authentication mechanisms. The CRUD components is the one that talks to the database.</p>
<h3 id="heading-level-4-code">Level 4: Code</h3>
<p>The deepest level of zoom is the code diagram. Although this diagram exists, it is often not used as the code paints a very similar picture. However, in highly regulated environments and complex legacy projects this level can help to paint a better picture of inner intricacies of the software.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde6e40f0-d713-46b9-906e-618fb61eb622_602x339.png" alt="https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde6e40f0-d713-46b9-906e-618fb61eb622_602x339" width="600" height="400" loading="lazy"></p>
<h3 id="heading-supplementary-diagrams">Supplementary Diagrams</h3>
<p>Besides the 4 diagrams above, there are a couple more worth mentioning:</p>
<ul>
<li><p>Deployment diagram</p>
</li>
<li><p>Dynamic diagram: to describe the process or a flow</p>
</li>
</ul>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa13b10a-2093-4ab8-8302-dc75b09be96f_1570x1461.png" alt="Login Flow" width="600" height="400" loading="lazy"></p>
<p>On this diagram we show a Login Flow, which is not a container or component, but rather a software process that happens in our software system. It shows that Web/Admin UIs use JWT-based authentication for communication with the API and the JWT token is stored in local storage on a client side.</p>
<h2 id="heading-diagrams-as-code">Diagrams as Code</h2>
<p>The power of C4 comes with a diagram-as-code approach. This means treating your diagrams just like your codebase:</p>
<ul>
<li><p><strong>Version control:</strong> Store them in a source control system (like Git) for easy tracking and collaboration.</p>
</li>
<li><p><strong>Collaboration:</strong> Work together on diagrams using pull requests, similar to code reviews.</p>
</li>
<li><p><strong>Automation:</strong> Integrate them into your build pipelines for automatic rendering with your preferred tools.</p>
</li>
</ul>
<h3 id="heading-helpful-tool-structurizr">Helpful Tool: Structurizr</h3>
<p>There are few tools to help with modeling and diagramming, but the most popular nowadays is <a target="_blank" href="https://www.structurizr.com/">Structurizr</a> with their custom DSL (Domain Specific Language).</p>
<p>All you need is to get familiar with the DSL syntax, which is pretty simple. As long as you get used to it you will be able to create or update diagrams in no time.</p>
<p>Below you can see the DSL for our Task Management Software System.</p>
<pre><code class="lang-bash">workspace {
    model {
        <span class="hljs-comment"># Actors</span>
        customer = person <span class="hljs-string">"Customer"</span> <span class="hljs-string">""</span> <span class="hljs-string">"person"</span>
        admin = person <span class="hljs-string">"Admin User"</span> <span class="hljs-string">""</span> <span class="hljs-string">"person"</span>

        <span class="hljs-comment"># External systems</span>
        emailSystem = softwareSystem <span class="hljs-string">"Email System"</span> <span class="hljs-string">"Mailgun"</span> <span class="hljs-string">"external"</span>
        calendarSystem = softwareSystem <span class="hljs-string">"Calendar System"</span> <span class="hljs-string">"Calendly"</span> <span class="hljs-string">"external"</span>

        <span class="hljs-comment"># Task Management System</span>
        taskManagementSystem  = softwareSystem <span class="hljs-string">"Task Management System"</span>{
            webContainer = container <span class="hljs-string">"User Web UI"</span> <span class="hljs-string">""</span> <span class="hljs-string">""</span> <span class="hljs-string">"frontend"</span>
            adminContainer = container <span class="hljs-string">"Admin Web UI"</span> <span class="hljs-string">""</span> <span class="hljs-string">""</span> <span class="hljs-string">"frontend"</span>
            dbContainer = container <span class="hljs-string">"Database"</span> <span class="hljs-string">"PostgreSQL"</span> <span class="hljs-string">""</span> <span class="hljs-string">"database"</span>
            apiContainer = container <span class="hljs-string">"API"</span> <span class="hljs-string">"Go"</span> {
                authComp = component <span class="hljs-string">"Authentication"</span>
                crudComp = component <span class="hljs-string">"CRUD"</span>
            }
        }

        <span class="hljs-comment"># Relationships (Actors &amp; Systems)</span>
        customer -&gt; webContainer <span class="hljs-string">"Manages tasks"</span>
        admin -&gt; adminContainer <span class="hljs-string">"Manages users"</span>
        apiContainer -&gt; emailSystem <span class="hljs-string">"Sends emails"</span>
        apiContainer -&gt; calendarSystem <span class="hljs-string">"Creates tasks in Calendar"</span>

        <span class="hljs-comment"># Relationships (Containers)</span>
        webContainer -&gt; apiContainer <span class="hljs-string">"Uses"</span>
        adminContainer -&gt; apiContainer <span class="hljs-string">"Uses"</span>
        apiContainer -&gt; dbContainer <span class="hljs-string">"Persists data"</span>

        <span class="hljs-comment"># Relationships (Components &amp; Containers)</span>
        crudComp -&gt; dbContainer <span class="hljs-string">"Reads from and writes to"</span>
        webContainer -&gt; authComp <span class="hljs-string">"Authenticates using"</span>
        adminContainer -&gt; authComp <span class="hljs-string">"Authenticates using"</span>
    }
}
</code></pre>
<p>Let's dive into the most important parts:</p>
<pre><code class="lang-yaml"><span class="hljs-string">workspace</span> [<span class="hljs-string">name</span>] [<span class="hljs-string">description</span>] {
    <span class="hljs-string">model</span> {
    }
}
</code></pre>
<p>Here we define our workspace which should have at least one model. A workspace can optionally be given a name and description.</p>
<pre><code class="lang-yaml"><span class="hljs-string">customer</span> <span class="hljs-string">=</span> <span class="hljs-string">person</span> <span class="hljs-string">"Customer"</span> <span class="hljs-string">""</span> <span class="hljs-string">"person"</span>
<span class="hljs-string">admin</span> <span class="hljs-string">=</span> <span class="hljs-string">person</span> <span class="hljs-string">"Admin User"</span> <span class="hljs-string">""</span> <span class="hljs-string">"person"</span>
</code></pre>
<p>In this section we define our persons (for example, a user, actor, role, or persona) in the following format: <code>person &lt;name&gt; [description] [tags]</code>.</p>
<p>You can use a similar format (name, description, tags) to identify the external systems:</p>
<pre><code class="lang-yaml">        <span class="hljs-string">emailSystem</span> <span class="hljs-string">=</span> <span class="hljs-string">softwareSystem</span> <span class="hljs-string">"Email System"</span> <span class="hljs-string">"Mailgun"</span> <span class="hljs-string">"external"</span>
        <span class="hljs-string">calendarSystem</span> <span class="hljs-string">=</span> <span class="hljs-string">softwareSystem</span> <span class="hljs-string">"Calendar System"</span> <span class="hljs-string">"Calendly"</span> <span class="hljs-string">"external"</span>
</code></pre>
<p>To describe the internal software system we need to write a block that also shows its containers and components:</p>
<pre><code class="lang-yaml"><span class="hljs-string">taskManagementSystem</span>  <span class="hljs-string">=</span> <span class="hljs-string">softwareSystem</span> <span class="hljs-string">"Task Management System"</span>{
    <span class="hljs-string">webContainer</span> <span class="hljs-string">=</span> <span class="hljs-string">container</span> <span class="hljs-string">"User Web UI"</span> <span class="hljs-string">""</span> <span class="hljs-string">""</span> <span class="hljs-string">"frontend"</span>
    <span class="hljs-string">adminContainer</span> <span class="hljs-string">=</span> <span class="hljs-string">container</span> <span class="hljs-string">"Admin Web UI"</span> <span class="hljs-string">""</span> <span class="hljs-string">""</span> <span class="hljs-string">"frontend"</span>
    <span class="hljs-string">dbContainer</span> <span class="hljs-string">=</span> <span class="hljs-string">container</span> <span class="hljs-string">"Database"</span> <span class="hljs-string">"PostgreSQL"</span> <span class="hljs-string">""</span> <span class="hljs-string">"database"</span>
    <span class="hljs-string">apiContainer</span> <span class="hljs-string">=</span> <span class="hljs-string">container</span> <span class="hljs-string">"API"</span> <span class="hljs-string">"Go"</span> {
        <span class="hljs-string">authComp</span> <span class="hljs-string">=</span> <span class="hljs-string">component</span> <span class="hljs-string">"Authentication"</span>
        <span class="hljs-string">crudComp</span> <span class="hljs-string">=</span> <span class="hljs-string">component</span> <span class="hljs-string">"CRUD"</span>
    }
}
</code></pre>
<ul>
<li><p>Container format: <code>container &lt;name&gt; [description] [technology] [tags]</code></p>
</li>
<li><p>Component format: <code>component &lt;name&gt; [description] [technology] [tags]</code></p>
</li>
</ul>
<p>The rest of the model is the most interesting part where we define the relationships between all parts (systems, containers, components):</p>
<pre><code class="lang-yaml"><span class="hljs-string">apiContainer</span> <span class="hljs-string">-&gt;</span> <span class="hljs-string">emailSystem</span> <span class="hljs-string">"Sends emails"</span>
</code></pre>
<p>The following format is used: <code>&lt;identifier&gt; -&gt; &lt;identifier&gt; [description] [technology] [tags]</code>.</p>
<p>There are other features available in Structurizr DSL, such as styling, themes, visibility, etc. You can find find them <a target="_blank" href="https://docs.structurizr.com/dsl/language">here</a>.</p>
<h2 id="heading-automate-rendering-in-your-ci">Automate Rendering in Your CI</h2>
<p>Since you can host your models on GitHub, it is very easy to automate the pipeline for rendering the diagrams in the tools of your choice.</p>
<p>In our case, Structurizr has a GitHub Action that allows you to run <strong>structurizr-cli</strong>, a command line utility for Structurizr that lets you create software architecture models based upon the C4 model using a textual domain specific language (DSL).</p>
<p>This sample repository contains a <a target="_blank" href="https://github.com/plutov/c4-diagram-example/blob/main/.github/workflows/pages.yaml">workflow</a> that simply generates a static page and publishes it to GitHub Pages.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">static</span> <span class="hljs-string">content</span> <span class="hljs-string">to</span> <span class="hljs-string">Github</span> <span class="hljs-string">Pages</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [<span class="hljs-string">"main"</span>]

<span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>
  <span class="hljs-attr">pages:</span> <span class="hljs-string">write</span>
  <span class="hljs-attr">id-token:</span> <span class="hljs-string">write</span>

<span class="hljs-attr">concurrency:</span>
  <span class="hljs-attr">group:</span> <span class="hljs-string">"pages"</span>
  <span class="hljs-attr">cancel-in-progress:</span> <span class="hljs-literal">false</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">container:</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">ghcr.io/avisi-cloud/structurizr-site-generatr</span>
      <span class="hljs-attr">options:</span> <span class="hljs-string">--user</span> <span class="hljs-string">root</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">site</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          /opt/structurizr-site-generatr/bin/structurizr-site-generatr generate-site -w diagram.dsl
</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">website</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">build/site</span>
  <span class="hljs-attr">deploy:</span>
    <span class="hljs-attr">needs:</span> <span class="hljs-string">build</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">github-pages</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">${{</span> <span class="hljs-string">steps.deployment.outputs.page_url</span> <span class="hljs-string">}}</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/download-artifact@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">website</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">build/site</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">Pages</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/configure-pages@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">artifact</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-pages-artifact@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">"build/site"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Pages</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">deployment</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/deploy-pages@v2</span>
</code></pre>
<p>This Github Action uses Structurizr CLI action to compile our DSL file as HTML and publish it to Github Pages.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I believe that creating and maintaining effective and clear diagrams should be effortless. Up-to-date visuals ensure everyone stays on the same page, eliminating confusion and wasted time.</p>
<p>The C4 model and a bit of automation with Structurizr DSL can help make this process faster and keep diagrams close to the codebase. The whole process can now be automated as well into your SDLC.</p>
<h3 id="heading-resources">Resources</h3>
<ul>
<li><p><a target="_blank" href="https://github.com/plutov/c4-diagram-example">Github Repository</a></p>
</li>
<li><p><a target="_blank" href="https://c4model.com/">C4 Model</a></p>
</li>
<li><p><a target="_blank" href="https://docs.structurizr.com/dsl/language">DSL Language Reference</a></p>
</li>
<li><p><a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=systemticks.c4-dsl-extension">C4 DSL Visual Studio Code Extension</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/marketplace/actions/structurizr-cli-action">structurizr-cli-action</a></p>
</li>
<li><p><a target="_blank" href="https://packagemain.tech">Discover more articles from packagemain.tech</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Time To Live in Event-Driven Architecture in AWS ]]>
                </title>
                <description>
                    <![CDATA[ Distributed systems generally involve the storage and exchange of huge amounts of data. Not all data is created the same, and some of it can even expire – by design.  As the Buddha said, "All conditioned things are impermanent." In this article, we'l... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-time-to-live-in-event-driven-architecture/</link>
                <guid isPermaLink="false">66d03360537f4deb8f7a794f</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ distributed systems ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Anant Chowdhary ]]>
                </dc:creator>
                <pubDate>Wed, 19 Jun 2024 18:08:45 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/07/pexels-giallo-859895.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Distributed systems generally involve the storage and exchange of huge amounts of data. Not all data is created the same, and some of it can even <em>expire</em> – by design. </p>
<p>As the Buddha said, "All conditioned things are impermanent."</p>
<p>In this article, we'll look at how the concept of time to live can help us with this type of data and when it makes sense to use it.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><a class="post-section-overview" href="#heading-what-is-time-to-live-ttl-in-distributed-systems">What is Time to Live (TTL) in Distributed Systems?</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-ttl-in-message-queues-aws-sqs">How to use TTL in Message Queues (AWS SQS)</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-ttl-in-object-storage-systems-aws-s3">How to use TTL in Object Storage Systems (AWS S3)</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-ttl-in-databases-aws-dynamodb">How to use TTL in Databases (AWS DynamoDB)</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-ttl-in-event-based-architecture">How to use TTL in Event Based Architecture</a></li>
<li><a class="post-section-overview" href="#heading-summary">Summary</a></li>
</ol>
<h2 id="heading-what-is-time-to-live-ttl-in-distributed-systems">What is Time to Live (TTL) in Distributed Systems?</h2>
<p>TTL, as the name suggests, is the amount of time a piece of data stays relevant or stays stored in a distributed system or a component of a distributed system. A TTL may be set on any piece of data that isn't needed indefinitely.</p>
<p>Knowing when and when not to use a TTL can sometimes be tricky. It can also affect the way a system is designed, cost and scaling considerations. In the following sections, we learn about when and when not to use TTL.</p>
<h3 id="heading-where-does-ttl-make-sense">Where does TTL make sense?</h3>
<p>As mentioned above, it makes sense to use TTL for any piece of data that is ephemeral. Some common examples of use cases where you can set a TTL on data are:</p>
<ol>
<li><strong>Cached data</strong>: Cached data is pretty much omnipresent in distributed systems. For instance, a very popular social media post's resources (image, video, audio) may be cached on a CDN (Content Delivery Network)'s servers. You don't want this data to live forever on the server, so in some cases it may make sense to add a TTL to this data, so that it is automatically removed after a certain period of time.</li>
<li><strong>Analytics Data</strong>: Most if not all large scale systems store some form of metrics that help analyze things like latency, system health, and product metrics amongst others. In a large number of cases, you wouldn't want these metrics to be stored in systems forever. Only recent data (say 60 days or 180 days) may be useful in most cases. A TTL on data in this case makes sense, especially if you have constraints on memory.</li>
<li><strong>Indexed data</strong>: Search is a feature that's ubiquitous across products. Be it social media apps, e-mail or search engines – indexed data is vital to blazing fast searches. Indexed data, however, can become stale after a while, so it makes sense for the index to <em>expire</em> after some time. Hence, a TTL here can be useful.</li>
<li><strong>Social media apps with short lived content:</strong> Social media apps with short lived content are extremely popular and images/videos posted are often short lived. In case these images do not need to be stored for posterity, they can benefit from a TTL being set on them. In addition to being memory efficient, it also aids privacy.</li>
</ol>
<h3 id="heading-where-does-ttl-not-make-sense">Where does TTL not make sense?</h3>
<p>In the above section we looked at a few cases where TTL makes sense. What about cases where TTL isn't common and isn't useful? Let's look at some examples:</p>
<ol>
<li><strong>Media stored for streaming platforms:</strong> Streaming platforms often use cloud storage solutions such as Amazon AWS S3 to store objects that correspond to the media they stream to customers. These forms of media are generally not ephemeral and are expected to stay on platforms for years if not decades. Since such data isn't expected to <em>expire</em> anytime, TTL does not make sense here.</li>
<li><strong>Bank transactions:</strong> Bank transactions produce some of the most sensitive data that are stored in cloud-based and distributed systems. For audit and book-keeping purposes, these pieces of data are generally stored for decades. So, since this data seemingly <em>never</em> expires, there's generally no use for a TTL here. This isn't to say that this form of data can't be moved from fast access databases/caches to slower and cheaper forms of data storage, though.</li>
</ol>
<h2 id="heading-how-to-use-ttl-in-message-queues-aws-sqs">How to Use TTL in Message Queues (AWS SQS)</h2>
<p>AWS SQS is a distributed message queuing solution that is the backbone of many versatile distributed systems across the world. Message queues can process billions of messages and are used almost universally across distributed systems around the world. </p>
<p>In this section, we'll look at how TTLs can be useful while we consider design options with respect to message queues.</p>
<p>What happens if a message queue's consumers have been backed up for several days, or messages simply haven't been consumed for a while? We have the option of setting a custom Time To Live on SQS messages.</p>
<p><a target="_blank" href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-basic-architecture.html#">By default, the retention period is 4 days</a>. The maximum TTL at the time of writing is 14 days. So it's important to be aware of constraints such as these while using AWS SQS to design systems.</p>
<p>Note that with AWS SQS, a retention period is a set on the queue itself, and not individually for each message.</p>
<p>Boto is an AWS SDK for Python that enables developers to create, configure, and manage AWS services and resources programmatically. Boto is widely used for prototyping, production systems, and in general offers a user-friendly interface for accessing services like S3, EC2, and DynamoDB.</p>
<p>Here's a code snippet using <a target="_blank" href="https://boto3.amazonaws.com/v1/documentation/api/latest/index.html">Boto</a> that will help you set the <code>MessageRetentionPeriod</code> attribute which is the formal name for TTL in this context.</p>
<pre><code class="lang-python">sqs = boto3.client(<span class="hljs-string">'sqs'</span>, 
aws_access_key_id=your_aws_access_key_id, 
aws_secret_access_key=aws_secret_access_key, 
region_name=<span class="hljs-string">'your_region'</span>)

<span class="hljs-comment"># Set the desired retention period in seconds</span>
retention_period_seconds = <span class="hljs-number">86400</span>  <span class="hljs-comment"># Example: 1 day</span>

<span class="hljs-comment"># Set the queue attributes</span>
response = sqs.set_queue_attributes(
    QueueUrl=your_queue_url,
    Attributes={
        <span class="hljs-string">'MessageRetentionPeriod'</span>: str(retention_period_seconds)
    }
)
</code></pre>
<h3 id="heading-visibility-timeout-in-message-queues-aws-sqs">Visibility Timeout in Message Queues (AWS SQS)</h3>
<p>Note that while it's tempting to think of <a target="_blank" href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html">Visibility Timeout in SQS</a> as Time To Live, these aren't the same. Time To Live or Retention Period is different from Visibility Timeout.</p>
<p>Visibility timeout instead refers to a generally shorter period of time by which a message should be processed (once picked up by a consumer). If not, it is back in the SQS queue and visible to consumers again, with its receive count having been increased by one.</p>
<h2 id="heading-how-to-use-ttl-in-object-storage-systems-aws-s3">How to Use TTL in Object Storage Systems (AWS S3)</h2>
<p>The all-versatile AWS S3, which is an object storage solution, gives users the ability to set a Time To Live on objects stored in S3 buckets.</p>
<p>S3 is extremely flexible with the way TTLs are set on objects / buckets. You can set Lifecycle rules to specify what objects or what versions of an object you'd like to remove.</p>
<p><a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html">Managing your storage lifecycle</a> is a great read on the AWS Documentation website.</p>
<h2 id="heading-how-to-use-ttl-in-databases-aws-dynamodb">How to Use TTL in Databases (AWS DynamoDB)</h2>
<p>Some types of data in databases are prime candidates to have a TTL set on them. Pieces of data such as logs and analytics data may become stale very fast, and/or they may lose utility with time.</p>
<p>TTL in DynamoDB provides a cost-effective approach that lets you automatically remove items that are no longer relevant. It is supported natively and can be set on the whole DynamoDB table.</p>
<p>Here's a code snippet that lets you set the TTL on a DynamoDB table (again, using Boto):</p>
<pre><code class="lang-python">ddb_client = boto3.client(<span class="hljs-string">'dynamodb'</span>)

<span class="hljs-comment"># Enable Time To Live (TTL) on an existing DynamoDB table</span>
ttl_response = ddb_client.update_time_to_live(
    TableName=your_table_name,
    TimeToLiveSpecification={
        <span class="hljs-string">'Enabled'</span>: <span class="hljs-literal">True</span>,
        <span class="hljs-string">'AttributeName'</span>: your_ttl_attribute_name
    }
)

<span class="hljs-comment"># Check for a successful status code in the response</span>
<span class="hljs-keyword">if</span> ttl_response[<span class="hljs-string">'ResponseMetadata'</span>][<span class="hljs-string">'HTTPStatusCode'</span>] == <span class="hljs-number">200</span>:
    print(<span class="hljs-string">"Time To Live (TTL) has been successfully enabled."</span>)
<span class="hljs-keyword">else</span>:
    print(<span class="hljs-string">f"Failed to enable Time To Live (TTL)"</span>)
</code></pre>
<p>Here, the <code>your_ttl_attribute_name</code> attribute is the attribute that DynamoDB looks at to determine whether or not the item is to be deleted. The attribute is generally set to some timestamp in the future. When that timestamp is reached, DynamoDB removes the item from the table.</p>
<h2 id="heading-how-to-use-ttl-in-event-based-architecture">How to Use TTL in Event-Based Architecture</h2>
<p>So far we've discussed Time To Live and where it can be useful. What about its implications? Lots of cloud based solutions provide notifications that can indicate that a piece of data has indeed reached it's <em>expiration</em>, and allow you to take actions based on the expiration of that data.</p>
<p>Let's look at a common use case. Suppose you have a social media app that you're building that lets users send each other ephemeral messages. Now while the contents of these messages themselves are ephemeral, you may still want to retain a log of what users a particular user exchanged messages with, even though the contents of the message (audio/video/image) may have expired.</p>
<p>The diagram below explains a possible architecture in a little more detail:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706250152880/7c0d7b46-df5f-4d22-996f-4ea75889630e.png" alt="Image" width="600" height="400" loading="lazy">
<em>Social Media App Architecture Example</em></p>
<p>Suppose a user exchanges messages with another user. An entry corresponding to a message is stored in <em>ActiveMessageDB</em> which, for the purpose of simplicity, we'll suppose is a NoSQL database that stores messages.</p>
<p>If the app here allows for expiring messages, you could set a TTL on the entry. While the message entry itself is deleted after the TTL is reached, an event can be fired off to let a system know that the message is being deleted. </p>
<p>In the above diagram, the event is picked up by an AWS Lambda instance and a much smaller amount of data is written to another database <em>MessageLogDB</em> which isn't as frequently accessed as <em>ActiveMessageDB.</em> What we just saw is an instance of event-based architecture being coupled with TTL.</p>
<h2 id="heading-summary">Summary</h2>
<ol>
<li>TTL is the amount of time a piece of data stays relevant or stays stored in a distributed system or a component of a distributed system.</li>
<li>TTL makes sense in use cases where data can be deleted, can expire, or its form can change after a certain period of time.</li>
<li>TTL is popular and generally easy to set on many distributed systems offerings.</li>
<li>TTL can be paired with event driven architecture to transform data.</li>
</ol>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How Auto Scaling and Load Balancing Work in Software Architecture ]]>
                </title>
                <description>
                    <![CDATA[ While auto scaling and load balancing are two separate techniques in software architecture management, they are often implemented simultaneously. In the software architecture wild, one rarely exists without the other, as they complement each other to... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/auto-scaling-and-load-balancing/</link>
                <guid isPermaLink="false">66d45e06182810487e0ce135</guid>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Daniel Adetunji ]]>
                </dc:creator>
                <pubDate>Mon, 17 Jun 2024 18:52:13 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/06/image--13-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>While auto scaling and load balancing are two separate techniques in software architecture management, they are often implemented simultaneously. In the software architecture wild, one rarely exists without the other, as they complement each other to handle unpredictable changes in demand.</p>
<p>This article will explain how auto scaling and load balancing work and why they're important to consider in your designs. It will also go through example architectures showing auto scaling and load balancing in action.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-auto-scaling-explained">Auto Scaling Explained</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-dynamic-scaling">Dynamic Scaling</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-scheduled-scaling">Scheduled Scaling</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-use-auto-scaling">Why use Auto Scaling</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-load-balancing-explained">Load Balancing Explained</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-use-load-balancing">Why use Load Balancing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-bringing-it-together-load-balancing-and-auto-scaling-in-action">Bringing it Together – Load Balancing and Auto Scaling in Action</a></p>
</li>
</ol>
<h2 id="heading-auto-scaling-explained">Auto Scaling Explained</h2>
<p>Auto scaling, as its name implies, is simply a way to automatically scale your compute instances. With most cloud providers like AWS, GCP and Azure, you select scaling policies that define how it will add or remove instances.</p>
<p>Scaling policies are simply rules that say how much you should increase or decrease the number of instances based on some predefined metric.</p>
<p>Scaling policies can be dynamic, for example, by adding new instances based on CPU utilisation of the existing instances. Scaling policies can also be based on a schedule, that is based on specific times of the day or the week when you anticipate higher or lower demand.</p>
<h3 id="heading-dynamic-scaling">Dynamic Scaling</h3>
<p>Dynamic scaling is ideal for when there is a large fluctuation of demand at unknown and unpredictable times. You know there may be a sudden surge or drop in demand on your instances, you just don’t know when.</p>
<p>Using a restaurant analogy, think of an instance as a chef doing the work of converting orders into meals. If you only have three chefs and don’t have large fluctuations in demand throughout the day or week, you have nothing to worry about.</p>
<p>But if your restaurant had a sale that was more popular than anticipated, or a large party of tourists were to suddenly descend upon the restaurant, how would you cope? What if you could add more chefs on the fly immediately when needed?</p>
<p>This is how dynamic auto scaling works. Dynamic scaling will cause chefs to spontaneously appear in the kitchen, ready to transform orders into delicious meals, based on a predefined metric that you can choose to measure how overworked the chefs are – that is, how much they are struggling to fulfill current orders.</p>
<p>Remember that these scaling policies are simply rules. These rules can be very simple, like:</p>
<blockquote>
<p><em>if CPU utilisation is &gt; 50%, add one more instance. If CPU utilisation is &lt;50%, remove an instance.</em></p>
</blockquote>
<p>These rules can also be more complex.</p>
<p>With AWS and GCP, for example, you can set a target tracking metric that will monitor the CPU performance of your scaling group and add or remove instances so that that the average CPU utilisation approximately matches your desired setting.</p>
<p>For example, if you specify that you want the average CPU utilisation of your scaling group to be at 60%, instances will be added or removed as required to approximately meet that target.</p>
<p>Using CPU utilisation to trigger a scaling action is one of the most popular patterns. But CPU utilisation is not the only metric you can use to scale. In some ways, it can actually be suboptimal to use CPU utilisation, especially if you want even more responsive scaling.</p>
<p>What if you could track another metric that anticipates the increase in CPU utilisation so you don’t have to wait for the inevitable increase in the CPU utilisation of your instances before a scaling action is triggered?</p>
<p>With GCP, for example, if you have an HTTP load balancer in front of your instances, you can configure your scaling to be triggered based on the number of requests hitting your load balancer. Similarly with AWS, if you have an <a target="_blank" href="https://lightcloud.substack.com/i/70277437/messaging-queues">SQS queue</a> in front of your instances, you can scale based on the number of messages in the queue.</p>
<p>In both of these examples, something else anticipates a likely increase in future CPU utilisation, so setting a scaling action to be triggered based on this is a way of creating more responsive scaling.</p>
<p>Bringing back our restaurant analogy, this would be like calling in more chefs to the kitchen once you see a large queue outside the restaurant. This is a more responsive way of dealing with a sudden surge in demand compared to waiting until your chefs are overwhelmed with orders.</p>
<h3 id="heading-scheduled-scaling">Scheduled Scaling</h3>
<p>Scheduled scaling is ideal for when there is a large fluctuation in demand at known times.</p>
<p>Using the restaurant analogy again, your scaling policy can be based on a schedule. So for example, if you know evenings and weekends are busier than mornings and weekdays, your scaling policy will ensure that there are more chefs during periods of higher expected demand.</p>
<p>With AWS and GCP, you can set a scheduled scaling policy to add or remove instances at specific times.</p>
<h3 id="heading-why-use-auto-scaling">Why Use Auto Scaling?</h3>
<p>Auto scaling solves the age old problem of capacity planning. Trying to accurately forecast how much compute will be required in the future is fraught with errors. Too little capacity, and your website is down during periods of high demand, costing you money and reputation. Too much capacity, and you are paying for unused instances.</p>
<p>Capacity planning is fundamentally a forecasting problem. And humans are not great at accurately forecasting the future. Before cloud providers like AWS, GCP, and Azure existed, companies needed to plan capacity based on expected future demand. This planning process was often just disguised guesswork. You had to pay upfront for servers and hope you didn’t significantly under or overestimate how many servers you needed.</p>
<p>The problem with forecasting arises because we have a misguided faith in the precise measurement of the unknowable future. Humans have been making inaccurate forecasts for a long time. As far back as 600 BC, the Greek philosopher Thales was so intent on counting the stars that he kept falling into potholes on the road.</p>
<p>Some things are fundamentally unknowable, and that is ok. Auto scaling removes the need to accurately forecast future demand since you can automatically increase or decrease the number of instances you have based on your scaling policy.</p>
<p>By using auto scaling, you get to improve the resilience of your architecture and reduce costs. These are the two main reasons to use auto scaling in your designs.</p>
<h4 id="heading-improve-resilience">Improve Resilience</h4>
<p>Being able to automatically and immediately increase the number of instances in response to growing demand reduces the chances that your instances are under excessive load and at risk of poor performance. This improves the resilience of your architecture.</p>
<p>Auto scaling is, however, not only about scaling. It can also be used to maintain a set number of instances. This is a great way of creating self healing architectures.</p>
<p>With AWS, you can set your minimum, maximum, and desired number of compute instances, without any scaling policy. AWS will simply attempt to maintain the desired number of instances specified by you. So if you set the min, max, and desired all equal to one, AWS will maintain one instance for you. If this instance fails, another will be automatically created to replace the failed instance to restore your desired capacity.</p>
<p>This is a cheap and easy way of ensuring <a target="_blank" href="https://lightcloud.substack.com/i/59017006/high-availability">high availability</a> without having multiple instances in different availability zones.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F263f0886-2617-480a-af2b-232e97270a24_1559x914.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Self healing in action, figuratively</em></p>
<p>The ability to create self healing architectures is a really strong argument to almost always place your instances in an auto scaling group. AWS and GCP do not, as of this writing, charge you for the privilege of using auto scaling. You only pay for the underlying infrastructure that is created to support your instances.</p>
<p>So, even if there is no requirement to be able to scale instances based on the demand thrown at them, having instances in an auto scaling group is a cheap and easy way of creating a self healing architecture.</p>
<h4 id="heading-reduce-cost">Reduce Cost</h4>
<p>Previous examples have been about scaling up the number of instances to meet higher demand. But equally as important is the ability to scale down during periods of lower demand.</p>
<p>Auto scaling allows you to do this using scheduled or dynamic scaling policies. This is a great way of ensuring that you are not paying for more than you need to.</p>
<h2 id="heading-load-balancing-explained">Load Balancing Explained</h2>
<p>Load balancers accept connections from clients and distribute the requests across target instances. The distribution of requests is usually done on layer 7 (application layer) or layer 4 (transport layer). These layers are a theoretical model that organises computer networking into 7 layers and is <a target="_blank" href="https://www.freecodecamp.org/news/osi-model-networking-layers-explained-in-plain-english/">know as the OSI model</a>.</p>
<p>I won't go into too much detail on the OSI model here, but for now, what is important to know is that most load balancers can work on the application layer or transport layer. This means that they work with layer 7 protocols like HTTP(S) or layer 4 protocols like TCP, UDP, SMTP, SSH.</p>
<p>The example in this section will only cover the more popular layer 7 application load balancers that work with HTTP/HTTPS.</p>
<p>While the low level implementation details and use cases between layer 7 and layer 4 load balancers are different, the principles remain the same. Load balancers are used to distribute incoming traffic across a number of target instances</p>
<p>The distribution of the requests among the target instances typically uses a round robin algorithm where requests are sent to each instance sequentially. So, request #1 goes to instance #1, request #2 to instance #2, request #3 to instance #3, request #4 again comes to instance #1, and so on.</p>
<p>While other balancing algorithms exist, the round robin algorithm is the most popular one used by most cloud providers for load balancing.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c5a66a2-31da-471e-a753-44b75dd78708_1898x1490.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>A simple view of how load balancers distribute requests</em></p>
<p>The diagram above is a logical depiction of how load balancers work. It only shows one load balancer, which is not a very resilient design. This logical abstraction is easy to illustrate, but is not accurate.</p>
<p>Behind the scenes, multiple load balancer nodes are deployed into each subnet within an availability zone. The load balancer is created with a single DNS record that points at all the elastic load balancer nodes created – that is, this single DNS record points at all of the IP addresses of the actual nodes deployed. All incoming requests are distributed equally across all the load balancer nodes and the load balancer nodes in turn equally distribute requests to target instances. In this way, you don’t have a single point of failure.</p>
<p>A more realistic, albeit more complex, representation of how load balancers work is shown below. In this example, requests will come to any of the load balancer nodes deployed across the three subnets and then they are equally distributed across the target instances.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14072032-eb00-4b67-b253-1e293331b732_1938x1665.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>A more accurate view of how load balancers distribute requests</em></p>
<h3 id="heading-why-use-load-balancing">Why Use Load Balancing?</h3>
<p>Load balancers ensure that traffic is distributed among the target instances. This spreads out the load and prevents a single instance from being overloaded with an excessive number of requests.</p>
<p>Load balancers also create a loosely coupled architecture. Loose coupling is generally sought because it mans that users don't have to be aware of the instances, or instances don't need to be aware of other instances.</p>
<p>What exactly does being “aware” mean? Since user requests are first sent to the load balancer, users are not aware of the instances actually responding to their request. All communication is done via the load balancer, so it becomes easy to change the type and number of instances without the user being aware of it. The load balancer is aware of the instances in its target so it can send the request to all relevant instances.</p>
<h2 id="heading-bringing-it-together-load-balancing-and-auto-scaling-in-action">Bringing it Together – Load Balancing and Auto Scaling in Action</h2>
<p>The diagram below shows load balancing and auto scaling used for a three tiered web application consisting of web, application, and database tiers. Each of these tiers have separate instances/infrastructure.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27dcd71d-6672-4f40-9dd4-1389a42869d7_1405x1923.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Load balancing and auto scaling used for a three tiered web application consisting of web, application, and database tiers.</em></p>
<p>The instances in the web and application tiers are in separate auto scaling groups. There is also a load balancer between the user and the web tier, and between the web tier and the application tier.</p>
<p>By having a load balancer between the user and the web tier, the web tier can scale independently, using the auto scaling feature to add or remove instances as needed.</p>
<p>The user does not need to know which instance to connect to as the connection is through a load balancer. This is loose coupling in action. The same logic applies between the web tier and application tier. Without the load balancer, the instances in the two tiers would be tightly coupled, making scaling difficult.</p>
<p>The database tier in this case is an RDS database with one master and two standby nodes. All reads and writes go to the master node and if this node fails, there is an automatic failover to one of the standby instances.</p>
<p>Auto scaling ensures:</p>
<ol>
<li><p><strong>Resilience</strong>, as it can automatically and immediately increase the number of instances in response to growing demand. It can also self heal, so even if you don’t anticipate the need for immediate and automatic scaling based on changes to demand, self healing is almost always desired as it increases the availability of your architecture</p>
</li>
<li><p><strong>Cost control</strong>, as it has the ability to scale in and reduce the number of instances used during periods of lower demand can save you money</p>
</li>
</ol>
<p>Load balancing ensures:</p>
<ol>
<li><p><strong>Distribution of load</strong>, as it prevents a single node being overloaded with requests</p>
</li>
<li><p><strong>Loose coupling</strong>, as it removes the need for awareness between users and instances, and between instances themselves. This allows for instances to scale independently</p>
</li>
</ol>
<p>Thank you for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Microservices vs Monoliths: Benefits, Tradeoffs, and How to Choose Your App's Architecture ]]>
                </title>
                <description>
                    <![CDATA[ When you're tasked with designing an application, one of the first questions that probably comes to your mind is whether to design a microservice or a monolith. The consequences of this seemingly simple and innocuous decision are potentially signific... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/microservices-vs-monoliths-explained/</link>
                <guid isPermaLink="false">66d45e1b4a7504b7409c3372</guid>
                
                    <category>
                        <![CDATA[ Microservices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Daniel Adetunji ]]>
                </dc:creator>
                <pubDate>Tue, 14 May 2024 00:18:44 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/05/cover--3-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you're tasked with designing an application, one of the first questions that probably comes to your mind is whether to design a microservice or a monolith.</p>
<p>The consequences of this seemingly simple and innocuous decision are potentially significant, and they're often not fully thought through. A wrong decision can be very expensive, not just financially, but also expensive with regard to the time required to develop the application and the time required to deploy any future changes.</p>
<p>There is no objectively correct approach, though. It all depends on what problem you are trying to solve and what trade-offs you are able to live with.</p>
<p>This article will explain the differences between monoliths and microservices as well as some heuristics to help you decide how to choose between the two architectures.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-monoliths-vs-microservices-an-analogy">Monoliths vs Microservices: An Analogy</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-monolith">What is a Monolith?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-microservices">What are Microservices?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-data-management-in-microservices">Data Management in Microservices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-database-isolation-in-microservices">Database Isolation in Microservices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-choose-between-monoliths-and-microservices">How to Choose Between Monoliths and Microservices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-you-should-start-with-a-monolith">Why you should start with a Monolith</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-you-should-start-with-a-microservice">Why you should start with a Microservice</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-hybrid-architecture-a-middle-ground">Hybrid Architecture – A Middle Ground</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-bringing-it-together">Bringing it Together</a></p>
</li>
</ol>
<h2 id="heading-monoliths-vs-microservices-an-analogy">Monoliths vs Microservices: An Analogy</h2>
<p>Before we go into the technical details of monoliths and microservices, let’s quickly explain the difference between the two architectures using an analogy.</p>
<p>A monolithic architecture is like a typical restaurant, where all kinds of dishes are prepared in one large kitchen and a single menu is presented to guests to choose from.</p>
<p>Just as the restaurant offers everything from starters to desserts in one place, a monolith includes all functionalities in one codebase.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a75fc63-2d14-4379-819f-24cfa8c9d8fe_1504x603.png" alt="Image" width="1456" height="584" loading="lazy"></p>
<p><em>A typical restaurant is like a monolithic application</em></p>
<p>A microservice architecture is like a food court composed of several small, specialised stalls, each serving a different type of cuisine. Here, you can pick and choose dishes from various stalls, each expertly preparing its own menu.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d8efa02-6ab9-4013-bc09-18343063139a_2462x1394.png" alt="Image" width="1456" height="824" loading="lazy"></p>
<p><em>A food court is like a microservice application</em></p>
<p>In a microservice architecture, the application is divided into smaller, independent services. Just as each stall in the food court manages its own menu, staff, and kitchen, in a microservice architecture, different services run separately and are responsible for handling their specific functionalities.</p>
<p>Customers can pick and choose dishes from any stall, mixing and matching as they like, just as different microservices can be used in combination to create a comprehensive application. Each service is self-contained and communicates with other services through simple, well-defined interfaces.</p>
<h2 id="heading-what-is-a-monolith">What is a Monolith?</h2>
<p>In a monolith, all the code needed for the all the features of the application is in a single codebase and gets deployed as a single unit.</p>
<p>Let's look at an e-commerce application, for example. Some of the important features of an e-commerce application are:</p>
<ol>
<li><p><strong>Product search service</strong>: Manages product listings, descriptions, inventory, prices, and categories. It's responsible for providing up-to-date information about products to other services and users.</p>
</li>
<li><p><strong>Payment service</strong>: Handles processing of payments and transactions. It interacts with external payment gateways and provides secure payment options to customers.</p>
</li>
<li><p><strong>Order management service</strong>: Manages the lifecycle of customer orders from creation to completion. This includes handling order processing, status updates and order cancellation.</p>
</li>
<li><p><strong>Recommendation service</strong>: Provides personalised product recommendations to users based on their search history and past purchases.</p>
</li>
</ol>
<p>In a monolithic application, the code for these features will be in a single codebase and deployed as a single unit. This is illustrated in the image below where the application is deployed to a single server with a separate database.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35d7463a-4c95-4f64-81c1-7a41bdb21d45_2246x752.png" alt="Image" width="1456" height="487" loading="lazy"></p>
<p><em>Monolithic e-commerce application deployed on a single server</em></p>
<p>The database is hosted on a separate server to improve performance and security, while the application servers handle the business logic.</p>
<p>Even in a monolithic architecture, the application can be duplicated and deployed across multiple servers, with a load balancer distributing traffic between the servers. This is illustrated below:</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F808895de-b53d-4b39-9adf-55007d185976_2442x1358.png" alt="Image" width="1456" height="810" loading="lazy"></p>
<p><em>Monolithic e-commerce application deployed on two separate servers</em></p>
<h2 id="heading-what-are-microservices">What are Microservices?</h2>
<p>Microservices are independently deployable services modeled around a business domain.</p>
<p>In contrast to a monolithic architecture, where all the application components are tightly integrated and deployed as a single unit, a microservices architecture breaks down the application into smaller, independently deployable services. Each service runs its own process and communicates with other services over a network, typically using <a target="_blank" href="https://lightcloud.substack.com/i/137067496/rest">HTTP/REST</a>, <a target="_blank" href="https://lightcloud.substack.com/i/137067496/rpc">RPC</a>, or message queues.</p>
<p>We can brea the monolithic e-commerce application we talked about above down into a microservice architecture, as shown below:</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c9f2b4-fb93-411d-88dc-330628b222f5_2440x1022.png" alt="Image" width="1456" height="610" loading="lazy"></p>
<p><em>Microservice e-commerce application</em></p>
<p>The following are some key differences between the monolithic and microservices e-commerce application:</p>
<p>In the microservice architecture, every feature of the application is in a separate codebase. This separation ensures we have independently deployable services modeled around business domains (Product Search Service, Payment Service, Order Management Service and Recommendation Service).</p>
<p>Having a separate codebase for every service ensures:</p>
<ol>
<li><p><strong>Simplified deployment:</strong> With each service in its own codebase, it can be updated, tested, and deployed independently of others.</p>
</li>
<li><p><strong>Fault Tolerance</strong>: Separate codebases contribute to fault tolerance. If one service experiences a failure, it does not necessarily compromise the operation of others. This is crucial for maintaining the overall system's availability and reliability. For example, if the payment service fails, only customers that want to purchase an item will be affected. Other customers can still search through the application for things to buy, track existing orders, and get recommendations for things they might want to buy.</p>
</li>
<li><p><strong>Technology Flexibility</strong>: Separate codebases allow each service to be developed using the technology stack best suited to its needs. Different teams can choose different programming languages, frameworks, or databases depending on what works best for the specific functionality of that service.</p>
</li>
<li><p>Each service is deployed on its own servers. The servers hosting each service can be scaled independently based on its specific demand and resource requirements. This is much more efficient than scaling a monolithic application where scaling up often means scaling the entire application, even if only one part of it is under heavy load. For example, the payment service might be really busy during a promotion/sale. This can be independently scaled instead of scaling the entire application, which can be a waste of money.</p>
</li>
</ol>
<p>Each service has its own database (if it needs a database). This ensures:</p>
<ol>
<li><p>Every microservice can run independently of other services. If every service used the same database (as is the case in a monolithic application), a database failure will bring down the entire application.</p>
</li>
<li><p>The databases can be scaled independently as needed. Some databases will be busier than others, so having the flexibility to scale them independently is useful.</p>
</li>
<li><p>Every microservice uses the right type of database. Some microservices might function better with different types of databases. For example, Elasticsearch would be ideal for the product search database of the e-commerce application due to its powerful full-text search capabilities, while a relational SQL database will be better suited for the order and payment databases.</p>
</li>
<li><p>An <a target="_blank" href="https://lightcloud.substack.com/p/api-gateway-explained">API Gateway</a> sits in front of the services. This acts as the middle-man between users and the many services they may need to access. The API Gateway handles <a target="_blank" href="https://lightcloud.substack.com/i/138365595/authorisation-and-authentication">authorisation and authentication</a>, <a target="_blank" href="https://lightcloud.substack.com/i/138365595/request-routing">request routing</a> and <a target="_blank" href="https://lightcloud.substack.com/i/138365595/rate-limiting">rate limiting</a>.</p>
</li>
</ol>
<h3 id="heading-data-management-in-microservices">Data Management in Microservices</h3>
<p>Managing data between services is the most complex part of a microservice architecture. Communication between services is either synchronous or asynchronous.</p>
<p><strong>Synchronous Communication:</strong> Services communicate directly with each other. This is a straightforward approach, easy to understand and implement.</p>
<p>For example, in an e-commerce application, when a customer places an order, the Order Management Service might directly call the Product Search Service to check if the item is in stock before proceeding.</p>
<p><strong>Asynchronous Communication:</strong> Services do not wait for a direct response from another service. Instead, they communicate through events or messages using a message broker.</p>
<p>In the e-commerce example, when a new order is placed, the Order Management Service will publish an "Order Created" event to a message queue. The Product Search Service, subscribing to this queue, reacts to the event at its own pace and updates the inventory accordingly. This decouples the services, allowing them to operate and scale independently.</p>
<p>Synchronous communication is simpler to understand and implement but lacks <a target="_blank" href="https://lightcloud.substack.com/i/59017006/fault-tolerance">fault tolerance</a>.</p>
<h3 id="heading-database-isolation-in-microservices">Database Isolation in Microservices</h3>
<p>In a microservice architecture, it is a standard practice to prevent services from directly accessing the databases of other services. You'd typically do this to ensure that each service can manage its data schema independently, without affecting other services.</p>
<p>Looking back at our e-commerce example, suppose the Payment Service decides to change its data schema and rename a column called “amount” to “order_value”, as “amount” can be quite an ambiguous term. If the Order Management Service were directly querying the Payment Service’s database, any direct SQL queries from the Order Management Service to the Payment Service’s database on this column would fail because of this schema change.</p>
<p>To handle these dependencies and changes securely and efficiently, the services should interact via APIs rather than via direct database access. By providing an API as an interface, the Payment Service can abstract the complexities of its underlying data model.</p>
<p>For instance, regardless of whether the database field is named “amount” or “order_value”, the API can expose a parameter called “payment_amount”. This allows the Payment Service to internally map “payment_amount” to whatever the current database schema is using.</p>
<h2 id="heading-how-to-choose-between-monoliths-and-microservices">How to Choose Between Monoliths and Microservices</h2>
<p>Choosing between a monolith and a microservice architecture depends on what problem you are trying to solve and what trade-offs you are able to live with.</p>
<p>Microservices are newer and more popular with the large technology companies. Most technical books and blogs cover the architectures of these large companies.</p>
<p>But the engineering problems of large companies operating at scale are not necessarily the same engineering problems faced by smaller companies.</p>
<p>Copying what the large technology companies do is reasoning by analogy. This is not necessarily wrong, but it can introduce unnecessary complexities for a smaller company/startup. Better to reason by first principles, or better yet, choose better analogues.</p>
<p>You can look at what other startups are doing, or what the technology giants of today did when they were much smaller. For example, <a target="_blank" href="https://blog.dreamfactory.com/microservices-examples/">Etsy, Netflix and Uber</a> all started as monoliths before migrating to a microservice architecture.</p>
<h3 id="heading-why-you-should-start-with-a-monolith">Why you should start with a Monolith</h3>
<p>Creating an application should be done for one reason and one reason alone: to build something that people want to use. Users of your application do not care if you use a microservice or monolith. They care that you are solving a problem for them.</p>
<p>To quote <a target="_blank" href="https://paulgraham.com/startuplessons.html">Paul Graham</a>:</p>
<blockquote>
<p>“Almost everyone’s initial plan is broken. If companies stuck to their initial plans, Microsoft would be selling programming languages and Apple would be selling printed circuit boards. In both cases, their customers told them what their business should be and they were smart enough to listen”.</p>
</blockquote>
<p>There is arguably no need to spend so much time designing and implementing a highly complex microservice architecture when you are not even sure that you are building something that people want to use.</p>
<p>So, why should you start with a monolith when building an application?</p>
<ol>
<li><p><strong>Simplicity</strong>: A monolith does not require dealing with the complexities of a distributed system, such as network latency, data consistency, or inter-service communication. This lack of complexity not only makes the initial development phase smoother but also reduces the overhead for new developers, who can contribute more quickly without having to understand the intricacies of a distributed system</p>
</li>
<li><p><strong>Ease of Iteration</strong>: In the early stages of a product, rapid iteration based on user feedback is critical. The product direction is still evolving, and quick pivots or adjustments are necessary based on user input. This is usually easier to achieve with a simple monolithic architecture.</p>
</li>
<li><p><strong>Low Cost</strong>: Running a monolithic application can be less expensive in the early stages, as it typically requires less infrastructure and fewer resources than a distributed microservices architecture. This is crucial for startups and small businesses where money can be in short supply.</p>
</li>
</ol>
<p>Beginning with a monolith often aligns better with the practical realities of launching and iterating on a new application.</p>
<h3 id="heading-why-you-should-start-with-a-microservice">Why you should start with a Microservice</h3>
<ol>
<li><p><strong>Scalability from the Start:</strong> One of the strongest arguments for microservices is their innate ability to scale. If you anticipate rapid growth in usage or data volume, microservices allow you to scale specific components of the application that require more resources without scaling the entire application. This can be particularly valuable for applications expected to handle varying loads or for services that might grow unpredictably.</p>
</li>
<li><p><strong>Resilience:</strong> Microservices enhance the overall resilience of the application. Because each service is independent, failures in one area are less likely to bring down the whole system. This isolation helps in maintaining resilience by ensuring that parts of your application can still function even if others fail.</p>
</li>
<li><p><strong>Flexible Tech Stacks:</strong> Microservices allow different teams to use the technology stacks that are best suited for their specific needs. Going back to our e-commerce example, the other services may be written in Java, but the recommendation service can be written in Python if the team responsible for building that has more expertise in Python. This is a very crude example, but the principle holds. A microservice architecture gives teams flexibility on which technology they can use. Taken to its logical extreme, this can also be a flaw since it can add additional complexity to the overall architecture. Introducing a different language for a service might require different build tools and deployment processes.</p>
</li>
</ol>
<h2 id="heading-hybrid-architecture-a-middle-ground">Hybrid Architecture – A Middle Ground</h2>
<p>The formal, academic definition of a microservice is that it is an independently deployable service modeled around a business domain. Under the thumb of this definition, each business domain should be a separate service.</p>
<p>But you're not confined to this strict definition when it comes to implementing a design. Let’s look at our e-commerce microservice application again.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc244781f-08a0-42b1-a928-ddc95e02d437_2440x1022.png" alt="Image" width="1456" height="610" loading="lazy"></p>
<p><em>Microservice e-commerce application</em></p>
<p>We can choose to keep the product search service as a microservice. Since more people search for products than buy them, we may want the ability to scale this service independently of the others.</p>
<p>Also, this service will need its own dedicated full text search database like Elasticsearch or Solr. SQL databases are not well-suited for full text search and product filtering.</p>
<p>We can also choose to keep the recommendation service as a microservice since this will be written in a different language from the other services. This service will also need its own separate graph database like Neo4j to help make recommendations to users about what to buy based on their past searches and purchases.</p>
<p>We are left with the payment service and the order management service which can be combined into a monolith. This is illustrated below.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F113147b6-7a41-49a1-a4ad-88130de2f9fd_2334x922.png" alt="Image" width="1456" height="575" loading="lazy"></p>
<p><em>Hybrid monolithic/microservice architecture</em></p>
<p>In this example, we haven’t followed the academic definition of a microservice architecture, where every service is modeled around a business domain. Instead, we have chosen to be pragmatic and create microservices because we want to use a specific technology and because we want to be able to scale some services independently.</p>
<h2 id="heading-bringing-it-together">Bringing it Together</h2>
<p>In a monolith, all the code needed for the all the features of an application is in a single codebase and is deployed as a single unit. In a microservices architecture, the application is divided into smaller, independent components, each responsible for specific features or functionalities. Each microservice has its own codebase and can be deployed independently of others.</p>
<p>Choosing between a monolith and a microservice depends on the problem you are trying to solve and what trade-offs you are able to live with.</p>
<p>Monoliths provide simplicity, ease of iteration and low cost. Microservices provide scalability, resilience and a more flexible tech stack.</p>
<p>For startups, the simplicity, ease of iteration, and cost-efficiency of a monolithic architecture make it an ideal initial choice, allowing them to focus on developing core features and finding product-market fit without the overhead of managing a distributed system.</p>
<p>For a more established company with growing needs for scalability, resilience, and technological flexibility, a microservice architecture can be a better choice.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Design and Build Sustainable Software ]]>
                </title>
                <description>
                    <![CDATA[ Electronic devices that run software are all around us. Your mobile phone, your car, your microwave, and virtually anything around you – there’s a very good chance that they have electronic components running some form of software. These devices inev... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/design-and-build-sustainable-software/</link>
                <guid isPermaLink="false">66b99d6465fc624db0255e0b</guid>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ sustainability ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jayant Chowdhary ]]>
                </dc:creator>
                <pubDate>Thu, 18 Jan 2024 01:28:38 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/Cover-4.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Electronic devices that run software are all around us. Your mobile phone, your car, your microwave, and virtually anything around you – there’s a very good chance that they have electronic components running some form of software.</p>
<p>These devices inevitably use power and have batteries and circuits, which degrade over time. As time passes, these devices get replaced by newer upgraded ones. But this also means that more and more electronic waste builds up on our planet. </p>
<p>Also, since we have more electronic devices entering our ecosystem every day, we’re also using more power, leading to more carbon emissions. </p>
<p>This means we need to design both hardware and software in a way which acknowledges that the earth’s resources are limited. This article will give you an introduction on how you can architect your software for sustainability. </p>
<h3 id="heading-here-is-what-well-cover">Here is what we'll cover:</h3>
<ul>
    <li><a href="#prerequisites">Prerequisites</a> </li>
    <li><a href="#whyarewethinkingaboutthisnow">Why are we thinking about this now?</a> </li>
    <li><a href="#whatsinvolvedinarchitectingandprogrammingforsustainability">What's involved in architecting and programming for sustainability?</a></li>
    <li><a href="#profileyoursoftwareforpowerandenergyconsumption">Profile your software for power and energy consumption</a>
    <ul><li><a href="#energydelayproductedt">Energy Delay Product</a></li></ul>
    <ul><li><a href="#greenuppowerupandspeedup">Greenup, Powerup and Speedup</a></li></ul>
    <ul><li><a href="#toolsforprofiling">Tools for Profiling</a></li></ul></li>
    <li><a href="#softwaretechniquesanddesignpatternsforsustainability">Software Techniques and Design Patterns for Sustainability</a>

   <ul><li><a href="#considerwhichprogramminglanguagetouse"> Consider which programming language to use </a></li></ul>
   <ul><li><a href="#makesoftwarepowerawarereacttothermalevents">Make software Power aware: React to Thermal events </a></li></ul>

    <ul><li><a href="#whenappropriateuselowpowerstatesonhardware"> When appropriate use low power states on hardware </a></li></ul>
    <ul><li><a href="#investigatethetradeoffsbetweeninterruptdrivenioandpolling"> Investigate the tradeoffs between Interrupt driven I/O and Polling </a></li></ul>
     <ul><li><a href="#lookintocaching"> Look into Caching </a></li></ul>
     <ul><li><a href="#designsoftwarewithlifetimeandupdatabilityinmind"> Design software with lifetime and updatability in mind</a></li></ul></li>
     <li><a href="#conclusion">Conclusion</a>
    </li>
</ul>

<p>##Prerequisites</p>
<p>I wrote this article with a focus on general ideas in computer science. This <a target="_blank" href="http://wla.berkeley.edu/~ee42/sp01/LectNotes/Lect6.PDF">lecture</a> will help with some background on Power and Energy in electrical circuits. </p>
<p>Other parts of this article provide references wherever you might need some background.</p>
<h2 id="heading-why-are-we-thinking-about-this-now">Why Are We Thinking About this Now?</h2>
<p><a target="_blank" href="https://climate.nasa.gov/scientific-consensus/">Human made climate change</a> has been steadily accelerating for decades. Carbon emissions have been rising. As a part of the technology community, we can help reduce some factors which have been contributing towards the degradation of the earth’s environment. </p>
<p>In this article I will present a few ideas and techniques on how you as a programmer and a software architect can make choices which will lead to more sustainable technology.</p>
<h2 id="heading-whats-involved-in-architecting-and-programming-for-sustainability">What's Involved in Architecting and Programming for Sustainability?</h2>
<p>In the context of this article, when I describe architecting software and programming for sustainability, I’m referring to the following aims: </p>
<p>Designing software so that it:</p>
<ol>
<li>Consumes the smallest amount of energy possible, to get the task at hand done.</li>
<li>Results in the least amount of battery degradation possible, while maintaining the minimum amount of performance needed for the task.</li>
<li>Needs a minimum amount of cooling for the hardware that it is running on.</li>
<li>Results in devices lasting longer.</li>
</ol>
<p>You’ll see that the techniques we'll discuss here all have the same theme behind them: make electronic hardware do the minimum amount of work possible to complete the task at hand. </p>
<p>Let's start with some techniques that you can use to accomplish this while architecting and writing software.</p>
<p>##Profile Your Software for Power and Energy Consumption</p>
<p>A problem which cannot be measured, cannot be solved. As a result, measuring the energy and power consumption of software is one of the most important tasks we need to do when designing with sustainability in mind. </p>
<p>Let’s take a step back and cover some of the basics of power and energy. </p>
<p>As you may know, power is the rate at which energy is consumed. That is:</p>
<p>$$P = dE/dt$$</p><p>In continuous time domain, we could also say that:</p>
<p>$$Energy = \int_{0}^{t} P \,dt$$</p><p>Practically, we never really get power measurements in the continous time domain. We usually have discrete power measurements over an interval of time. So our Power vs time graph might look something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/image-94.png" alt="Image" width="600" height="400" loading="lazy">
<em>Figure 1: Power vs time and computing energy consumption</em></p>
<p>Since we have <code>n</code> discrete Power measurements over time, the energy consumption can be estimated as the area under the curve. This can be modeled as the sum of <code>n-1</code>  trapezoids.</p>
<p> (Energy = \sum_{i=1}^{n-1} A^{i}) where  (A^{i}) is the area of the ith trapezoid.</p>
<p>Profiling software for energy consumption refers to the process of measuring the energy consumed by the software being run.</p>
<p>So should you be profiling power or energy ? The answer is – it depends. In some scenarios which involve heavy user interaction (where the user controls the amount of time spent), it makes more sense to measure power. </p>
<p>For example: When a user is watching a video on a laptop or a mobile phone, they control many things such as the duration of the video watched, the screen’s brigthness, speaker volume, and so on. </p>
<p>In other scenarios where the user doesn’t have as much control over the work being performed by the electronic device, it makes sense for energy to be profiled. An example of such a case might be the energy needed to send a message via SMS / IP messaging service (not counting the energy needed to type out the message).</p>
<p>Let's look at some common metrics for profiling energy consumption next.</p>
<p>###Energy Delay Product (EDT)</p>
<p>Used in the research paper <a target="_blank" href="https://discovery.ucl.ac.uk/id/eprint/10074516/1/GKLS18_MSR18.pdf">What are your programming language's Energy delay implications?</a>, the energy delay product is a weighted metric which is defined as follows:</p>
<p> $$EDT = E *  T^{w}$$</p>
<p>where E is the Energy consumption for the task and T is the time taken for the task to be completed.</p>
<p>This metric aims to give us a measure of how much energy an operation takes, while at the same time penalizing the metric for the amount of time taken. That is, an operation consuming the same amount of energy but taking longer to perform by a system, has a higher EDT and is therefore considered less energy efficient.</p>
<p>The weight <code>w</code> can be chosen to be:</p>
<ul>
<li>1: When energy efficiency is of major concern</li>
<li>2: When both energy and performance are important</li>
<li>3: When performance is more important than energy efficiency </li>
</ul>
<p>So, you can tailor <code>w</code> to give you an idea of your software system’s energy and performance, based on indicators fit for your use case – whether energy is more important or performance.</p>
<h3 id="heading-greenup-powerup-and-speedup">Greenup, Powerup, and SpeedUp</h3>
<p>In the thesis <a target="_blank" href="https://digital.library.txst.edu/items/e8174fcc-4799-4612-88ad-5e3b7e3c3efb">Using the Greenup, Powerup And Speedup Metrics To Evaluate Software Energy Efficiency</a>, AbdulSalam et al introduced new metrics to measure energy efficiency. </p>
<p>They saw that EDT had one shortcoming: since it was a product of two quantities (Energy and weighted time taken to complete the task at hand), it was possible that two systems which had the same EDT for a task, actually differed when it came to energy efficiency and performance - but their EDT was still the same. </p>
<p>As a result, it was hard to conclude which system was better from an energy + performance point of view, where energy and performance had equal importance.</p>
<p>They introduced 3 metrics to address this issue.</p>
<ol>
<li><strong>Speedup</strong>, which is defined as:</li>
</ol>
<p>$$Speedup = T_{base} / T_{opt}$$</p><p>where (T<em>{base}) = Time taken to complete the task for the unoptimized case, (T</em>{opt}) = Time to complete the task for the optimized case. </p>
<p>If the optimized case is more performant than the unoptimized case (since remember, we could be optimizing for only energy, only performance, or both) then Speedup &gt; 1.</p>
<ol start="2">
<li><p><strong>Greenup</strong>, which is defined as:</p>
<p>$$Greenup = Energy_{base} / Energy_{opt} = P_{base} * T_{base} / P_{opt} * T_{opt}$$</p><p>. </p>
</li>
</ol>
<p>Here (P<em>{base}) is the average power consumed by the task in the unoptimized case and similarly, (P</em>{opt}) is the average power consumed by the task in the unoptimized case.</p>
<p>If we look at different values of Speedup and Powerup, they can fall into the following categories:</p>
<ol>
<li><strong>Zone 1</strong>: Powerup &lt; 1 and Speedup &lt; 1 and Speedup &gt; Powerup – in this scenario, the optimized solution sacrificed some performance, but there was a greater power reduction. As a result Greenup  &gt; 1, so there are energy savings.</li>
<li><strong>Zone 2</strong>: Powerup &lt; 1, Speedup &gt; 1 – in this scenario, the optimized solution improved in performance and at the same time, reduced power consumption. As a result, energy consumption decreased and performance improved. This is the best case scenario for any optimization.</li>
<li><strong>Zone 3</strong>: Powerup &gt; 1, Speedup &gt; 1 and Speedup &gt; Powerup – in this case, average power consumption improved, but the speedup more than made up for the increase in power consumption. As a result, energy consumed still reduced between the optimized and unoptimized solutions.</li>
<li><strong>Zone 4</strong>: Powerup &gt; 1, Speedup &gt; 1 and Powerup &gt; Speedup – in this case, power consumption decreased, but so did performance. Still, the energy consumption as a whole increased since the loss of performance was greater than the power savings.</li>
<li><strong>Zone 5</strong>: Powerup &gt; 1, Speedup &lt; 1 – in this case, the energy consumption increased since there was a performance degradation and also there was an increase in power consumption.</li>
<li><strong>Zone 6</strong>: Powerup &lt; 1, Speedup &lt; 1, Powerup &gt; Speedup – in this case, performance improved, but power increased more than the amount that performance improved. As a result, the energy consumption increased as a whole.</li>
</ol>
<p>The figure below (inspired by this <a target="_blank" href="https://digital.library.txst.edu/items/e8174fcc-4799-4612-88ad-5e3b7e3c3efb">thesis)</a> shows the zones where energy consumption increases and where it decreases. The red (4, 5 and 6) and the green areas (1, 2 and 3) depict these zones.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/image-90.png" alt="Image" width="600" height="400" loading="lazy">
<em>Figure 2 : The different zones for Speedup and Powerup</em></p>
<p>We went over a couple of metrics which both have their strengths and weaknesses. EDT’s strength is that you can give more weightage to performance. But its weakness is that many a time, it may not tell you whether one solution is really better than the other. </p>
<p>While knowing about energy consumption is very important, you should not ignore power consumption. High power consumption, amongst other effects, can have adverse thermal impacts on electronic devices. That is, your device’s skin and component temperature can increase. This may lead to: </p>
<ol>
<li><strong>Component damage</strong>: Electronic components such as memory, wires, and capacitors on your device can get damaged as temperature increases.</li>
<li><strong>Battery damage</strong>: Batteries can get damaged at higher temperatures. Their <a target="_blank" href="https://www.intercel.eu/frequently-asked-questions/temperature-effects-on-batteries/#:~:text=Battery%20capacity%20is%20reduced%20by,%2C%20AGM%2C%20industrial%20or%20whatever.">life</a> may decrease and in some extreme cases they may also <a target="_blank" href="https://www.sciencedirect.com/science/article/pii/S2215098618310000">explode</a>.</li>
</ol>
<p>Commercial electronics are usually built to operate within a 0 - 70 degrees Celsius temperature range. But for mobile phones it is usually recommended that the operating temperature not exceed 35 degrees celsius. </p>
<p>Before you profile the energy and power being consumed by a specific task on your devices, it's important to isolate the task as much as possible. </p>
<p>By this, I mean that its important that we take out as many other power consuming tasks or variables. These may be other services / daemons or settings on the device which may affect the power consumed in unreliable ways. </p>
<p><strong>For example</strong>, if the device uses the internet, background processes may be doing some activities which may vary between multiple profile runs, so in general it is a good idea to switch off the internet (unless the task being measured uses the internet). We want to do this since we want reproducible power profiling numbers from run to run. </p>
<p>So in general, it is a good idea to make a checklist of the conditions which need to be constant for power profiling to be reliable and reproducible. This could vary from system to system.</p>
<p>##Tools for Profiling</p>
<p>Now, let's talk about how power can be profiled. Different systems may offer different ways of profiling energy consumed. </p>
<p>Depending on the operating system and the underlying hardware, there are a few options. I won’t go into details of these tools since there are many options and there is also good documentation available on their official websites. </p>
<p>###Linux</p>
<ol>
<li><a target="_blank" href="https://manpages.ubuntu.com/manpages/xenial/man8/powerstat.8.html">PowerStat</a> is a tool that can measure power consumption on Intel hardware that supports the <a target="_blank" href="https://sustainable-computing.io/design/kepler-energy-sources/#:~:text=Intel's%20Running%20Average%20Power%20Limit,versions%20of%20Intel's%20processing%20architecture.">RAPL</a> (Running  average power limit) interface.</li>
<li><a target="_blank" href="https://github.com/sosy-lab/cpu-energy-meter/blob/main/README.md">Cpu-energy-meter</a> is a tool that measures the energy consumed by CPUs in a given time period.</li>
<li><a target="_blank" href="https://www.intel.com/content/www/us/en/developer/articles/tool/powertop-primer.html">Powertop</a> is a tool authored by Intel which gives you information about many things of interest such as power consumption, CPU <a target="_blank" href="https://www.intel.com/content/www/us/en/docs/socwatch/user-guide/2020/c-state.html#:~:text=C%2DState%20residencies%20are%20collected,the%20processor%20is%20%22idle%22.">C / P States</a>, CPU usage, and file system operations per second (and so on).</li>
</ol>
<p>###macOS</p>
<ol>
<li><a target="_blank" href="https://www.intel.com/content/www/us/en/developer/articles/tool/power-gadget.html">Intel power gadget</a> is a tool authored by Intel which gives users the ability to monitor power consumption, CPU frequency, CPU utilization, and even temperature on Intel based Mac machines. Note: According to Intel’s website, Power gadget will not get updates anymore and it recommends using <a target="_blank" href="https://www.intel.com/content/www/us/en/developer/articles/tool/performance-counter-monitor.html">Intel Performance Counter</a> instead.</li>
<li><a target="_blank" href="https://www.seense.com/menubarstats/mxpg/">MxPower Gadget</a> is a tool similar to the Intel power gadget, but for Apple silicon-based Macs.</li>
<li><a target="_blank" href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/MonitoringEnergyUsage.html">Powermetrics</a> is a <a target="_blank" href="https://firefox-source-docs.mozilla.org/performance/powermetrics.html">command line tool</a> that is pre-installed on Apple Silicon and Intel-based machines. It helps you obtain power measurements for CPU and GPU operation.</li>
<li><a target="_blank" href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/MonitoringEnergyUsage.html">The Activity Monitor App</a> is pre-installed on Macs, and gives you an overview of how your app is performing. It also gives you an overview of various metrics about your Mac. While it doesn’t give you power numbers, it is still a useful tool.</li>
</ol>
<p>###Android</p>
<p>The <a target="_blank" href="https://source.android.com/">AOSP</a> (Android Open Source Project) official documentation provides some guidelines on how to measure system component power <a target="_blank" href="https://source.android.com/docs/core/power/component">here</a>. This process may vary depending on the manufacturer of the device.</p>
<p>Android Studio, Android’s official development IDE also offers a <a target="_blank" href="https://developer.android.com/studio/profile/power-profiler">Power Profiler</a> to measure power. The On-Device Power Monitor (ODPM) reports power consumed by all profilable sub-systems through their power rails. Note: These measurements are not specific to any app, as they measure device power on the whole.</p>
<p>###iOS</p>
<p>During iOS app development, developers can measure the energy impact of their app using XCode’s inbuilt <a target="_blank" href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/EnergyGuide-iOS/MonitorEnergyWithInstruments.html">Instruments</a> profiler.</p>
<p>We talked about various ways to profile power and energy in this section. In the coming sections, I'll discuss some techniques and considerations to keep in mind to make your software more sustainable.</p>
<p>##Software Techniques and Design Patterns for Sustainability</p>
<p>###Consider which programming language to use</p>
<p>Programming languages have multiple levels of abstraction over the hardware that they are running on. Some have a model of the machine presented to the programmer which is really close to the actual hardware they are running on.</p>
<p>Languages which are directly compiled to machine code are generally more power efficient. They also are generally more performant, since there is less overhead compared to languages which are interpreted or run on a virtual machine, such as Java. </p>
<p>The research paper I mentioned earlier (<a target="_blank" href="https://discovery.ucl.ac.uk/id/eprint/10074516/1/GKLS18_MSR18.pdf">What are your programming language's Energy delay implications</a>) discusses this in detail and is a great read. </p>
<p>While many popular operating systems have the majority of their APIs in languages like Java, Kotlin, and Swift (which are not native), they usually have performance critical APIs available in native code as well – for example, Android’s <code>C</code> interface-based <a target="_blank" href="https://developer.android.com/ndk">NDK</a> library. These can interface with their virtual machine-run counterparts and in combination be used to write efficient applications.</p>
<p>###Make software power aware: React to thermal events</p>
<p>In the previous section, I talked about the importance of keeping power consumption low on the devices which run your software. This is not only important to reduce energy consumption, it's also important so that your device’s operating temperature doesn’t go into territory which can damage your device. </p>
<p>The operating temperature of your device doesn’t only depend on the software running on it (which includes your software). It also depends on the ambient temperature. </p>
<p>For example: if a smartphone is used in a car on a hot day, there’s a good chance that when a demanding application is run on it, it’ll thermally stress out the device. So what can you as a software architect do about this? </p>
<p>You cannot control ambient temperature, but you can control how your software responds to thermal events. Many popular operating systems offer APIs to ‘listen’ for thermal events. When apps get notifications that the device’s thermal load has crossed a certain limit, they should take appropriate actions. </p>
<p><strong>For example</strong>, if a device is streaming video and it gets a notification about thermally heating up, it may lower the resolution at which it is streaming video in order to reduce power consumption. </p>
<p>If a video conferencing app gets a worrisome thermal event notification, it may want to reduce the frame rate at which it is capturing video / reduce the resolution.</p>
<p>The following are thermal event APIs on popular operating systems</p>
<p><strong>MacOS and iOS:</strong> the <code>NSNotificationCenter</code> offers a <a target="_blank" href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/RespondToThermalStateChanges.html">thermal event listener</a> which userspace applications can register for<strong>.</strong></p>
<p><strong>Android:</strong> PowerManager offers an <a target="_blank" href="https://developer.android.com/reference/android/os/PowerManager.OnThermalStatusChangedListener">onThermalStatusChangedListener</a> which apps can register for, to receive thermal event notification.</p>
<p>###When appropriate, use low power states on hardware</p>
<p>Most modern processors have provisions for reducing power consumption. In these modes, the processor consumes less power by changing the CPU clock speed, putting the CPU into various idle states among various methods. Operating systems give the user the ability to leverage these low power states. </p>
<p><strong>For example</strong>, Linux has the <a target="_blank" href="https://linux.die.net/man/1/cpupower-frequency-set"><code>cpupower-frequency-set</code></a> tool which allows users to modify CPU frequency settings. </p>
<p>As another example, <a target="_blank" href="https://docs.arduino.cc/learn/electronics/low-power">Arduino microcontrollers</a> give programmers the ability to put them in various sleep modes when there are no active tasks to be run. These microcontrollers are typically woken up by an interrupt when a task to be processed is ready. </p>
<p>###Investigate the tradeoffs between Interrupt driven-I/O and Polling</p>
<p>Often, software needs to wait on another component of hardware or software for data to process. There are usually two parties involved: the producer of the data and the consumer of the data </p>
<p>For example, a mobile application may be waiting on a touch event on the display. Here the consumer is the mobile application and the producer is the display hardware + software. </p>
<p>Another example could be a native embedded program that is waiting on the status of a CPU register to change in order to perform some task. Here the consumer is the native embedded program and the producer is the CPU register (well, the producer may actually be producing some other data, but for simplicity we’re only considering a CPU register here).  </p>
<p>There are broadly 2 ways of doing this:</p>
<p><strong>Polling</strong>: The process of checking the status of a data-readiness at regular predetermined intervals of time is referred to as polling. Typically it looks something like this in code:</p>
<pre><code><span class="hljs-comment">// psuedo-code</span>
<span class="hljs-keyword">while</span>(poll) {
    bool data_available =  check_data();
    <span class="hljs-keyword">if</span> (data_available) {
        process_data();
    }
    sleep(SLEEP_TIME); <span class="hljs-comment">// to avoid wasting cpu cycles</span>
}
</code></pre><p>Polling seems pretty simple to implement. But unless you’re absolutely sure that the data or condition you’re waiting on is going to be available at a regular frequency, it has some disadvantages:</p>
<ol>
<li>Polling doesn’t respond to the data being available as soon as it is available, since it checks for availability after every SLEEP_TIME ms.</li>
<li>By constantly checking every time the polling thread wakes up, it still wastes cpu cycles and as a result some power.</li>
</ol>
<p><strong>Interrupt-driven I/O:</strong> in this strategy, there are no explicit checks for the data that is being waited on to be ready. Instead, it is the responsibility of the producer to inform the consumer whenever the data is ready. The consumer does not explicitly check in with the producer, even periodically. As a result, this saves CPU cycles and also power! </p>
<p>There are multiple ways of implementing interrupt-driven I/O. There can be hardware interrupts as well as software interrupts. </p>
<p>For example, when a user touches a mobile phone screen at a particular location, the operating system could send a callback to one of  the application’s thread’s notifying it that a touch input event is ready to process. Until the thread gets the notification, it can either do something else, or just stay asleep! </p>
<p>So in short, interrupt-driven I/O is generally more power efficient than polling, and you should prefer it, unless there’s a very good reason to use polling.</p>
<h3 id="heading-look-into-caching">Look into Caching</h3>
<p>Caching in computer science refers to the process of saving data in a storage location that is typically faster to access than high latency storage (which are generally larger in capacity).</p>
<p>Caching is useful when the same set of data needs to be read or modified repeatedly by software. It allows efficient retrieval and re-use of data. </p>
<p>You can see caching in action in many forms such as:</p>
<ol>
<li>Random Access Memory (RAM) is a cache for underlying disk</li>
<li>CPU cache – L(n) – where n is the level of the cache. As n decreases, the cache size usually gets smaller and the latency of data retrieval gets smaller as well.</li>
<li>Applications can cache data retrieved from the internet into their on device memory for quick access.</li>
<li>Local servers can cache data from remote servers for quicker access as well.</li>
</ol>
<p>And many more.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/image-98.png" alt="Image" width="600" height="400" loading="lazy">
<em>Figure 3: Flow of data from disk to CPU via cache hierarchy</em></p>
<p>As a result of being more efficient with data access, caching usually reduces power consumption and saves energy as well.</p>
<p>Before software can take advantage of caching, it needs to be architected so that that's possible. In general, the following steps can help you:</p>
<ol>
<li>The very first thing is to make sure you're on the lookout for opportunities to cache data.</li>
<li>Then, implement caching. How you implement it will depend on the type of software you’re building. It might range from tiling data matrix operations to caching data retrieved from the internet for re-use later on.</li>
<li>Monitor profile performance, energy and power usage – and make sure that these metrics are actually improving. It is possible with sub-optimal implementations to cause thrashing of the cache – which may increase power consumption and reduce performance.</li>
</ol>
<p>###Design software with lifetime and updatability in mind</p>
<p>This article has talked a lot about your software’s energy and power consumption. Finally, there’s something else that is very important as well: the ability for your software to last a long time and be updatable. </p>
<p>It's very beneficial for software to last a long time, since that in turn means that devices will work well for longer. Software being updatable means that older devices get important feature and security updates, which leads to users not necessarily needing to buy newer devices just to get better software. This leads to a reduction in electronic waste and energy being spent on recycling electronic components. </p>
<p>Software updatability is a large topic which I will not cover in detail here. But there are some principles that you shoulder consider which will serve you well while designing for updatability:</p>
<p><strong>Design your software in modules which are updatable by themselves</strong>: This may involve having strict interfaces between different modules so that on updating one module the software package as a whole still functions correctly.</p>
<p><strong>Focus on using memory as efficiently as possible</strong>: If all software was designed keeping in mind memory efficiency, electronic devices would last longer, since the amount of memory they had would be sufficient for longer. </p>
<p>As an example, consider the iPhone: the first generation in 2007 had 128MB of RAM and 16GB of flash storage (maximum). Today, the iPhone 15 has 6GB of RAM and has a maximum storage option of 1 TB (1024GB). That is a nearly 16x increase in RAM and 64x increase in flash memory. This was needed since the amount of memory needed by applications and the operating system itself grew in size by a huge amount. </p>
<p>As time passes, if memory is used judiciously, we can think of a future where electronic devices last for multiple decades instead of being phased out every 5-6 years.</p>
<p><strong>Testing needs to be bulletproof</strong>: When software is going to be updated frequently, there’ll be multiple version of software running on devices which interact with other pieces of software which might be older. These pieces of software must still function correctly. </p>
<p>For this to happen, before deploying updates, software must be tested thoroughly. As an example: when an app developer releases an app, they must test with multiple versions of operating systems to make sure that their app behaves well on all of them.</p>
<p>##Conclusion</p>
<p>This article introduced the concept of engineering software for sustainability and discussed why this is necessary. </p>
<p>You also learned about various metrics as well as some techniques to make your software sustainable and more efficient. </p>
<p>Finally, I hope it inspired you to think about a very serious problem we're facing right now and how we in the tech community can do our part.</p>
<p>I hope you enjoyed the article!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Teach Yourself Computer Science – Key CS Concepts You Should Know ]]>
                </title>
                <description>
                    <![CDATA[ Software development may feel like a bit of a race to keep up with new technologies. There's always a new frontend framework to learn, or a new database or language that's a variation of another language. It's never ending and feels like you always h... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-every-software-engineer-should-know/</link>
                <guid isPermaLink="false">66bb8c9aa5fd14123a8b4a10</guid>
                
                    <category>
                        <![CDATA[ coding ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Computer Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ fundamentals ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tamerlan Gudabayev ]]>
                </dc:creator>
                <pubDate>Thu, 22 Jun 2023 22:06:46 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/06/pexels-christina-morillo-1181298--2-.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Software development may feel like a bit of a race to keep up with new technologies.</p>
<p>There's always a new frontend framework to learn, or a new database or language that's a variation of another language. It's never ending and feels like you always have to keep up.</p>
<p>But it doesn't have to be this way. </p>
<h2 id="heading-everything-is-built-from-the-fundamentals">Everything is Built From the Fundamentals</h2>
<p>If you learn the fundamentals, then everything else will become easier. </p>
<p>For example, if you <a target="_blank" href="https://www.freecodecamp.org/news/what-is-tcp-ip-layers-and-protocols-explained/">learn how the TCP/IP protocol works</a>, then from that you can easily learn all the other protocols that are built on top of it. </p>
<p>You're essentially having to cover less ground. The more fundamentals you know, the less you will struggle learning new things.</p>
<p>I believe there are 10 core subjects which, if you learn them, will give you a solid foundation. </p>
<h2 id="heading-why-should-you-learn-these-key-concepts">Why Should You Learn These Key Concepts?</h2>
<p>Learning the fundamentals will really put you in the top 5% of all programmers.</p>
<p>If we look at it from a different perspective, here's a great quote from Ras Bodik.</p>
<blockquote>
<p>Don’t be a boilerplate programmer. Instead, build tools for users and other programmers. Take historical note of textile and steel industries: do you want to build machines and tools, or do you want to operate those machines?</p>
</blockquote>
<h2 id="heading-but-there-are-a-million-different-courses-to-choose-from">But There are a Million Different Courses to Choose From</h2>
<p>Well, look no further. </p>
<p>Below, I've compiled a bunch of helpful resources on each subject – with some alternatives, of course. So you can focus on learning and not on mindlessly researching which books/videos/courses are the best. </p>
<h3 id="heading-a-few-notes">A few notes</h3>
<p>This article is well-suited for self-taught developers or developers who don't feel quite comfortable with certain computer science concepts. </p>
<p>If you are learning to program for the first time, I would recommend <a target="_blank" href="https://www.reddit.com/r/learnprogramming/">r/learnprogramming</a> community on Reddit. </p>
<p>This article was heavily inspired by <a target="_blank" href="https://twitter.com/oznova_">Oz Nova</a> and <a target="_blank" href="https://twitter.com/quackingduck">Myles Byrne</a> who authored the site <a target="_blank" href="https://teachyourselfcs.com/">teachyourselfcs.com</a>. If you enjoy this article, feel free to check out and share their site. </p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-programming">Programming</a></li>
<li><a class="post-section-overview" href="#heading-computer-architecture">Computer Architecture</a></li>
<li><a class="post-section-overview" href="#algorithms-and-data-structures">Algorithms and Data Structures</a></li>
<li><a class="post-section-overview" href="#heading-math-for-computer-science">Math for Computer Science</a></li>
<li><a class="post-section-overview" href="#heading-operating-systems">Operating Systems</a></li>
<li><a class="post-section-overview" href="#heading-computer-networking">Computer Networking</a> </li>
<li><a class="post-section-overview" href="#heading-databases">Databases</a></li>
<li><a class="post-section-overview" href="#language-and-compilers">Languages and Compilers</a></li>
<li><a class="post-section-overview" href="#heading-distributed-systems">Distributed Systems</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/p/dfc9104e-85e4-4749-88dc-1859e6c643b9/web-security">Web Security</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/p/dfc9104e-85e4-4749-88dc-1859e6c643b9/conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-programming">Programming</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-84.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://dabeaz.com/sicp.html">Source</a></em></p>
<p>I'm not talking about syntax here. But actually programming or problem solving. Things such as abstraction, functions as data, recursion, and the different kinds of programming paradigms (object-oriented, functional, and declarative). </p>
<p>The book I recommend to learn programming is <a target="_blank" href="https://sarabander.github.io/sicp/html/index.xhtml">Structures and Interpretations of Computer Programs</a> (SICP) (It's also known as the wizard book). </p>
<p>The book is free and has a set of <a target="_blank" href="https://ocw.mit.edu/courses/6-001-structure-and-interpretation-of-computer-programs-spring-2005/video_galleries/video-lectures/">MIT lectures</a>. But the MIT lectures are a bit hard to watch due to the poor quality of that time (2005). So I recommend <a target="_blank" href="https://archive.org/details/ucberkeley-webcast-PL3E89002AA9B9879E?sort=titleSorter">Brian Harvey’s SICP lectures</a> (for the 61A course at Berkeley) instead.</p>
<h3 id="heading-why-this-book">Why this book?</h3>
<p>Because this book focuses on the big picture. </p>
<p>It doesn't care about the programming language. It uses a variation of Lisp called Scheme. Scheme is very easy to learn (you can probably learn it in less than an hour), so it let's you focus on ideas not syntax. </p>
<p>Because of its simplicity, it makes it possible to examine different programming paradigms. It's a functional first approach, but you can implement your own OOP. </p>
<p>Scheme is a great language for teaching because it takes the focus away from the language and puts it on the big ideas. </p>
<p>If you're worried about it not being used in the industry, it's ok – you can always learn a more commonly-used programming languages after you grasp these high-level concepts.</p>
<h3 id="heading-but-what-if-i-really-dont-want-to-learn-scheme">But what if I REALLY don't want to learn Scheme?</h3>
<p>Okay, you can follow the new version of the book which uses Python. The book is called <a target="_blank" href="http://composingprograms.com/">Composing Programs</a>. It also has its own <a target="_blank" href="https://cs61a.org/">set of lectures</a>. </p>
<p>But, I highly recommend at least trying to do the Scheme version. It's just magical once you get it. </p>
<h3 id="heading-okay-ive-tried-it-but-its-really-hard">Okay, I've tried it but it's really hard</h3>
<p>Yes, I understand. </p>
<p>Some of you will find SICP a bit too hard. It's not meant for purely beginner programmers. </p>
<p>If that's the case, then I would recommend <a target="_blank" href="https://htdp.org/">How to Design Programs (HTDP)</a>. It uses a language very similar to Scheme and is generally more slower paced. You can use this book and its <a target="_blank" href="https://www.edx.org/course/how-to-code-simple-data">course on edX</a>.</p>
<h3 id="heading-tips-on-studying">Tips on Studying</h3>
<p>Don't read these books like a story. </p>
<p>It's not meant to be read cover to cover. Instead, <strong>focus on the exercises</strong>. You don't have to do them all, but just make sure you know how to solve most of them. </p>
<p>The lectures are optional and only needed if they help. It's really up to you. </p>
<h3 id="heading-additional-resources">Additional Resources</h3>
<ul>
<li><a target="_blank" href="https://www.youtube.com/playlist?list=PLVFrD1dmDdvdvWFK8brOVNL7bKHpE-9w0">Virtual meetups that talk about SICP</a> </li>
<li><a class="post-section-overview" href="#https://racket-lang.org/">Racket</a> (IDE for Scheme) (<a target="_blank" href="https://stackoverflow.com/a/25096066">Checkout this StackOverFlow answer for Scheme setup</a>)</li>
</ul>
<h2 id="heading-computer-architecture">Computer Architecture</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-128.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://github.com/ahmeducf/computer-systems-CS-APP3e">Source</a></em></p>
<p>You wrote some code and it magically runs. </p>
<p>How does that work? Well, this is what you will learn with computer architecture. This is by far the most neglected subject by most self taught engineers. </p>
<p>As engineers, we don't believe in magic. We have to unravel the magic behind computers. You will also learn some useful stuff such as:</p>
<ul>
<li>What is the L1, L2 cache? </li>
<li>Why does cyberpunk lag?</li>
</ul>
<p>The book I recommend here is <em><a target="_blank" href="http://csapp.cs.cmu.edu/3e/home.html">Computer Systems: A Programmer's Perspective</a></em>. I would also recommend an <a target="_blank" href="https://www.cs.cmu.edu/afs/cs/academic/class/15213-f16/www/schedule.html">introductory course</a> that will cover chapters 1-6 of the book (That was made by the authors of the book). </p>
<h3 id="heading-but-theres-a-catch">But there's a catch</h3>
<p><strong>This book is not meant to be read cover to cover</strong>. It has a lot of content, that may not well be presented in the optimal order.</p>
<p>So I recommend that you follow the course and do the labs. </p>
<h3 id="heading-what-if-i-find-it-too-hard">What if I find it too hard?</h3>
<p>A lot of people will find the content a bit heavy, so to ease into it, I would recommend doing the following:</p>
<ul>
<li>Read <a target="_blank" href="https://www.amazon.com/Code-Language-Computer-Hardware-Software/dp/0735611319">Code: The Hidden Language of Computer Hardware and Software</a>\</li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/an-introduction-to-software-architecture-patterns/">Read this handbook on Software Architecture</a></li>
<li>Watch all 4 videos of <a target="_blank" href="https://www.youtube.com/playlist?list=PLFt_AvWsXl0dPhqVsKt1Ni_46ARyiCGSq">Exploring How Computers Work</a></li>
<li>Watch all 41 videos of <a target="_blank" href="https://www.youtube.com/playlist?list=PLH2l6uzC4UEW0s7-KewFLBC1D0l6XRfye">Crash Course: Computer Science</a></li>
<li>Take the course <a target="_blank" href="https://www.coursera.org/learn/build-a-computer">NAnd2Tetris</a> </li>
<li>Learn a bit of C by reading the book: <a target="_blank" href="https://www.amazon.com/C-Programming-Modern-Approach-2nd/dp/0393979504">C Programming a Modern Approach</a></li>
</ul>
<p>Once you finish that you can then come back to Computer Systems: A Programmers Perspective. </p>
<h2 id="heading-data-structures-and-algorithms">Data Structures and Algorithms</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-131.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://twitter.com/StevenSkiena/status/1336050368875290629">Source</a></em></p>
<p>Everyone wants to work at a FAANG company, but no one wants to learn Data Structures and Algorithms. </p>
<p>Nevertheless, I don't want you to learn these just because of technical interviews. Data structures and algorithms are important because they help build your problem solving skills. </p>
<p>There are many great books and courses on Data Structures and Algorithms but the one I would recommend is a book named <em><a target="_blank" href="https://www.amazon.com/Algorithm-Design-Manual-Steven-Skiena/dp/1849967202">The Algorithm Design Manual</a></em> by Steven Skiena. You can also check out his course <a target="_blank" href="https://www3.cs.stonybrook.edu/~skiena/373/videos/">here</a>. </p>
<h3 id="heading-dont-forget-to-practice">Don't forget to practice</h3>
<p>Same rules apply here. Don't just learn about data structures, but create them in whatever language you want. Don't just memorize algorithms but implement them and see where and how you can use them. </p>
<p>A good tip is solving some <a target="_blank" href="https://leetcode.com/">Leetcode</a> problems while going through the book/course. </p>
<h3 id="heading-what-if-i-find-it-too-hard-1">What if I find it too hard?</h3>
<p>If you find the material a bit heavy, then I would recommend the following resources:</p>
<ul>
<li>Read the book: <a target="_blank" href="https://www.amazon.com/Grokking-Algorithms-illustrated-programmers-curious/dp/1617292230">Grokking Algorithms</a> </li>
<li>Read the book: <a target="_blank" href="https://www.amazon.com/How-Solve-Mathematical-Princeton-Science/dp/069111966X#:~:text=Polya%2C%20How%20to%20Solve%20It,winning%20a%20game%20of%20anagrams.">How to Solve It: A New Aspect of Mathematical Method</a></li>
</ul>
<p>In general there are many different books/courses that teach Data Structures and Algorithms – Here are some other great resources:</p>
<ul>
<li><a target="_blank" href="https://www.freecodecamp.org/news/algorithms-and-data-structures-free-treehouse-course/">Data Structures and Algorithms course on freeCodeCamp</a></li>
<li><a target="_blank" href="https://algorithmsilluminated.org/">Algorithms Illuminated</a> </li>
<li><a target="_blank" href="https://www.coursera.org/learn/algorithms-part1">Princeton Algorithms Course</a> </li>
<li><a target="_blank" href="https://frontendmasters.com/courses/algorithms/">ThePrimegeans Data Structures and Algorithms Course</a></li>
<li><a target="_blank" href="https://ocw.mit.edu/courses/6-006-introduction-to-algorithms-fall-2011/">MIT Introduction to Algorithms</a> </li>
</ul>
<h2 id="heading-math-for-computer-science">Math for Computer Science</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-144.png" alt="Image" width="600" height="400" loading="lazy">
<em>Don't worry, this is some random math photo found <a target="_blank" href="https://unsplash.com/photos/h3kuhYUCE9A">here</a></em></p>
<p>Many new developers skip this. </p>
<p>But, hear me out – computer science is essentially a branch of mathematics. Learning it will make you a better developer by honing your problem solving skills. </p>
<h3 id="heading-the-most-relevant-area-for-cs-is-discrete-mathematics">The most relevant area for CS is Discrete Mathematics</h3>
<p>Discrete mathematics is the branch of mathematics that deals with countable or finite numbers. </p>
<p>The topics in discrete mathematics are many, but the ones which are relevant for CS are:</p>
<ul>
<li>Logic </li>
<li>Combinatorics </li>
<li>Discrete Probability </li>
<li>Set Theory </li>
<li>Graph Theory</li>
<li>Number Theory </li>
</ul>
<h3 id="heading-how-to-study-discrete-mathematics">How to Study Discrete Mathematics</h3>
<p>I would suggest starting with a set of <a target="_blank" href="https://cims.nyu.edu/~regev/teaching/discrete_math_fall_2005/dmbook.pdf">lecture notes by László Lovász</a>. </p>
<p>Professor Lovász notes are easier to digest than formal texts and are just generally fun. He starts of with a problem and solves it using discrete mathematics. </p>
<p>After that, you can take an MIT book called <em><a target="_blank" href="https://courses.csail.mit.edu/6.042/spring17/mcs.pdf">Mathematics for Computer Science</a>.</em> The book comes with video lectures that are <a target="_blank" href="https://ocw.mit.edu/courses/6-042j-mathematics-for-computer-science-fall-2010/video_galleries/video-lectures/">freely available</a>. </p>
<h3 id="heading-what-if-its-too-hard">What if it's too hard?</h3>
<p>Don't worry – sometimes you just have to accept that <strong>you won't always get it right away</strong>. </p>
<p>It's fine. </p>
<p>But if you feel like you're missing some fundamental knowledge, then it's a different story. The fundamental prerequisite subjects for discrete mathematics are:</p>
<ul>
<li>Algebra</li>
<li>Geometry</li>
<li>Calculas</li>
</ul>
<p>There are many free resources, but the ones I would recommend are:</p>
<ul>
<li><a target="_blank" href="https://www.khanacademy.org/">Khan Academy</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/college-algebra-course-with-python-code/">College Algebra course</a> </li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/learn-college-calculus-in-free-course/">Calculus 1 course</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/learn-calculus-2-in-this-free-7-hour-course/">Calculus 2 course</a></li>
</ul>
<h2 id="heading-operating-systems">Operating Systems</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-228.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://pages.cs.wisc.edu/~remzi/OSTEP/">Source</a></em></p>
<p>Remember when I told you that we as developers want to remove the magic in computing?</p>
<p>Same thing applies here – operating systems seem like some sort of magical black box. But they're not – it's just a lot of clever engineering. </p>
<p>If you know how these operating systems are built and work, then you will definitely be in a league of your own. </p>
<p>It's somewhat difficult to find good resources online for operating systems but the most recommend book is <a target="_blank" href="https://pages.cs.wisc.edu/~remzi/OSTEP/">Operating Systems: Three Easy Pieces (OSTEP).</a> There isn't any official video lecture online that fully covers the book but I found this <a target="_blank" href="https://www.youtube.com/playlist?list=PLhtZD20ADU45ADsAIxlNpFowP3iYvGXvJ">playlist on YouTube</a>. </p>
<h3 id="heading-recommend-prerequisites">Recommend Prerequisites</h3>
<p>I would suggest learning computer architecture first and a little bit about C before embarking on the operating systems journey. </p>
<h3 id="heading-optional-resources">Optional Resources</h3>
<p>Now, I recommend finishing OSTEP first and then checking out the other recommend resources. They are all optional. </p>
<ul>
<li>Want to build your own Linux system? Check out <a target="_blank" href="https://www.linuxfromscratch.org/">Linux from Scratch</a>. </li>
<li>Want an in-depth overview of Linux, MacOS, and Windows? <a target="_blank" href="https://www.freecodecamp.org/news/an-introduction-to-operating-systems/">Here's a handbook for you</a>.</li>
<li>Want to build your own OS? Check out <a target="_blank" href="https://wiki.osdev.org/Introduction">OSDEV</a></li>
<li>Want to build your own Sockets? Check out <a target="_blank" href="https://beej.us/guide/bgnet/">Beej's Guide to Network Programming</a> </li>
</ul>
<h2 id="heading-computer-networking">Computer Networking</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-237.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://github.com/topics/top-down-approach">Source</a></em></p>
<p>Since the dawn of the internet, computer networking has been one the most important subjects for software engineers. </p>
<p>If you don't know things like IP, TCP, UDP, HTTP, TLS, DNS, SMTP, and so on — then you should learn about computer networking (especially if you are a backend engineer).</p>
<p>The recommended book here is <a target="_blank" href="https://gaia.cs.umass.edu/kurose_ross/wireshark.php">Computer Networking: A Top-Down Approach</a>. You can also check out the <a target="_blank" href="https://www.youtube.com/playlist?list=PLByK_3hwzY3Tysh-SY9MKZhMm9wIfNOas">video lectures</a> from the author of the book himself. </p>
<p>But before beginning that I would recommend to check out this <a target="_blank" href="https://www.youtube.com/playlist?list=PLowKtXNTBypH19whXTVoG3oKSuOcw_XeW">video crash course on computer networking</a> from a bottom up approach. And <a target="_blank" href="https://www.freecodecamp.org/news/computer-networking-how-applications-talk-over-the-internet/">here's a helpful tutorial</a> that covers the basics well.</p>
<h2 id="heading-databases">Databases</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-229.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://www.astera.com/type/blog/a-quick-overview-of-different-types-of-databases/">Source</a></em></p>
<p>Databases are somewhat new – they came around the 1970s and have since become integral parts of many applications.</p>
<p>I highly recommend the below courses from the <a target="_blank" href="https://www.youtube.com/@CMUDatabaseGroup/featured">CMU Database Group</a>. They're all freely available on YouTube. I would recommend going through at least the first one. </p>
<ol>
<li><a target="_blank" href="https://www.youtube.com/playlist?list=PLSE8ODhjZXjaKScG3l0nuOiDTTqpfnWFf">Introduction To Databases</a></li>
<li><a target="_blank" href="https://www.youtube.com/playlist?list=PLSE8ODhjZXjZKp-oX_75aBnznulk7nubu">Database Seminars</a></li>
<li><a target="_blank" href="https://www.youtube.com/playlist?list=PLSE8ODhjZXjYzlLMbX3cR0sxWnRM7CLFn">Advanced Databases</a></li>
</ol>
<p>Also, <a target="_blank" href="https://www.freecodecamp.org/news/learn-sql-free-relational-database-courses-for-beginners/">here's a great collection of resources</a> to help you learn about SQL databases. And <a target="_blank" href="https://www.freecodecamp.org/news/learn-nosql-in-3-hours/">here's a free course on NoSQL databases</a>.</p>
<h2 id="heading-languages-and-compilers">Languages and Compilers</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-230.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://chidiwilliams.com/post/crafting-interpreters-a-review/">Source</a></em></p>
<p>You may know how to code in one or more programming languages. </p>
<p>But do you know how to create or design one? That's what you will learn by studying programming languages and compilers.</p>
<p>The recommended introductory book is called <a target="_blank" href="https://craftinginterpreters.com/contents.html">Crafting Interpreters</a>. </p>
<p>After that you can move on to <em><a target="_blank" href="https://smile.amazon.com/Compilers-Principles-Techniques-Tools-2nd/dp/0321486811">Compilers: Principles, Techniques &amp; Tools</a></em>, also called “the Dragon Book”. The book is covers a lot of topics so I highly recommend taking a course with it. The one I recommend is from <a target="_blank" href="https://www.edx.org/course/compilers">Alex Aiken on edX</a>.</p>
<p>And <a target="_blank" href="https://www.freecodecamp.org/news/what-is-a-compiler-in-c/">here's a helpful beginner-friendly tutorial</a> on how the compiler works in C programming.</p>
<h2 id="heading-distributed-systems">Distributed Systems</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-231.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://vvsevolodovich.dev/designing-data-intensive-applications-by-martin-kleppmann/">Source</a></em></p>
<p>If you choose to study only one subject from the list, make it distributed systems. It's the holy grail for tech companies, and if you want to get a developer job, you should be proficient about distributed systems. </p>
<p>My recommend path in learning the subject is:</p>
<ol>
<li>Read the book: <a target="_blank" href="https://www.amazon.com/gp/product/1838430202/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;tag=utsavized0d-20&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=1838430202&amp;linkId=8f3007bbed9b958980492f5c0bb1105f">Understanding Distributed Systems</a> </li>
<li>Read the book: <a target="_blank" href="https://www.amazon.com/Designing-Data-Intensive-Applications-Reliable-Maintainable/dp/1449373321">Designing Data Intensive Applications</a> also known as the "red book"</li>
<li>While reading the "red book", take its accompanying <a target="_blank" href="https://www.youtube.com/@6.824/videos">MIT course on YouTube</a>. </li>
<li>Read the book: <a target="_blank" href="https://www.amazon.com/dp/1492086894?psc=1&amp;linkCode=sl1&amp;tag=utsavized0d-20&amp;linkId=64e15d236bb8c1015661423e5be849ac&amp;language=en_US&amp;ref_=as_li_ss_tl">Software Architecture: The Hard Parts</a> (Optional) </li>
<li>You can also <a target="_blank" href="https://www.freecodecamp.org/news/design-patterns-for-distributed-systems/">check out my handbook about design patterns for distributed systems</a>.</li>
</ol>
<h2 id="heading-web-security">Web Security</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-232.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://www.ifourtechnolab.com/blog/principles-of-web-security">Source</a></em></p>
<p>There have been a lot of security breaches in the last 2-3 years. </p>
<p>It's getting dangerous out there – and as a software engineer, knowing some fundamentals of web security will give you an edge. </p>
<p>The course I recommend first is <a target="_blank" href="https://web.stanford.edu/class/cs253/">CS253 Web Security by Stanford</a>. It gives a comprehensive overview of web security. So expect topics like web app vulnerabilities, injection, denial-of-service, and many more. </p>
<p>You can also <a target="_blank" href="https://www.freecodecamp.org/news/technical-dive-into-owasp/">review these common vulnerabilities</a> and learn how to prevent attacks that take advantage of them.</p>
<p>Later on, if you want, you could learn how to hack using <a target="_blank" href="https://pwn.college/">pwn.college</a>. </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Learning all these subjects will take you a while, and will require consistent effort. But if you like what you do and are interested in the subject, then it should feel like play and not a chore. </p>
<p>Regardless of the subjects you choose to study. The most important tip I can give you is...</p>
<h3 id="heading-dont-be-a-passive-learner">Don't be a passive learner</h3>
<p>Don't just watch videos – do the exercises as well. Build the projects along with the tutorials. </p>
<p>Don't just read books but engage in the book by asking questions and doing your own research. </p>
<p>You want the information to stick, so you don't forget it. And to make it stick you have to actively engage in the subject. </p>
<p>I sincerely hope that I encouraged you to study some of these subjects. As always I want to end it of by thanking you for reading this article. </p>
<p>I wish you all the best. </p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
