<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Clean Architecture: Contact Management Application on Nitin Kumar Singh</title><link>https://nitinksingh.com/series/clean-architecture-contact-management-application/</link><description>Recent content in Clean Architecture: Contact Management Application on Nitin Kumar Singh</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>© 2026 Nitin Kumar Singh. All rights reserved.</copyright><lastBuildDate>Thu, 16 Jul 2026 09:00:00 +0530</lastBuildDate><atom:link href="https://nitinksingh.com/series/clean-architecture-contact-management-application/index.xml" rel="self" type="application/rss+xml"/><item><title>Best Practices for Creating and Using DTOs in the API</title><link>https://nitinksingh.com/posts/best-practices-for-creating-and-using-dtos-in-the-api/</link><pubDate>Sun, 01 Dec 2024 09:00:00 +0530</pubDate><guid>https://nitinksingh.com/posts/best-practices-for-creating-and-using-dtos-in-the-api/</guid><description>&lt;p&gt;Serializing domain entities straight to the client looks like a shortcut until the first schema change. Rename one property on the entity and every consumer of the API breaks with it, and you find out from their bug reports. DTOs are the cheap insurance against that: the entity can change shape freely because the contract the client sees is a separate class you control. This post covers the DTO conventions I use in the Contact Management Application and why each one exists.&lt;/p&gt;</description></item><item><title>Clean Architecture: Introduction to the Project Structure</title><link>https://nitinksingh.com/posts/clean-architecture-introduction-to-the-project-structure/</link><pubDate>Sun, 01 Dec 2024 09:00:00 +0530</pubDate><guid>https://nitinksingh.com/posts/clean-architecture-introduction-to-the-project-structure/</guid><description>&lt;p&gt;Most Clean Architecture articles stop at the circle diagram. The part that actually decides whether a team can live with the pattern is more mundane: which project does this class go in, which direction do the references point, and where do the interfaces live. I built the &lt;a href="https://github.com/nitin27may/clean-architecture-docker-dotnet-angular" target="_blank" rel="noreferrer"&gt;Contact Management Application&lt;/a&gt; as a .NET Core Web API to answer exactly those questions with running code, and this series walks through it layer by layer.&lt;/p&gt;</description></item><item><title>Dependency Injection Setup Across Layers</title><link>https://nitinksingh.com/posts/dependency-injection-setup-across-layers/</link><pubDate>Sun, 01 Dec 2024 09:00:00 +0530</pubDate><guid>https://nitinksingh.com/posts/dependency-injection-setup-across-layers/</guid><description>&lt;p&gt;Program.cs is where service registration goes to rot. It starts at five lines, then every feature adds its repositories, validators, and settings bindings until nobody can tell which layer owns what. The Contact Management Application avoids that with one rule: each layer registers its own services through an extension method, and Program.cs only calls those methods. This post walks through that setup layer by layer, using the real code, including two spots where the real code deserves a warning label.&lt;/p&gt;</description></item><item><title>Dockerizing the .NET Core API, Angular and MS SQL Server</title><link>https://nitinksingh.com/posts/dockerizing-the-.net-core-api-angular-and-ms-sql-server/</link><pubDate>Sun, 01 Dec 2024 09:00:00 +0530</pubDate><guid>https://nitinksingh.com/posts/dockerizing-the-.net-core-api-angular-and-ms-sql-server/</guid><description>&lt;p&gt;Getting the Contact Management Application running used to mean installing the .NET SDK, Node, and a local SQL Server, then hoping the versions matched mine. That is three toolchains of setup before anyone sees a login page. Containerizing the stack collapses all of it into &lt;code&gt;docker-compose up --build&lt;/code&gt;: the API, the Angular frontend, SQL Server, and an nginx load balancer come up together, wired the same way on every machine. This post walks through the Dockerfiles and compose files that make that work, including the parts I would do differently today.&lt;/p&gt;</description></item><item><title>Error Handling and Exception Management in the API</title><link>https://nitinksingh.com/posts/error-handling-and-exception-management-in-the-api/</link><pubDate>Sun, 01 Dec 2024 09:00:00 +0530</pubDate><guid>https://nitinksingh.com/posts/error-handling-and-exception-management-in-the-api/</guid><description>&lt;p&gt;An unhandled exception in a default ASP.NET Core API gives the client an empty 500 and gives you a stack trace in a log nobody is watching. The only thing worse is the API that returns the stack trace to the caller. The Contact Management Application takes the middle path: one middleware catches everything, maps known exception types to sensible status codes, returns a predictable JSON shape, and logs the details server-side. This post walks through that implementation and where I would lean on the newer built-in alternatives instead.&lt;/p&gt;</description></item><item><title>Handling Authorization and Role-Based Access Control (RBAC)</title><link>https://nitinksingh.com/posts/handling-authorization-and-role-based-access-control-rbac/</link><pubDate>Sun, 01 Dec 2024 09:00:00 +0530</pubDate><guid>https://nitinksingh.com/posts/handling-authorization-and-role-based-access-control-rbac/</guid><description>&lt;h2 class="relative group"&gt;Introduction&#10; &lt;div id="introduction" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#introduction" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Static role checks (&lt;code&gt;[Authorize(Roles = &amp;quot;Admin&amp;quot;)]&lt;/code&gt;) fall apart the first time someone asks you to add a permission without a redeploy. Once roles and permissions have to change at runtime, hard-coded role attributes become a liability. The Contact Management Application takes a different route: a dynamic policy provider that builds authorization policies from the database at request time, covering both the backend API and the Angular frontend, wired into JWT authentication without breaking the separation of concerns Clean Architecture expects.&lt;/p&gt;</description></item><item><title>Implementing Activity Logging with Custom Attributes</title><link>https://nitinksingh.com/posts/implementing-activity-logging-with-custom-attributes/</link><pubDate>Sun, 01 Dec 2024 09:00:00 +0530</pubDate><guid>https://nitinksingh.com/posts/implementing-activity-logging-with-custom-attributes/</guid><description>&lt;p&gt;The first version of audit logging in the Contact Management Application was what most codebases end up with: &lt;code&gt;_logger.LogInformation(&amp;quot;Creating contact...&amp;quot;)&lt;/code&gt; copy-pasted into every controller action. It worked, until someone forgot one, and the one they forgot was of course the action the auditors asked about. Logging that depends on developer discipline isn&amp;rsquo;t an audit trail; it&amp;rsquo;s a suggestion.&lt;/p&gt;</description></item><item><title>Implementing AutoMapper for DTO Mapping with Audit Details</title><link>https://nitinksingh.com/posts/implementing-automapper-for-dto-mapping-with-audit-details/</link><pubDate>Sun, 01 Dec 2024 09:00:00 +0530</pubDate><guid>https://nitinksingh.com/posts/implementing-automapper-for-dto-mapping-with-audit-details/</guid><description>&lt;p&gt;Early versions of the Contact Management Application had the same four lines at the end of every create and update method: set &lt;code&gt;CreatedBy&lt;/code&gt;, set &lt;code&gt;CreatedOn&lt;/code&gt; (or their &lt;code&gt;Updated*&lt;/code&gt; twins), then save. Copy-pasted audit code gets forgotten just often enough to make the audit columns untrustworthy, and an audit column you can&amp;rsquo;t trust is worse than none. Moving that logic into AutoMapper profiles fixed it in one place.&lt;/p&gt;</description></item><item><title>Seeding Initial Data Using Docker Compose and SQL Scripts</title><link>https://nitinksingh.com/posts/seeding-initial-data-using-docker-compose-and-sql-scripts/</link><pubDate>Sun, 01 Dec 2024 09:00:00 +0530</pubDate><guid>https://nitinksingh.com/posts/seeding-initial-data-using-docker-compose-and-sql-scripts/</guid><description>&lt;h2 class="relative group"&gt;Introduction&#10; &lt;div id="introduction" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#introduction" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Nothing kills momentum on a new project like cloning the repo, running &lt;code&gt;docker compose up&lt;/code&gt;, and landing on an empty database. No roles, no permissions, no login, so nothing works until someone reads the wiki and hand-runs a SQL script. On the Contact Management Application I wanted &lt;code&gt;docker compose up&lt;/code&gt; to be the whole setup: containers come up, the schema seeds itself, and you can log in.&lt;/p&gt;</description></item><item><title>Unit of Work Pattern and Its Role in Managing Transactions</title><link>https://nitinksingh.com/posts/unit-of-work-pattern-and-its-role-in-managing-transactions/</link><pubDate>Sun, 01 Dec 2024 09:00:00 +0530</pubDate><guid>https://nitinksingh.com/posts/unit-of-work-pattern-and-its-role-in-managing-transactions/</guid><description>&lt;p&gt;The bug that makes people care about the Unit of Work pattern is the partial write. An operation inserts a row in one table, fails on the second, and the database lands in a state nobody designed for. I&amp;rsquo;ve spent enough late evenings reconstructing &amp;ldquo;how did this record end up half-created&amp;rdquo; timelines to want transaction boundaries stated explicitly in code, not implied by hope.&lt;/p&gt;</description></item><item><title>Using Dapper for Data Access and Repository Pattern</title><link>https://nitinksingh.com/posts/using-dapper-for-data-access-and-repository-pattern/</link><pubDate>Sun, 01 Dec 2024 09:00:00 +0530</pubDate><guid>https://nitinksingh.com/posts/using-dapper-for-data-access-and-repository-pattern/</guid><description>&lt;h2 class="relative group"&gt;Introduction&#10; &lt;div id="introduction" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#introduction" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;On the Contact Management Application I reached for Dapper instead of Entity Framework Core, and the reason was boring: I wanted to see the SQL. EF Core is fine until a generated query does something surprising under load, and then you are reverse-engineering LINQ translations at 2 a.m.&lt;/p&gt;</description></item><item><title>Validating Inputs with FluentValidation</title><link>https://nitinksingh.com/posts/validating-inputs-with-fluentvalidation/</link><pubDate>Sun, 01 Dec 2024 09:00:00 +0530</pubDate><guid>https://nitinksingh.com/posts/validating-inputs-with-fluentvalidation/</guid><description>&lt;h2 class="relative group"&gt;Introduction&#10; &lt;div id="introduction" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#introduction" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Validation is the one part of a request I refuse to trust anywhere but the edge. Domain objects assume they are already valid; rejecting a malformed email or a missing name belongs at the API boundary, before that data reaches a service or a repository.&lt;/p&gt;</description></item></channel></rss>