<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Tech With Nadir Badnjevic]]></title><description><![CDATA[A place where I share my experience with .NET, Cloud, Software Architectures &amp; Design, Engineering leadership and technology.]]></description><link>https://nadirbad.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 06:49:23 GMT</lastBuildDate><atom:link href="https://nadirbad.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[EventStorming: How to Stop Building the Wrong System]]></title><description><![CDATA[Most teams make their most critical architectural decisions during the first few weeks of a project. That's also when collective knowledge about the domain is at its absolute lowest.
It's the Project ]]></description><link>https://nadirbad.dev/event-storming-how-to-stop-building-the-wrong-system</link><guid isPermaLink="true">https://nadirbad.dev/event-storming-how-to-stop-building-the-wrong-system</guid><category><![CDATA[#eventStorming]]></category><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[Domain Modeling]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[workshop]]></category><dc:creator><![CDATA[Nadir Badnjevic]]></dc:creator><pubDate>Sat, 07 Mar 2026 09:30:00 GMT</pubDate><content:encoded><![CDATA[<p>Most teams make their most critical architectural decisions during the first few weeks of a project. That's also when collective knowledge about the domain is at its absolute lowest.</p>
<p>It's the Project Paradox, and it's how legacy systems are born. You rush past domain exploration, celebrate quick technical progress, and end up building something that doesn't solve the actual business problem.</p>
<p><a href="https://www.eventstorming.com/">Event Storming</a> breaks this cycle. It's a collaborative workshop technique created by Alberto Brandolini that puts sticky notes on a wall and forces everyone in the room to confront reality together, before a single line of code gets written.</p>
<p>I've used it on many projects ranging from enterprise platforms to multi-tenant SaaS products. Every time, the same thing happens: people who thought they agreed on how the business process works discover they don't. And that discovery, made on Day 3 instead of Month 6, saves the project.</p>
<h2><strong>Domain Events: Why Start There?</strong></h2>
<p>An event in Event Storming is a business fact. Something that already happened. Past tense. Always.</p>
<p><strong><em>Contract Signed. Invoice Sent. Appointment Scheduled. Patient Registered</em>.</strong></p>
<p>This matters because it keeps the conversation grounded. When people write in past tense, they describe reality. When they write in present or future tense, they speculate. And speculation during discovery leads to building features nobody needs.</p>
<p>There's another reason past-tense verbs work so well: they cut through language confusion. Different departments often use the same noun - <em>"Order"</em>, <em>"Client"</em>, <em>"Contract"</em> - to mean completely different things. But when you say <em>"Order Placed"</em> versus <em>"Order Shipped"</em>, the ambiguity disappears. Events reveal hidden boundaries that nouns hide.</p>
<p>Think about the start of your own morning: You heard the alarm -&gt; You switched it off -&gt; You got dressed -&gt; You had breakfast. Each of those is an event. Stringing them together creates a timeline. And that timeline reveals how a process actually works.</p>
<p>Event Storming is, at its core, putting these events on a wall where everyone can see them. You visualize what happens in a business process and then do something with it.</p>
<h2>From Events to Domain Modeling</h2>
<p>Events are just what you can see on the surface. Underneath them is behavior: the repeating patterns a series of events reveals. Deeper still is system structure, the root causes that drive those patterns.</p>
<p>On one project, a healthcare platform, we started by listing events like <em>"Appointment Requested", "Appointment Confirmed",</em> and <em>"Appointment Cancelled"</em>. Simple enough. But when we looked at the pattern, we noticed that <em>"Appointment Cancelled"</em> appeared far more often than anyone expected. That led us to the root cause: the scheduling rules were so rigid that patients couldn't find available slots, so they'd book anything and cancel later. The events told us what was happening. The patterns told us why.</p>
<p>When you run an Event Storming workshop, you start at the surface and work your way down. You're either changing an existing system for the better or designing a new one that produces the events you actually want.</p>
<h2>The Toolkit: What You Actually Need</h2>
<p>For an in-person Big Picture workshop, the requirements are simple. A long wall, a paper roll to cover it, plenty of orange sticky notes, markers for everyone, food and coffee, and no chairs. Standing keeps energy high. Hunger kills it.</p>
<p>For remote sessions, a collaborative whiteboard like Miro works. But know that remote workshops require extra structure. Split them into 1.5-hour sessions across multiple days, use breakout rooms, and find a co-facilitator. Running a remote Event Storming session alone is a bad idea.</p>
<p>Four sticky note types drive a Big Picture session:</p>
<h3>Event</h3>
<img src="https://cdn.hashnode.com/uploads/covers/62a344e51ba253b13869b821/a04d67c4-6d1a-4678-ad8e-424d70429de2.png" alt="An example of an event" style="display:block;margin:0 auto" />

<p>Orange notes for Domain Events. The business facts. The backbone of the entire exercise.</p>
<h3>Hot Spot</h3>
<img src="https://cdn.hashnode.com/uploads/covers/62a344e51ba253b13869b821/9514d71a-3640-4c95-82f4-993b4c9f3dde.png" alt="An example of a hot spot" style="display:block;margin:0 auto" />

<p>Red notes for Hot Spots. Questions nobody can answer, risks nobody has addressed, or topics that spark heated disagreement. I pay more attention to these than anything else on the wall - they're the problems that will surface in production if you don't address them now.</p>
<h3>External System</h3>
<img src="https://cdn.hashnode.com/uploads/covers/62a344e51ba253b13869b821/8529a1d9-3f95-4985-a4a1-e600fbe4d3a9.png" alt="An example of an external systems" style="display:block;margin:0 auto" />

<p>Pink notes for External Systems. Payment gateways, third-party APIs, Excel sheets etc. Systems or tools you don't control but that shape your process.</p>
<h3>Comment</h3>
<img src="https://cdn.hashnode.com/uploads/covers/62a344e51ba253b13869b821/1813acf9-08ed-429f-9fd3-760b2ddc4ca4.png" alt="An example of a comment" style="display:block;margin:0 auto" />

<p>Yellow notes for Comments. Extra context, clarifications, or observations that don't fit neatly into the other categories.</p>
<p>Keep a visible legend on the wall throughout the session. First-timers forget which color means what, and stopping to explain it five times kills momentum.</p>
<h2>Three Levels of Detail</h2>
<p>Event Storming isn't a single technique. It scales across three levels, each suited to a different stage of understanding.</p>
<h3>Big Picture</h3>
<p>Gives you the general overview. You identify key business events, order them in time, and find major pain points. This is where beginners should start, and it's where most of the strategic value lives.</p>
<h3>Process Level</h3>
<p>Goes deeper. You add detail to the map by adding:</p>
<ul>
<li><p>Actors - who triggers the event?</p>
</li>
<li><p>Commands - what action is requested?</p>
</li>
<li><p>Policies - what automated rules apply?</p>
</li>
<li><p>Read models - what data does someone need to make a decision?</p>
</li>
</ul>
<h3>Design Level</h3>
<p>The most detailed. Here, participants group related business rules into aggregates, define pre-conditions and post-conditions, and sometimes write pseudo-code directly on sticky notes. At this level, you're approaching code-ready models, the kind that map to actual project folders and feature slices in your solution.</p>
<p>The Big Picture session is where 80% of the strategic insight comes from. It's also where the cross-functional conversations happen between people who rarely talk to each other. Don't skip it by jumping straight into Design Level. That's a common mistake.</p>
<h2>Running a Big Picture Workshop: Four Steps</h2>
<p>Here's how a typical Big Picture session runs.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62a344e51ba253b13869b821/e6b3a7be-21a4-4fa5-a2a4-e8aa3eb22c6c.png" alt="Flow diagram showing the four steps of a Big Picture Event Storming session: Storm, Timeline, Pivotal Events, and Grouping and Naming" style="display:block;margin:0 auto" />

<h3>Step 1: The Storm</h3>
<p>Everyone gets orange sticky notes and markers. Write down every business event you can think of. Post them on the wall. No wrong answers. The goal is volume. One rule: focus on business events, not technical ones. <em>"Contract Signed"</em> belongs. <em>"Button Clicked"</em> or <em>"Request Sent to API"</em> doesn't. Those details are noise at this level.</p>
<p>When you see a note that's clearly technical or too vague, don't call the person out. Just rotate it 45 degrees. That's the signal it needs revisiting. It keeps the energy up without embarrassing anyone.</p>
<h3>Step 2: The Timeline</h3>
<p>Take all those chaotic orange notes and arrange them chronologically, left to right. The result is a long horizontal line, often called the "snake." This is where gaps become visible. When two events sit next to each other but the team can't explain what connects them, you've found a blind spot.</p>
<p>Do this silently at first. When people talk while sorting, one or two confident voices take over and the timeline reflects their mental model, not the team's. Silent sorting forces everyone to engage physically with the material. Disagreements surface naturally when two people try to place the same event in different spots - that's the conversation you want.</p>
<h3>Step 3: Pivotal Events</h3>
<p>Identify the turning points in the process. They share two characteristics. First, they mark moments where actions become hard to undo. Once an invoice is sent, reversing it costs real money and effort. Second, they often signal a switch of actors, where responsibility shifts from one person or department to another. Mark these with a vertical separator on the wall.</p>
<h3>Step 4: Grouping and Naming</h3>
<p>Pivotal events create natural boundaries. The linear snake breaks into meaningful segments. Let the domain experts name these groups. This turns a flat timeline into a high-level map of the business domain. In DDD terms, these named groups map directly to Bounded Contexts.</p>
<p>Before the session ends, do a quick Vote. Give everyone three to five sticky arrows and ask them to mark the area of the board they think is the most critical problem to solve. You don't need consensus. You need to see where attention is clustering. That becomes the input for your next workshop.</p>
<h2>Why This Matters for Your Architecture</h2>
<p>The groups you discover in Step 4 aren't just organizational labels. They're the starting point for your system's module boundaries.</p>
<p>I've seen teams spend weeks debating module boundaries in meeting rooms, drawing boxes on whiteboards that looked clean but had no connection to how the business actually worked. Event Storming shortcuts that. The groups you discover aren't based on someone's mental model of what the system <em>should</em> look like. They come from how the business actually operates.</p>
<p>And that connects to something I care a lot about in architecture: removability. If your module boundaries match the natural seams of the business, each module becomes replaceable. You can drop a bad idea without the rest of the system collapsing. Not perfect architecture on day one, but architecture that can evolve. That's what you're after. (I wrote about how this plays out in practice in <a href="https://nadirbad.dev/vertical-slice-architecture-folder-structure-4-approaches-compared">Vertical Slice Architecture Folder Structure: 4 Approaches Compared</a>.</p>
<h2>Pre-Workshop Checklist</h2>
<p>Before you run your first session, cover these basics:</p>
<ul>
<li><p>Get a sponsor with authority to send the invitation. Not just someone who thinks it's a good idea — someone who will act on the findings afterward. Without that commitment, the wall of sticky notes goes nowhere.</p>
</li>
<li><p>Define scope to one or two concrete use cases / processes. Don't try to map the entire domain.</p>
</li>
<li><p>Assemble the right people. Roughly half domain experts, half technical. Keep it under 10 participants.</p>
</li>
<li><p>Run a 15-30 minute briefing beforehand so everyone arrives with shared context.</p>
</li>
<li><p>Prepare the physical space. Long wall. Paper roll. Markers. Plenty of orange stickies. Food. No chairs.</p>
</li>
</ul>
<h2>Don't Map the Mess</h2>
<p>If you're working on a legacy system, don't model the as-is state. I made this mistake early on. We spent an entire session mapping how a healthcare scheduling platform currently worked, and all it did was depress the room. Everyone already knew the system was broken. What they needed was a shared picture of where they were going. Event Storm the target vision. Design the future, don't document the mess.</p>
<h2>What You Walk Away With</h2>
<p>The single most valuable outcome of an Event Storming session isn't the wall of sticky notes. It's the shared understanding that forms in the minds of everyone who participated.</p>
<p>You could photograph the board and throw the stickies in the bin. The session was still a success if the team is aligned. That alignment — between business and technical, between what the system does and what the business needs — is what makes the difference between architecture that holds up and architecture you're rewriting in two years.</p>
<p>By the end of the workshop, you have a clear map: the subdomains, the boundaries, and where the natural module seams are. That's your starting point for architecture decisions that hold up as the system grows.</p>
<h2>Quick-Start Checklist</h2>
<p>If you want to run your first Big Picture session this week, here's the short version:</p>
<ul>
<li><p>Get a sponsor to send the invite and commit to acting on the findings</p>
</li>
<li><p>Set the scope (1-2 use cases max)</p>
</li>
<li><p>Assemble 6-10 people, half domain experts, half technical</p>
</li>
<li><p>Run a 15-minute briefing so everyone shows up with shared context</p>
</li>
<li><p>Storm: everyone writes orange sticky notes with business events (rotate bad ones 45°)</p>
</li>
<li><p>Timeline: arrange events left to right, silently first - let disagreements surface naturally</p>
</li>
<li><p>Pivotal Events: mark the turning points with vertical separators</p>
</li>
<li><p>Group and Name: let domain experts label the segments</p>
</li>
<li><p>Vote: mark the most critical problem area before leaving the room</p>
</li>
<li><p>Capture Hot Spots as action items</p>
</li>
</ul>
<p>Event Storming sits between Big Up-Front Design and pure emergent design. Enough structure to align everyone, not so much that you're buried in details before writing a line of code. If your team is about to start a new project, or if your current system has grown into something nobody fully understands, put sticky notes on a wall. The conversations they trigger will save you months of rework.</p>
<p>But here's the question I hear most often after teams run their first workshop: "We have a wall full of sticky notes. Now what?"</p>
<p>And it's exactly what I'll cover in the next article in this series.</p>
<p>If you're planning your first Event Storming session and want a second pair of eyes on your approach, <a href="https://www.linkedin.com/in/nadir-badnjevic/">contact me directly</a> and I'll help you set it up right.</p>
]]></content:encoded></item><item><title><![CDATA[Vertical Slice Architecture Folder Structure: 4 Approaches Compared]]></title><description><![CDATA[You've decided on Vertical Slice Architecture. Now comes the question that trips up every team: how do you actually organize the folders?
I've tried all the common approaches. Some blew up at scale. Others worked fine until someone tried to extract a...]]></description><link>https://nadirbad.dev/vertical-slice-architecture-folder-structure-4-approaches-compared</link><guid isPermaLink="true">https://nadirbad.dev/vertical-slice-architecture-folder-structure-4-approaches-compared</guid><category><![CDATA[dotnet]]></category><category><![CDATA[software development]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[project structure]]></category><category><![CDATA[Vertical Slice Architecture]]></category><dc:creator><![CDATA[Nadir Badnjevic]]></dc:creator><pubDate>Sun, 25 Jan 2026 17:43:49 GMT</pubDate><content:encoded><![CDATA[<p>You've decided on Vertical Slice Architecture. Now comes the question that trips up every team: <em>how do you actually organize the folders?</em></p>
<p>I've tried all the common approaches. Some blew up at scale. Others worked fine until someone tried to extract a microservice. Here are four that actually hold up—and when to use each.</p>
<p>The choice depends on your team size, domain complexity, and how much ceremony you're willing to tolerate.</p>
<p><em>This article is part of the <a target="_blank" href="/vertical-slice-architecture-dotnet">Vertical Slice Architecture Series</a>, which covers implementation patterns, architecture comparisons, and hands-on guides.</em></p>
<h2 id="heading-why-folder-structure-matters-in-vsa">Why Folder Structure Matters in VSA</h2>
<p>Vertical Slice Architecture promises that code which changes together lives together. Your folder structure is what makes that promise real.</p>
<p>Get it right, and your codebase becomes a "screaming architecture." One look at the folder tree tells you what the system does. Get it wrong, and you end up with "grep architecture," where you need search to find anything.</p>
<p>The difference is stark. In a well-organized VSA project, a new developer can open the solution, see folders like <code>Scheduling</code>, <code>Prescriptions</code>, and <code>Billing</code>, and grasp the business capabilities within seconds. They don't need to ask "where does the appointment booking logic live?" The structure answers that question.</p>
<p>Bad structure, on the other hand, creates friction. You waste time navigating. You accidentally duplicate code because you didn't realize it already existed somewhere else. You hesitate to delete features because you're not sure what depends on what.</p>
<p>Jimmy Bogard, who coined the term Vertical Slice Architecture: take all code related to a single user request—from the UI to the database—and move it to a single location on disk. That's the north star. The four approaches below are different ways to achieve it.</p>
<blockquote>
<p><strong>Want my recommendation?</strong> Skip to <a class="post-section-overview" href="#my-recommended-structure">My Recommended Structure</a>. But understanding the trade-offs will help you adapt it to your context.</p>
</blockquote>
<h2 id="heading-approach-1-feature-based-folders">Approach 1: Feature-Based Folders</h2>
<p>This approach creates a separate folder for each feature, with multiple files inside representing different concerns.</p>
<h3 id="heading-structure-overview">Structure Overview</h3>
<pre><code class="lang-text">src/
└── Features/
    └── Scheduling/
        └── BookAppointment/
            ├── BookAppointmentCommand.cs
            ├── BookAppointmentHandler.cs
            ├── BookAppointmentValidator.cs
            ├── BookAppointmentEndpoint.cs
            └── BookAppointmentResult.cs
</code></pre>
<p>Each feature gets its own folder. Inside, you'll find separate files for the command/query, handler, validator, endpoint, and any DTOs. This mirrors the "one class per file" convention that many .NET teams follow.</p>
<h3 id="heading-trade-offs">Trade-offs</h3>
<p>The big win: clear separation makes IDE navigation straightforward. Git diffs show exactly which concern changed (handler vs. validator). Code generation tools that create one file per class work out of the box.</p>
<p>The downside: many small files mean lots of folder drilling to understand a complete feature. For simple CRUD operations, the overhead feels excessive. You'll end up with dozens of folders even for a modest application. And renaming a feature? You're updating multiple files and folders.</p>
<h3 id="heading-when-it-works">When It Works</h3>
<p>Large teams where multiple developers work on different aspects of the same feature simultaneously. The file separation creates natural ownership boundaries. It's also a good fit when your features have real complexity—five separate files make sense when each file contains substantial logic. For a handler that's 10 lines, it's overkill.</p>
<h2 id="heading-approach-2-single-file-with-nested-classes">Approach 2: Single File with Nested Classes</h2>
<p>This approach collapses everything for a feature into one file using nested classes.</p>
<h3 id="heading-structure-overview-1">Structure Overview</h3>
<pre><code class="lang-text">src/
└── Application/
    └── Scheduling/
        ├── BookAppointment.cs
        ├── CancelAppointment.cs
        ├── CompleteAppointment.cs
        ├── GetAppointments.cs
        └── GetAppointmentById.cs
</code></pre>
<p>One file, one feature. The file contains the command/query record, validator, handler, and endpoint as nested classes inside a static outer class. Here's what it looks like in practice, from my <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">Vertical Slice Architecture template</a>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BookAppointment</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">Command</span>(<span class="hljs-params">
        Guid PatientId,
        Guid DoctorId,
        DateTimeOffset Start,
        DateTimeOffset End,
        <span class="hljs-keyword">string</span>? Notes</span>) : IRequest&lt;ErrorOr&lt;Result&gt;&gt;</span>;

    <span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">Result</span>(<span class="hljs-params">Guid Id, DateTime StartUtc, DateTime EndUtc</span>)</span>;

    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Endpoint</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task&lt;IResult&gt; <span class="hljs-title">Handle</span>(<span class="hljs-params">
            Command command,
            ISender mediator</span>)</span>
        {
            <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> mediator.Send(command);
            <span class="hljs-keyword">return</span> result.Match(
                success =&gt; Results.Created(<span class="hljs-string">$"/api/appointments/<span class="hljs-subst">{success.Id}</span>"</span>, success),
                errors =&gt; MinimalApiProblemHelper.Problem(errors));
        }
    }

    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Validator</span> : <span class="hljs-title">AbstractValidator</span>&lt;<span class="hljs-title">Command</span>&gt;
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Validator</span>(<span class="hljs-params"></span>)</span>
        {
            RuleFor(v =&gt; v.PatientId).NotEmpty();
            RuleFor(v =&gt; v.DoctorId).NotEmpty();
            RuleFor(v =&gt; v.Start).LessThan(v =&gt; v.End)
                .WithMessage(<span class="hljs-string">"Start time must be before end time"</span>);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">Handler</span>(<span class="hljs-params">ApplicationDbContext context</span>)
        : IRequestHandler&lt;Command, ErrorOr&lt;Result&gt;&gt;</span>
    {
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;ErrorOr&lt;Result&gt;&gt; Handle(
            Command request, CancellationToken ct)
        {
            <span class="hljs-comment">// Check for conflicts, create appointment, save...</span>
            <span class="hljs-keyword">var</span> appointment = Appointment.Schedule(
                request.PatientId, request.DoctorId,
                request.Start.UtcDateTime, request.End.UtcDateTime,
                request.Notes);

            context.Appointments.Add(appointment);
            <span class="hljs-keyword">await</span> context.SaveChangesAsync(ct);

            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Result(appointment.Id,
                appointment.StartUtc, appointment.EndUtc);
        }
    }
}
</code></pre>
<h3 id="heading-pros-and-cons">Pros and Cons</h3>
<p><strong>Pros:</strong></p>
<ul>
<li>Everything in one place—no jumping between files to understand a feature</li>
<li>Deleting a feature means deleting one file</li>
<li>Reduces ceremony for simple features</li>
<li>The file name (<code>BookAppointment.cs</code>) is self-documenting</li>
<li>Easier to move or refactor entire features</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>Files can grow large for complex features (300+ lines isn't unusual)</li>
<li>Some developers dislike nested classes and find them harder to read</li>
<li>IDE navigation within the file requires folding/unfolding</li>
<li>Code generation tools may need customization</li>
</ul>
<h3 id="heading-when-to-use-this-approach">When to Use This Approach</h3>
<p>This is my preferred approach for small to medium teams building CRUD-heavy applications. When feature cohesion matters more than file size, this structure keeps you moving fast. Putting all related code close together eliminates the "scatter" problem.</p>
<p>The cognitive load argument is strong: when you open <code>BookAppointment.cs</code>, you see everything. No context switching. No wondering where the validator lives.</p>
<p><strong>Want to try this approach?</strong> Check out the <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">Vertical Slice Architecture template</a> or follow the <a target="_blank" href="/vertical-slice-architecture-template-quickstart">Quick Start Guide</a> to set it up.</p>
<h2 id="heading-approach-3-hybrid-clean-vsa">Approach 3: Hybrid Clean + VSA</h2>
<p>This approach keeps some horizontal layers (like a shared Domain) while organizing the Application layer by feature.</p>
<h3 id="heading-structure-overview-2">Structure Overview</h3>
<pre><code class="lang-text">src/
├── Api/
│   └── Program.cs
│
├── Domain/                    # Shared domain entities
│   ├── Appointment.cs
│   ├── Patient.cs
│   └── Doctor.cs
│
├── Application/               # Feature slices
│   ├── Scheduling/
│   │   ├── BookAppointment.cs
│   │   └── CancelAppointment.cs
│   └── Prescriptions/
│       └── CreatePrescription.cs
│
└── Infrastructure/            # Shared infrastructure
    └── Persistence/
        └── ApplicationDbContext.cs
</code></pre>
<p>The Domain and Infrastructure remain as shared horizontal layers. The Application layer uses vertical slices. This is essentially Clean Architecture with the Application layer reorganized by feature.</p>
<h3 id="heading-why-teams-choose-this">Why Teams Choose This</h3>
<p>It's familiar. Teams coming from Clean Architecture don't have to unlearn everything. The domain model stays protected in its own layer. You get a gradual migration path—change the Application layer now, deal with the rest later.</p>
<h3 id="heading-the-catch">The Catch</h3>
<p>You still jump between layers (Domain → Application → Infrastructure). There's a real risk of drifting back toward pure Clean Architecture over time. That "protected domain" layer? It often becomes a shared dumping ground. And you won't get full isolation—features still reach across layers.</p>
<h3 id="heading-when-it-makes-sense">When It Makes Sense</h3>
<p>Teams migrating from Clean Architecture who want feature organization without abandoning familiar patterns. Also appropriate when you have a genuinely complex domain model that benefits from isolation—if your <code>Appointment</code> entity has complex business rules enforced through invariants, keeping it in a dedicated Domain layer is reasonable.</p>
<p>The risk: teams often adopt this as a stepping stone to "real" VSA but never complete the journey. If the hybrid becomes permanent, make that decision intentionally.</p>
<h2 id="heading-approach-4-domain-organized-slices">Approach 4: Domain-Organized Slices</h2>
<p>This approach organizes top-level folders by bounded context or business domain, with each domain containing its own features, entities, and infrastructure.</p>
<h3 id="heading-structure-overview-3">Structure Overview</h3>
<pre><code class="lang-text">src/
├── Api/
│   └── Program.cs
│
├── Scheduling/                # Bounded context
│   ├── Features/
│   │   ├── BookAppointment.cs
│   │   ├── CancelAppointment.cs
│   │   └── GetAppointments.cs
│   ├── Domain/
│   │   └── Appointment.cs
│   └── Infrastructure/
│       └── SchedulingDbContext.cs
│
├── Prescriptions/             # Bounded context
│   ├── Features/
│   │   └── CreatePrescription.cs
│   ├── Domain/
│   │   └── Prescription.cs
│   └── Infrastructure/
│       └── PrescriptionsDbContext.cs
│
└── Shared/                    # Cross-cutting kernel
    └── Common/
        └── AuditableEntity.cs
</code></pre>
<p>Each bounded context is self-contained. It has its own features, domain entities, and infrastructure. The only shared code lives in a <code>Shared</code> or <code>Kernel</code> folder for truly cross-cutting concerns.</p>
<h3 id="heading-pros-and-cons-1">Pros and Cons</h3>
<p><strong>Pros:</strong></p>
<ul>
<li>Natural path to a modular monolith</li>
<li>Clear bounded context boundaries from day one</li>
<li>Each domain can evolve independently (different patterns, different complexity)</li>
<li>Easy to extract into microservices later if needed</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>May duplicate common infrastructure (separate DbContexts, separate validators)</li>
<li>Requires upfront domain knowledge to draw the right boundaries</li>
<li>More complex project structure for small applications</li>
<li>Risk of creating artificial boundaries that don't match the business</li>
</ul>
<h3 id="heading-when-to-use-this-approach-1">When to Use This Approach</h3>
<p>This is the right choice when you're building a system that you expect to split into modules or microservices. If multiple teams will own different business domains, this structure sets clear ownership boundaries from the start.</p>
<p>It's also appropriate when bounded contexts are well-defined—because you've already done the collaborative discovery work. Techniques like <a target="_blank" href="https://www.eventstorming.com/">Event Storming</a> or <a target="_blank" href="https://domainstorytelling.org/">Domain Storytelling</a> help teams build shared understanding of the business domain before writing code. If you've been through workshops like these and can articulate the key use cases, processes, and where one domain ends and another begins ("Scheduling is separate from Prescriptions because..."), domain-organized slices are the obvious choice.</p>
<p>If you haven't done this discovery work yet, start with a simpler approach and refactor when the domain boundaries become clear through experience.</p>
<h2 id="heading-my-recommended-structure">My Recommended Structure</h2>
<p>I combine <strong>Approach 4 (domain-organized slices)</strong> at the top level with <strong>Approach 2 (single file)</strong> inside each feature. Here's what it looks like in my <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">Vertical Slice Architecture template</a>:</p>
<pre><code class="lang-text">src/
├── Api/                          # ASP.NET Core entry point
│   └── Program.cs
│
└── Application/                  # All features and domain logic
    ├── Scheduling/               # Feature slice folder
    │   ├── BookAppointment.cs    # Command + Validator + Handler + Endpoint
    │   ├── CancelAppointment.cs
    │   ├── CompleteAppointment.cs
    │   ├── GetAppointments.cs
    │   ├── GetAppointmentById.cs
    │   └── AppointmentDto.cs     # Shared DTO for the feature area
    │
    ├── Domain/                   # Domain entities and events
    │   ├── Appointment.cs
    │   ├── Patient.cs
    │   └── Doctor.cs
    │
    ├── Common/                   # Shared infrastructure
    │   ├── Behaviours/
    │   └── Models/
    │
    └── Infrastructure/           # Data access
        └── Persistence/
            └── ApplicationDbContext.cs
</code></pre>
<p><strong>Why this combination?</strong></p>
<p>The domain-organized top level (<code>Scheduling/</code>, <code>Domain/</code>, <code>Common/</code>) gives me clear navigation and screaming architecture. I can see the business capabilities at a glance. The single-file features inside each area minimize ceremony and keep related code together.</p>
<p>The <code>Common/</code> and <code>Infrastructure/</code> folders hold genuinely shared code—MediatR behaviors, validation filters, the DbContext. These are things every feature needs, so centralizing them makes sense. But feature-specific logic stays in feature files.</p>
<p>This structure works for most projects I encounter: complex enough to need organization, but not so large that separate bounded context projects are justified. Start here, evolve when needed.</p>
<h2 id="heading-common-mistakes-to-avoid">Common Mistakes to Avoid</h2>
<p><strong>Creating a "Shared" folder that becomes a dumping ground.</strong> Every team does this. You create <code>Shared/</code> or <code>Common/</code> for genuinely cross-cutting code, and within months it contains business logic that belongs in specific features. Be ruthless: if code relates to a specific feature, it lives with that feature. Only true infrastructure belongs in shared folders.</p>
<p><strong>Mixing approaches inconsistently.</strong> Pick one approach and stick with it across the codebase. I've seen projects where half the features use single files and half use separate folders. The cognitive overhead of switching mental models as you navigate the code is significant. Consistency matters more than picking the "perfect" approach.</p>
<p><strong>Over-engineering folder structure before you have features.</strong> Don't create elaborate hierarchies for a system that has three endpoints. Start flat, and add structure when you feel the pain. Premature organization is just as problematic as premature optimization.</p>
<p><strong>Ignoring the "screaming architecture" test.</strong> Periodically ask: can a new developer look at this folder structure and understand what the system does? If they see <code>Controllers/</code>, <code>Services/</code>, <code>Repositories/</code> instead of <code>Scheduling/</code>, <code>Prescriptions/</code>, <code>Billing/</code>, your structure is screaming about technology instead of business capabilities.</p>
<h2 id="heading-picking-the-right-approach">Picking the Right Approach</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Approach</td><td>Best For</td><td>Avoid When</td></tr>
</thead>
<tbody>
<tr>
<td>Feature-Based Folders</td><td>Large teams, complex features</td><td>Simple CRUD, small teams</td></tr>
<tr>
<td>Single File (Nested Classes)</td><td>Small-medium teams, CRUD apps</td><td>Very complex features (300+ lines)</td></tr>
<tr>
<td>Hybrid Clean + VSA</td><td>Clean Architecture migrations</td><td>Greenfield projects</td></tr>
<tr>
<td>Domain-Organized Slices</td><td>Modular monoliths, multi-team</td><td>Unclear domain boundaries</td></tr>
</tbody>
</table>
</div><p>There's no universally correct answer. The best structure is the one your team can maintain consistently.</p>
<p>Start with the single-file approach (Approach 2) if you're unsure. It delivers most of the VSA benefits with minimal overhead. You can always refactor toward feature folders or domain organization as your application grows.</p>
<p>For a deeper dive into Vertical Slice Architecture itself—including CQRS patterns, MediatR setup, and testing strategies—see <a target="_blank" href="/vertical-slice-architecture-dotnet">Vertical Slice Architecture in .NET 10: The Ultimate Guide</a> or explore <a target="_blank" href="/vertical-slice-vs-clean-architecture">how VSA compares to Clean Architecture</a>.</p>
<hr />
<p><strong>Stop debating folder structure in code reviews.</strong> Clone the <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">Vertical Slice Architecture template</a> and start building. It uses the single-file approach with domain organization - the combination I recommend for most .NET projects.</p>
]]></content:encoded></item><item><title><![CDATA[Getting Started with the Vertical Slice Architecture Template for .NET 10]]></title><description><![CDATA[I built this template because I was tired of creating the same boilerplate every time I started a new project. After writing about Vertical Slice Architecture and seeing the pattern gain traction, I wanted a starting point that showed how all the pie...]]></description><link>https://nadirbad.dev/vertical-slice-architecture-template-quickstart</link><guid isPermaLink="true">https://nadirbad.dev/vertical-slice-architecture-template-quickstart</guid><category><![CDATA[dotnet]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Aspnetcore]]></category><category><![CDATA[c sharp]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software design]]></category><category><![CDATA[dotnet9]]></category><category><![CDATA[dotnetcore]]></category><dc:creator><![CDATA[Nadir Badnjevic]]></dc:creator><pubDate>Sat, 17 Jan 2026 19:52:35 GMT</pubDate><content:encoded><![CDATA[<p>I built this template because I was tired of creating the same boilerplate every time I started a new project. After <a target="_blank" href="/vertical-slice-architecture-dotnet">writing about Vertical Slice Architecture</a> and seeing the pattern gain traction, I wanted a starting point that showed how all the pieces fit together in a real domain—not another todo app.</p>
<p>This guide walks you through cloning the template, running it, understanding its structure, and adding your first feature.</p>
<h2 id="heading-whats-included-in-the-template">What's Included in the Template</h2>
<p>The <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">Vertical Slice Architecture template</a> models a medical clinic appointment scheduling system. Patients book appointments with doctors, and the system enforces real-world constraints: no double-booking, appointments scheduled in advance, and controlled state transitions.</p>
<p><strong>Features implemented:</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td>Endpoint</td><td>What It Does</td></tr>
</thead>
<tbody>
<tr>
<td>Book Appointment</td><td><code>POST /api/appointments</code></td><td>Creates appointment with conflict detection</td></tr>
<tr>
<td>Get Appointments</td><td><code>GET /api/appointments</code></td><td>Filtered, paginated list</td></tr>
<tr>
<td>Get by ID</td><td><code>GET /api/appointments/{id}</code></td><td>Single appointment with details</td></tr>
<tr>
<td>Complete</td><td><code>POST /api/appointments/{id}/complete</code></td><td>Marks appointment done</td></tr>
<tr>
<td>Cancel</td><td><code>POST /api/appointments/{id}/cancel</code></td><td>Cancels with required reason</td></tr>
</tbody>
</table>
</div><p><strong>Tech stack:</strong></p>
<ul>
<li>.NET 10 Minimal APIs</li>
<li>MediatR for request/response pattern</li>
<li>FluentValidation for declarative validation</li>
<li>ErrorOr for result pattern error handling</li>
<li>Entity Framework Core 10 (in-memory or SQL Server)</li>
<li>xUnit + FluentAssertions for testing</li>
</ul>
<p>The template has over 540 stars on GitHub. It's not production-ready as-is. Think of it as a learning tool that shows patterns you can adapt for your own projects.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<h3 id="heading-net-10-sdk">.NET 10 SDK</h3>
<p>Download from <a target="_blank" href="https://dot.net/download">dot.net</a>. Verify your installation:</p>
<pre><code class="lang-bash">dotnet --version
<span class="hljs-comment"># Should output 10.0.x</span>
</code></pre>
<h3 id="heading-ide-options">IDE Options</h3>
<p>Pick what works for you:</p>
<ul>
<li><strong>Visual Studio 2022</strong> (17.8+) — Full IDE experience, best debugging</li>
<li><strong>JetBrains Rider</strong> — Cross-platform, strong refactoring tools</li>
<li><strong>VS Code</strong> — Lightweight, free</li>
</ul>
<h3 id="heading-optional-docker-for-sql-server">Optional: Docker for SQL Server</h3>
<p>The template runs with an in-memory database by default—no setup required. If you want persistent data with SQL Server, you'll need Docker:</p>
<pre><code class="lang-bash">docker pull mcr.microsoft.com/azure-sql-edge:latest
</code></pre>
<p>Azure SQL Edge works on all platforms including Apple Silicon.</p>
<h2 id="heading-installation-and-setup">Installation and Setup</h2>
<h3 id="heading-clone-the-repository">Clone the Repository</h3>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/nadirbad/VerticalSliceArchitecture.git
<span class="hljs-built_in">cd</span> VerticalSliceArchitecture
</code></pre>
<h3 id="heading-database-configuration">Database Configuration</h3>
<p><strong>Option 1: In-Memory (Default)</strong></p>
<p>Nothing to configure. The template uses an in-memory database by default. Sample data gets seeded automatically in development mode.</p>
<p><strong>Option 2: SQL Server</strong></p>
<p>Start a SQL Server container:</p>
<pre><code class="lang-bash">docker run --cap-add SYS_PTRACE -e <span class="hljs-string">'ACCEPT_EULA=1'</span> -e <span class="hljs-string">'MSSQL_SA_PASSWORD=yourStrong(!)Password'</span> -p 1433:1433 --name azuresqledge -d mcr.microsoft.com/azure-sql-edge
</code></pre>
<p>Update <code>src/Api/appsettings.json</code>:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"UseInMemoryDatabase"</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">"ConnectionStrings"</span>: {
    <span class="hljs-attr">"DefaultConnection"</span>: <span class="hljs-string">"Server=localhost;Database=VerticalSliceDb;User Id=sa;Password=yourStrong(!)Password;TrustServerCertificate=True"</span>
  }
}
</code></pre>
<p>Apply migrations:</p>
<pre><code class="lang-bash">dotnet ef database update --project src/Application --startup-project src/Api
</code></pre>
<h3 id="heading-running-the-api">Running the API</h3>
<pre><code class="lang-bash">dotnet run --project src/Api/Api.csproj
</code></pre>
<p>Open <a target="_blank" href="https://localhost:7098">https://localhost:7098</a> in your browser. Swagger UI loads at the root with sample patient and doctor IDs ready to use.</p>
<p>Try booking an appointment:</p>
<pre><code class="lang-bash">curl -X POST https://localhost:7098/api/appointments \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{
    "patientId": "11111111-1111-1111-1111-111111111111",
    "doctorId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
    "start": "2027-01-20T09:00:00Z",
    "end": "2027-01-20T09:30:00Z",
    "notes": "Annual checkup"
  }'</span>
</code></pre>
<h2 id="heading-exploring-the-codebase">Exploring the Codebase</h2>
<h3 id="heading-solution-structure">Solution Structure</h3>
<pre><code class="lang-text">src/
├── Api/                          # ASP.NET Core entry point
│   └── Program.cs                # Minimal hosting, Swagger config
│
└── Application/                  # All features and domain logic
    ├── Scheduling/               # Feature slices
    │   ├── BookAppointment.cs
    │   ├── CancelAppointment.cs
    │   ├── CompleteAppointment.cs
    │   ├── GetAppointments.cs
    │   └── GetAppointmentById.cs
    │
    ├── Domain/                   # Entities and domain events
    │   ├── Appointment.cs
    │   ├── Patient.cs
    │   └── Doctor.cs
    │
    ├── Common/                   # Shared infrastructure
    │   └── Behaviours/           # MediatR pipeline behaviors
    │
    └── Infrastructure/
        └── Persistence/          # EF Core DbContext
</code></pre>
<p>Notice the <code>Scheduling/</code> folder. Each file is a complete feature—command, validator, handler, and endpoint in one place. No jumping between Controllers, Services, and Repositories folders. For a deeper dive into folder organization options, see <a target="_blank" href="/vertical-slice-architecture-folder-structure">VSA Folder Structure: 4 Approaches Compared</a>.</p>
<h3 id="heading-feature-anatomy">Feature Anatomy</h3>
<p>Open <code>BookAppointment.cs</code>. A single file contains four components:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BookAppointment</span>
{
    <span class="hljs-comment">// 1. Command - What the caller sends</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">Command</span>(<span class="hljs-params">
        Guid PatientId,
        Guid DoctorId,
        DateTimeOffset Start,
        DateTimeOffset End,
        <span class="hljs-keyword">string</span>? Notes</span>) : IRequest&lt;ErrorOr&lt;Result&gt;&gt;</span>;

    <span class="hljs-comment">// 2. Result - What the caller gets back</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">Result</span>(<span class="hljs-params">Guid Id, DateTime StartUtc, DateTime EndUtc</span>)</span>;

    <span class="hljs-comment">// 3. Endpoint - HTTP binding</span>
    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Endpoint</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task&lt;IResult&gt; <span class="hljs-title">Handle</span>(<span class="hljs-params">
            Command command,
            ISender mediator</span>)</span>
        {
            <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> mediator.Send(command);
            <span class="hljs-keyword">return</span> result.Match(
                success =&gt; Results.Created(<span class="hljs-string">$"/api/appointments/<span class="hljs-subst">{success.Id}</span>"</span>, success),
                errors =&gt; MinimalApiProblemHelper.Problem(errors));
        }
    }

    <span class="hljs-comment">// 4. Validator - Input validation</span>
    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Validator</span> : <span class="hljs-title">AbstractValidator</span>&lt;<span class="hljs-title">Command</span>&gt;
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Validator</span>(<span class="hljs-params"></span>)</span>
        {
            RuleFor(v =&gt; v.PatientId).NotEmpty();
            RuleFor(v =&gt; v.DoctorId).NotEmpty();
            RuleFor(v =&gt; v.Start).LessThan(v =&gt; v.End);
            <span class="hljs-comment">// ... more rules</span>
        }
    }

    <span class="hljs-comment">// 5. Handler - Business logic</span>
    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Handler</span> : <span class="hljs-title">IRequestHandler</span>&lt;<span class="hljs-title">Command</span>, <span class="hljs-title">ErrorOr</span>&lt;<span class="hljs-title">Result</span>&gt;&gt;
    {
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;ErrorOr&lt;Result&gt;&gt; Handle(
            Command request,
            CancellationToken cancellationToken)
        {
            <span class="hljs-comment">// Check conflicts, create appointment, save</span>
        }
    }
}
</code></pre>
<p>Everything for booking an appointment lives in one file. When requirements change, you modify one file. When you review a PR, you see the complete context.</p>
<h3 id="heading-testing-setup">Testing Setup</h3>
<p>Tests mirror the feature structure:</p>
<pre><code class="lang-bash">dotnet <span class="hljs-built_in">test</span>

<span class="hljs-comment"># Unit tests - validators and domain logic</span>
dotnet <span class="hljs-built_in">test</span> tests/Application.UnitTests

<span class="hljs-comment"># Integration tests - API endpoints</span>
dotnet <span class="hljs-built_in">test</span> tests/Application.IntegrationTests
</code></pre>
<p>Unit tests use FluentValidation's <code>TestValidate</code> helper:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BookAppointmentValidatorTests</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> BookAppointment.Validator _validator = <span class="hljs-keyword">new</span>();

    [<span class="hljs-meta">Fact</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Should_Have_Error_When_PatientId_Is_Empty</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> command = <span class="hljs-keyword">new</span> BookAppointment.Command(
            Guid.Empty, Guid.NewGuid(),
            DateTimeOffset.UtcNow.AddHours(<span class="hljs-number">1</span>),
            DateTimeOffset.UtcNow.AddHours(<span class="hljs-number">2</span>),
            <span class="hljs-literal">null</span>);

        <span class="hljs-keyword">var</span> result = _validator.TestValidate(command);
        result.ShouldHaveValidationErrorFor(x =&gt; x.PatientId);
    }
}
</code></pre>
<h2 id="heading-creating-your-first-feature">Creating Your First Feature</h2>
<p>Let's add a <code>RescheduleAppointment</code> feature that moves an existing appointment to a new time slot.</p>
<h3 id="heading-step-1-create-the-feature-file">Step 1: Create the Feature File</h3>
<p>Create <code>src/Application/Scheduling/RescheduleAppointment.cs</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> ErrorOr;
<span class="hljs-keyword">using</span> FluentValidation;
<span class="hljs-keyword">using</span> MediatR;
<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Http;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;
<span class="hljs-keyword">using</span> VerticalSliceArchitecture.Application.Common;
<span class="hljs-keyword">using</span> VerticalSliceArchitecture.Application.Domain;
<span class="hljs-keyword">using</span> VerticalSliceArchitecture.Application.Infrastructure.Persistence;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">VerticalSliceArchitecture.Application.Scheduling</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">RescheduleAppointment</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">Command</span>(<span class="hljs-params">
        Guid AppointmentId,
        DateTimeOffset NewStart,
        DateTimeOffset NewEnd</span>) : IRequest&lt;ErrorOr&lt;Result&gt;&gt;</span>;

    <span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">Result</span>(<span class="hljs-params">Guid Id, DateTime StartUtc, DateTime EndUtc</span>)</span>;

    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Endpoint</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task&lt;IResult&gt; <span class="hljs-title">Handle</span>(<span class="hljs-params">
            Guid appointmentId,
            Command command,
            ISender mediator</span>)</span>
        {
            <span class="hljs-keyword">if</span> (appointmentId != command.AppointmentId)
            {
                <span class="hljs-keyword">return</span> Results.BadRequest(<span class="hljs-keyword">new</span> { error = <span class="hljs-string">"Route ID does not match command"</span> });
            }

            <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> mediator.Send(command);
            <span class="hljs-keyword">return</span> result.Match(
                success =&gt; Results.Ok(success),
                errors =&gt; MinimalApiProblemHelper.Problem(errors));
        }
    }

    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Validator</span> : <span class="hljs-title">AbstractValidator</span>&lt;<span class="hljs-title">Command</span>&gt;
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Validator</span>(<span class="hljs-params"></span>)</span>
        {
            RuleFor(v =&gt; v.AppointmentId).NotEmpty();
            RuleFor(v =&gt; v.NewStart).LessThan(v =&gt; v.NewEnd)
                .WithMessage(<span class="hljs-string">"Start time must be before end time"</span>);
            RuleFor(v =&gt; v.NewStart)
                .Must(start =&gt; start &gt; DateTimeOffset.UtcNow.AddMinutes(<span class="hljs-number">15</span>))
                .WithMessage(<span class="hljs-string">"Must reschedule at least 15 minutes in advance"</span>);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">Handler</span>(<span class="hljs-params">ApplicationDbContext context</span>)
        : IRequestHandler&lt;Command, ErrorOr&lt;Result&gt;&gt;</span>
    {
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;ErrorOr&lt;Result&gt;&gt; Handle(
            Command request,
            CancellationToken cancellationToken)
        {
            <span class="hljs-keyword">var</span> appointment = <span class="hljs-keyword">await</span> context.Appointments
                .FirstOrDefaultAsync(a =&gt; a.Id == request.AppointmentId, cancellationToken);

            <span class="hljs-keyword">if</span> (appointment <span class="hljs-keyword">is</span> <span class="hljs-literal">null</span>)
            {
                <span class="hljs-keyword">return</span> Error.NotFound(<span class="hljs-string">"Appointment.NotFound"</span>,
                    <span class="hljs-string">$"Appointment <span class="hljs-subst">{request.AppointmentId}</span> not found"</span>);
            }

            <span class="hljs-keyword">if</span> (appointment.Status != AppointmentStatus.Scheduled)
            {
                <span class="hljs-keyword">return</span> Error.Validation(<span class="hljs-string">"Appointment.CannotReschedule"</span>,
                    <span class="hljs-string">"Only scheduled appointments can be rescheduled"</span>);
            }

            <span class="hljs-comment">// Check for conflicts at new time</span>
            <span class="hljs-keyword">var</span> hasConflict = <span class="hljs-keyword">await</span> context.Appointments
                .AnyAsync(a =&gt;
                    a.Id != request.AppointmentId &amp;&amp;
                    a.DoctorId == appointment.DoctorId &amp;&amp;
                    a.Status == AppointmentStatus.Scheduled &amp;&amp;
                    a.StartUtc &lt; request.NewEnd.UtcDateTime &amp;&amp;
                    a.EndUtc &gt; request.NewStart.UtcDateTime,
                    cancellationToken);

            <span class="hljs-keyword">if</span> (hasConflict)
            {
                <span class="hljs-keyword">return</span> Error.Conflict(<span class="hljs-string">"Appointment.Conflict"</span>,
                    <span class="hljs-string">"Doctor has a conflicting appointment at the new time"</span>);
            }

            <span class="hljs-comment">// Update times (you'd add a Reschedule method to the domain entity)</span>
            <span class="hljs-comment">// For now, this shows the pattern</span>
            <span class="hljs-keyword">await</span> context.SaveChangesAsync(cancellationToken);

            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Result(appointment.Id, appointment.StartUtc, appointment.EndUtc);
        }
    }
}
</code></pre>
<h3 id="heading-step-2-register-the-endpoint">Step 2: Register the Endpoint</h3>
<p>Open <code>src/Application/Scheduling/AppointmentEndpoints.cs</code> and add:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">group</span>.MapPost(<span class="hljs-string">"/{appointmentId:guid}/reschedule"</span>, RescheduleAppointment.Endpoint.Handle)
    .WithName(<span class="hljs-string">"RescheduleAppointment"</span>)
    .WithSummary(<span class="hljs-string">"Reschedule an existing appointment"</span>)
    .Produces&lt;RescheduleAppointment.Result&gt;()
    .ProducesProblem(StatusCodes.Status404NotFound)
    .ProducesProblem(StatusCodes.Status409Conflict);
</code></pre>
<h3 id="heading-step-3-add-a-test">Step 3: Add a Test</h3>
<p>Create <code>tests/Application.UnitTests/Scheduling/RescheduleAppointmentValidatorTests.cs</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> FluentValidation.TestHelper;
<span class="hljs-keyword">using</span> VerticalSliceArchitecture.Application.Scheduling;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">VerticalSliceArchitecture.Application.UnitTests.Scheduling</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">RescheduleAppointmentValidatorTests</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> RescheduleAppointment.Validator _validator = <span class="hljs-keyword">new</span>();

    [<span class="hljs-meta">Fact</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Should_Have_Error_When_Start_After_End</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> command = <span class="hljs-keyword">new</span> RescheduleAppointment.Command(
            Guid.NewGuid(),
            DateTimeOffset.UtcNow.AddHours(<span class="hljs-number">2</span>),
            DateTimeOffset.UtcNow.AddHours(<span class="hljs-number">1</span>));

        <span class="hljs-keyword">var</span> result = _validator.TestValidate(command);
        result.ShouldHaveValidationErrorFor(x =&gt; x.NewStart);
    }

    [<span class="hljs-meta">Fact</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Should_Not_Have_Error_When_Valid</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> start = DateTimeOffset.UtcNow.AddHours(<span class="hljs-number">1</span>);
        <span class="hljs-keyword">var</span> command = <span class="hljs-keyword">new</span> RescheduleAppointment.Command(
            Guid.NewGuid(), start, start.AddHours(<span class="hljs-number">1</span>));

        <span class="hljs-keyword">var</span> result = _validator.TestValidate(command);
        result.ShouldNotHaveAnyValidationErrors();
    }
}
</code></pre>
<p>Run tests to verify:</p>
<pre><code class="lang-bash">dotnet <span class="hljs-built_in">test</span>
</code></pre>
<h2 id="heading-customization-options">Customization Options</h2>
<h3 id="heading-switching-databases">Switching Databases</h3>
<p>The <code>ConfigureServices.cs</code> file controls database selection:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IServiceCollection <span class="hljs-title">AddInfrastructure</span>(<span class="hljs-params">
    <span class="hljs-keyword">this</span> IServiceCollection services,
    IConfiguration configuration</span>)</span>
{
    <span class="hljs-keyword">if</span> (configuration.GetValue&lt;<span class="hljs-keyword">bool</span>&gt;(<span class="hljs-string">"UseInMemoryDatabase"</span>))
    {
        services.AddDbContext&lt;ApplicationDbContext&gt;(options =&gt;
            options.UseInMemoryDatabase(<span class="hljs-string">"VerticalSliceDb"</span>));
    }
    <span class="hljs-keyword">else</span>
    {
        services.AddDbContext&lt;ApplicationDbContext&gt;(options =&gt;
            options.UseSqlServer(
                configuration.GetConnectionString(<span class="hljs-string">"DefaultConnection"</span>)));
    }
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>For PostgreSQL, swap <code>UseSqlServer</code> for <code>UseNpgsql</code> and add the Npgsql.EntityFrameworkCore.PostgreSQL package.</p>
<h3 id="heading-adding-new-domains">Adding New Domains</h3>
<p>The template focuses on scheduling, but you can add parallel domains:</p>
<ol>
<li>Create a new folder under <code>Application/</code> (e.g., <code>Billing/</code>)</li>
<li>Add feature files following the same pattern</li>
<li>Create a domain entity in <code>Domain/</code></li>
<li>Register endpoints in a new <code>BillingEndpoints.cs</code></li>
<li>Map endpoints in <code>Program.cs</code>: <code>app.MapBillingEndpoints()</code></li>
</ol>
<p>Keep domains isolated. Features within a domain can share its entities, but don't couple domains to each other.</p>
<h3 id="heading-modifying-pipeline-behaviors">Modifying Pipeline Behaviors</h3>
<p>MediatR pipeline behaviors run for every request. The template includes:</p>
<ul>
<li><strong>ValidationBehaviour</strong> — Runs FluentValidation before the handler</li>
<li><strong>PerformanceBehaviour</strong> — Logs slow requests</li>
</ul>
<p>Add your own in <code>Common/Behaviours/</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">LoggingBehaviour</span>&lt;<span class="hljs-title">TRequest</span>, <span class="hljs-title">TResponse</span>&gt;
    : <span class="hljs-title">IPipelineBehavior</span>&lt;<span class="hljs-title">TRequest</span>, <span class="hljs-title">TResponse</span>&gt;
    <span class="hljs-keyword">where</span> <span class="hljs-title">TRequest</span> : <span class="hljs-title">IRequest</span>&lt;<span class="hljs-title">TResponse</span>&gt;
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;TResponse&gt; <span class="hljs-title">Handle</span>(<span class="hljs-params">
        TRequest request,
        RequestHandlerDelegate&lt;TResponse&gt; next,
        CancellationToken cancellationToken</span>)</span>
    {
        <span class="hljs-comment">// Log before</span>
        <span class="hljs-keyword">var</span> response = <span class="hljs-keyword">await</span> next();
        <span class="hljs-comment">// Log after</span>
        <span class="hljs-keyword">return</span> response;
    }
}
</code></pre>
<p>Register in <code>ConfigureServices.cs</code>:</p>
<pre><code class="lang-csharp">options.AddOpenBehavior(<span class="hljs-keyword">typeof</span>(LoggingBehaviour&lt;,&gt;));
</code></pre>
<h2 id="heading-next-steps">Next Steps</h2>
<p>From here:</p>
<ul>
<li><strong>Understand the theory</strong>: Read <a target="_blank" href="/vertical-slice-architecture-dotnet">Vertical Slice Architecture in .NET: The Ultimate Guide</a> for the architectural reasoning behind this approach</li>
<li><strong>Compare alternatives</strong>: See <a target="_blank" href="/vertical-slice-vs-clean-architecture">Vertical Slice vs. Clean Architecture</a> if you're deciding between patterns</li>
<li><strong>Explore folder options</strong>: Check out <a target="_blank" href="/vertical-slice-architecture-folder-structure">VSA Folder Structure: 4 Approaches Compared</a> to see different organization strategies</li>
<li><strong>Contribute</strong>: The <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">template repo</a> welcomes issues and PRs</li>
</ul>
<p>The template is a starting point, not a framework. Take what works for your domain. Skip what doesn't.</p>
<hr />
<p><em>Found this useful? Star the <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">GitHub repo</a> to help others discover it.</em></p>
]]></content:encoded></item><item><title><![CDATA[Vertical Slice vs. Clean Architecture: Which Should You Choose?]]></title><description><![CDATA[Project structure shapes everything: onboarding speed, maintainability, how fast you ship features. For years, Clean Architecture (and its cousins Onion and Hexagonal) was the default. But Vertical Slice Architecture (VSA) has become a serious altern...]]></description><link>https://nadirbad.dev/vertical-slice-vs-clean-architecture</link><guid isPermaLink="true">https://nadirbad.dev/vertical-slice-vs-clean-architecture</guid><category><![CDATA[software development]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Clean Architecture]]></category><category><![CDATA[Vertical Slice Architecture]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software design]]></category><dc:creator><![CDATA[Nadir Badnjevic]]></dc:creator><pubDate>Fri, 26 Dec 2025 19:32:18 GMT</pubDate><content:encoded><![CDATA[<p>Project structure shapes everything: onboarding speed, maintainability, how fast you ship features. For years, Clean Architecture (and its cousins Onion and Hexagonal) was the default. But Vertical Slice Architecture (VSA) has become a serious alternative, and I've watched teams argue about this more than almost anything else.</p>
<p>This comparison applies equally to Onion and Hexagonal architectures, which share Clean Architecture's layered philosophy. The feature-based vs layered architecture debate comes down to where you want your coupling.</p>
<p>So which one fits your project? Let's break it down.</p>
<p><em>This article is part of my</em> <a target="_blank" href="/vertical-slice-architecture-dotnet"><em>Complete Guide to Vertical Slice Architecture in .NET</em></a><em>, which includes a production-ready template with 530+ GitHub stars-</em><a target="_blank" href="https://blog.ndepend.com/vertical-slice-architecture-in-asp-net-core/"><em>featured in NDepend's architecture comparison</em></a>.</p>
<blockquote>
<p><strong>Real-world comparison:</strong> This article compares two production-ready templates—<a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">Vertical Slice Architecture template for .NET 9</a> (530+ stars, healthcare domain) and <a target="_blank" href="https://github.com/jasontaylordev/CleanArchitecture">Jason Taylor's Clean Architecture template</a> (17k+ stars, ToDo). All code examples are from these actual codebases.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766775679779/81d5b107-b07f-4189-a5ee-f710c9b06659.png" alt="Clean Architecture vs Vertical Slice Architecture comparison infographic showing layered approach versus feature-first approach with decision guide" class="image--center mx-auto" /></p>
<h2 id="heading-clean-architecture-the-layered-approach">Clean Architecture: The Layered Approach</h2>
<p>Clean Architecture (CA), popularized by Robert C. Martin, organizes code through concentric layers. The key rule: all dependencies point inward toward the core.</p>
<ul>
<li><p><strong>Domain (Core):</strong> Business rules and entities live here. This layer depends on nothing else, so database or UI changes can't touch it.</p>
</li>
<li><p><strong>Application (Use Cases):</strong> Application-specific business logic.</p>
</li>
<li><p><strong>Infrastructure/UI (Outer Rings):</strong> The messy stuff: databases, web frameworks, external APIs.</p>
</li>
</ul>
<p>The goal? Keep business logic independent from frameworks and tools. In theory, you could swap your database with minimal pain.</p>
<h2 id="heading-vertical-slice-architecture-the-feature-first-approach">Vertical Slice Architecture: The Feature-First Approach</h2>
<p>Vertical Slice Architecture (VSA), coined by Jimmy Bogard, flips the script. Instead of organizing by technical layer, you organize by feature.</p>
<p>A "slice" cuts through the entire stack - from API endpoint to database for one specific use case. The guiding principle: <strong>minimize coupling between slices, maximize cohesion within a slice.</strong></p>
<p>When you add a feature in VSA, you're not scattering changes across layers. You're working in one folder that owns everything for that request.</p>
<h2 id="heading-how-they-compare">How They Compare</h2>
<h3 id="heading-code-organization">Code Organization</h3>
<p>Clean Architecture organizes by <strong>type</strong>. You get a folder for controllers, another for services, a project for repositories. Classic technical layering.</p>
<p>VSA organizes by <strong>capability</strong>. Got an "Order Cancellation" feature? There's an <code>OrderCancellation</code> folder with the endpoint, request model, handler, and data access all together. Some call this "Screaming Architecture" because your folder structure tells you what the system does.</p>
<h3 id="heading-the-cohesion-problem">The Cohesion Problem</h3>
<p>Clean Architecture keeps technical layers loosely coupled. But here's the trade-off: code that changes together (a feature's UI and its database schema) ends up scattered across multiple projects.</p>
<p>VSA takes the opposite bet, following what's sometimes called the <strong>code locality principle</strong>: <em>artifacts that change together should be grouped together.</em> The API, logic, and database for a feature are naturally coupled—they change together. So keep them together. Less jumping between files to understand what's happening.</p>
<h3 id="heading-real-example-adding-a-field">Real Example: Adding a Field</h3>
<p>Let's trace what happens when you need to add a field to a feature in both architectures. I'll use real examples from production templates.</p>
<p><strong>In Clean Architecture</strong> (<a target="_blank" href="https://github.com/jasontaylordev/CleanArchitecture">Jason Taylor's template</a>):</p>
<p>To add a "Tags" field to the TodoItem feature, you touch <strong>8-10 files across 4 projects</strong>:</p>
<ol>
<li><a target="_blank" href="https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Domain/Entities/TodoItem.cs"><code>Domain/Entities/TodoItem.cs</code></a> — Add property to entity</li>
<li><a target="_blank" href="https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Application/TodoItems/Commands/CreateTodoItem/CreateTodoItem.cs"><code>Application/TodoItems/Commands/CreateTodoItem/CreateTodoItem.cs</code></a> — Add to command</li>
<li><a target="_blank" href="https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Application/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommandValidator.cs"><code>Application/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommandValidator.cs</code></a> — Add validation rule</li>
<li><a target="_blank" href="https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Application/TodoItems/Commands/UpdateTodoItemDetail/UpdateTodoItemDetail.cs"><code>Application/TodoItems/Commands/UpdateTodoItemDetail/UpdateTodoItemDetail.cs</code></a> — Add to update command</li>
<li><a target="_blank" href="https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Application/TodoItems/Queries/GetTodoItemsWithPagination/TodoItemBriefDto.cs"><code>Application/TodoItems/Queries/GetTodoItemsWithPagination/TodoItemBriefDto.cs</code></a> — Add to list DTO</li>
<li><a target="_blank" href="https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Application/TodoLists/Queries/GetTodos/TodoItemDto.cs"><code>Application/TodoLists/Queries/GetTodos/TodoItemDto.cs</code></a> — Add to detail DTO</li>
<li><a target="_blank" href="https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Infrastructure/Data/Configurations/TodoItemConfiguration.cs"><code>Infrastructure/Data/Configurations/TodoItemConfiguration.cs</code></a> — Add EF Core mapping</li>
<li><a target="_blank" href="https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Web/Endpoints/TodoItems.cs"><code>Web/Endpoints/TodoItems.cs</code></a> — Update endpoint mappings (if needed)</li>
<li>Plus corresponding test files in <code>tests/Application.FunctionalTests/TodoItems/</code></li>
</ol>
<p>You also need to rebuild 4 separate projects (Domain, Application, Infrastructure, Web) for the change to compile.</p>
<p><strong>In VSA</strong> (<a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">my template</a>):</p>
<p>To add a "CancellationCategory" enum field to the CancelAppointment feature, you touch <strong>2-3 files</strong>:</p>
<ol>
<li><a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture/blob/main/src/Application/Domain/Appointment.cs"><code>Application/Domain/Appointment.cs</code></a> — Add property and update domain method</li>
<li><a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture/blob/main/src/Application/Scheduling/CancelAppointment.cs"><code>Application/Scheduling/CancelAppointment.cs</code></a> — Update command, validator, handler</li>
<li><a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture/blob/main/src/Application/Infrastructure/Persistence/Configurations/AppointmentConfiguration.cs"><code>Infrastructure/Persistence/Configurations/AppointmentConfiguration.cs</code></a> — Add EF mapping (if needed)</li>
</ol>
<pre><code class="lang-csharp"><span class="hljs-comment">// In Appointment.cs - Add property</span>
<span class="hljs-keyword">public</span> CancellationCategory? Category { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }

<span class="hljs-comment">// Update Cancel method signature</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Cancel</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> reason, CancellationCategory category, DateTime? cancelledAtUtc = <span class="hljs-literal">null</span></span>)</span>
{
    <span class="hljs-comment">// ... existing validation ...</span>
    Category = category;
}

<span class="hljs-comment">// In CancelAppointment.cs - Update command</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">Command</span>(<span class="hljs-params">Guid AppointmentId, <span class="hljs-keyword">string</span> Reason, CancellationCategory Category</span>)</span>;

<span class="hljs-comment">// Add validation</span>
RuleFor(v =&gt; v.Category).IsInEnum();

<span class="hljs-comment">// Use in handler</span>
appointment.Cancel(request.Reason, request.Category);

<span class="hljs-comment">// In AppointmentConfiguration.cs (optional - only if custom mapping needed)</span>
builder.Property(a =&gt; a.Category).HasConversion&lt;<span class="hljs-keyword">int</span>&gt;();
</code></pre>
<p>The entire request/response flow lives in one cohesive area. Domain model, slice logic, and persistence config all sit in the Application project.</p>
<p><em>Change impact: CA scatters changes across layers; VSA contains them in one folder.</em></p>
<h3 id="heading-testing">Testing</h3>
<p>Clean Architecture is <strong>mock-heavy</strong>. Every layer hides behind interfaces, so your unit tests often end up testing mocks of the layer below. You're testing the wiring, not the behavior.</p>
<p>VSA leans toward <strong>integration testing</strong>. A slice is a self-contained request/response, so the most useful test exercises the full slice from API to database. Save unit tests for the shared domain model, not the handlers.</p>
<p>Why the difference? In Clean Architecture, the layering makes it natural to test each layer in isolation. In VSA, the handler is so thin (mostly orchestration) that integration tests give you more bang for your buck. You save unit tests for the parts with complex logic domain and validators.</p>
<h2 id="heading-when-to-pick-each">When to Pick Each</h2>
<h3 id="heading-vsa-works-well-when">VSA Works Well When</h3>
<ul>
<li><p><strong>You need to ship fast.</strong> Features live in isolation, so you can move quickly without stepping on other code.</p>
</li>
<li><p><strong>Requirements keep changing.</strong> Throwing away a self-contained slice is easier than untangling logic spread across layers.</p>
</li>
<li><p><strong>Your team is experienced.</strong> VSA has fewer guardrails. You need developers who'll recognize when to extract shared domain logic instead of copy-pasting.</p>
</li>
<li><p><strong>You're building APIs.</strong> Request-response systems map naturally to slices.</p>
</li>
</ul>
<h3 id="heading-clean-architecture-works-well-when">Clean Architecture Works Well When</h3>
<ul>
<li><p><strong>You have complex business rules.</strong> The protected domain core gives you a place for rich logic that won't get polluted by infrastructure concerns.</p>
</li>
<li><p><strong>You actually might swap infrastructure.</strong> If there's a real chance you'll change databases or frameworks, CA's abstraction boundaries pay off.</p>
</li>
<li><p><strong>Your team is newer.</strong> The rigid "Controller calls Service calls Repository" structure acts as training wheels. It's harder to make a mess.</p>
</li>
</ul>
<h3 id="heading-the-hybrid-best-of-both">The Hybrid: Best of Both?</h3>
<p>Here's what I've seen work in practice: organize your top-level structure by features (VSA), but inside complex slices, apply CA principles.</p>
<ol>
<li><p><strong>Feature folders at the macro level.</strong> Each slice owns its endpoint, handler, and simple data access.</p>
</li>
<li><p><strong>Domain separation at the micro level.</strong> When a slice gets complex, extract domain logic into a separate class.</p>
</li>
<li><p><strong>Shared domain model.</strong> Multiple slices can reference common entities and value objects that stay persistence-ignorant.</p>
</li>
</ol>
<p>You don't have to pick one religion. Use the structure that fits the complexity at hand.</p>
<p><em>For detailed folder organization patterns, see</em> <a target="_blank" href="/vertical-slice-architecture-folder-structure"><em>VSA Folder Structure: 4 Approaches Compared</em></a>.</p>
<h2 id="heading-migrating-from-clean-architecture-to-vsa">Migrating From Clean Architecture to VSA</h2>
<p>If your CA project has turned into "lasagna architecture" with too many layers, you can simplify through <strong>defactoring</strong>:</p>
<ol>
<li><p><strong>Inline the abstractions.</strong> Move repository and service logic back into the handler for a single feature.</p>
</li>
<li><p><strong>Group by feature.</strong> Put the endpoint, command, handler, and validator in one folder.</p>
</li>
<li><p><strong>Delete the interfaces.</strong> If an interface only exists to satisfy the layering, remove it.</p>
</li>
</ol>
<p>This feels wrong at first. You've been taught that more abstraction is better. But if an abstraction doesn't have multiple implementations and doesn't enable testing, it's just noise.</p>
<h3 id="heading-cross-cutting-concerns">Cross-Cutting Concerns</h3>
<p>"But what about logging? Validation? Transactions?"</p>
<p>Use a mediator pipeline (MediatR, Wolverine). Each slice stays simple while behaviors handle the cross-cutting stuff in one place. You get consistency without polluting every handler.</p>
<p><em>For implementation details, see</em> <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">Vertical Slice Architecture template for .NET 9</a>.</p>
<h2 id="heading-choosing-based-on-your-team">Choosing Based on Your Team</h2>
<p><strong>Junior-heavy teams:</strong> Lean toward Clean Architecture. The prescriptive rules ("Controller calls Service") keep code organized while people learn. The structure does some of the thinking for them.</p>
<p><strong>Experienced teams:</strong> VSA gives you speed. Senior developers have the judgment to refactor procedural code into a rich domain model and abstractions when it makes sense, and to leave simple CRUD operations simple.</p>
<h2 id="heading-project-complexity-checklist">Project Complexity Checklist</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>If your project is...</td><td>Consider...</td><td>Example Template</td></tr>
</thead>
<tbody>
<tr>
<td>Primarily CRUD / Data-driven</td><td>VSA or simple N-Tier</td><td><a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">VSA template for .NET 9</a></td></tr>
<tr>
<td>Rich, complex business rules</td><td>Clean Architecture</td><td><a target="_blank" href="https://github.com/jasontaylordev/CleanArchitecture">Jason Taylor's Clean Architecture</a></td></tr>
<tr>
<td>Rapidly changing features</td><td>VSA</td><td><a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">VSA template for .NET 9</a></td></tr>
<tr>
<td>Long-term (5+ years) maintanance</td><td>Hybrid approach</td><td>Both patterns can evolve</td></tr>
<tr>
<td>Needs independent scalability, headed toward microservices</td><td>VSA (slices become services)</td><td>Vertical slices map to service boundaries</td></tr>
</tbody>
</table>
</div><h2 id="heading-the-bottom-line">The Bottom Line</h2>
<p>There's no universal winner. Clean Architecture protects complexity but adds ceremony. VSA reduces ceremony but requires discipline.</p>
<p>Start simple. If you're building a straightforward APIs, VSA will get you moving faster. If you're modeling a complex domain with lots of business rules, Clean Architecture's protected core is worth the overhead.</p>
<p>And don't be afraid to mix them. The best architectures I've seen treat this as a spectrum, not a binary choice.</p>
<hr />
<h2 id="heading-next-steps">Next Steps</h2>
<p><strong>New to the template?</strong> Start with the <a target="_blank" href="/vertical-slice-architecture-template-quickstart">Quick Start Guide</a> to get the healthcare API running in minutes.</p>
<p><strong>Deciding on folder organization?</strong> See <a target="_blank" href="/vertical-slice-architecture-folder-structure">VSA Folder Structure: 4 Approaches Compared</a> for practical patterns.</p>
<p><strong>Ready to try VSA?</strong> Clone the <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">Vertical Slice Architecture template</a> (540+ stars) — a production-ready starting point with healthcare domain examples.</p>
<p><strong>Want the full picture?</strong> Read the <a target="_blank" href="/vertical-slice-architecture-dotnet">Complete Guide to Vertical Slice Architecture</a> for implementation details, MediatR patterns, and testing strategies.</p>
]]></content:encoded></item><item><title><![CDATA[How to Setup and Use NDepend on macOS for .NET Development]]></title><description><![CDATA[Are you a .NET developer working on macOS, striving for high-quality code? If so, NDepend is one tool you should not overlook. In this post, we'll cover everything from setting it up to running your first analysis. Let's get started!
My recent explor...]]></description><link>https://nadirbad.dev/how-to-setup-and-use-ndepend-on-macos-for-net-development</link><guid isPermaLink="true">https://nadirbad.dev/how-to-setup-and-use-ndepend-on-macos-for-net-development</guid><category><![CDATA[dotnet]]></category><category><![CDATA[ndepend]]></category><dc:creator><![CDATA[Nadir Badnjevic]]></dc:creator><pubDate>Tue, 10 Dec 2024 21:35:14 GMT</pubDate><content:encoded><![CDATA[<p>Are you a .NET developer working on macOS, striving for high-quality code? If so, NDepend is one tool you should not overlook. In this post, we'll cover everything from setting it up to running your first analysis. Let's get started!</p>
<p>My recent exploration of <a target="_blank" href="https://www.ndepend.com/">NDepend</a>, thanks to an invitation from Patrick at NDepend, sparked my interest in its potential to enhance my codebase. As a Software Architect, I often use static code analysis tools to enhance code quality, and NDepend didn't disappoint.</p>
<h2 id="heading-getting-started-on-mac">Getting Started on Mac</h2>
<h3 id="heading-step-1-download-ndepend">Step 1: Download NDepend</h3>
<p>Visit the official <a target="_blank" href="https://www.ndepend.com/">NDepend</a> website and download the macOS version. Your download will be a ZIP file containing everything you need.</p>
<h3 id="heading-step-2-extract-and-set-up">Step 2: Extract and Set Up</h3>
<p>Unzip the downloaded file to a location of your choice. Let's say we've extracted it to <code>~/NDepend</code> for this guide. All further CLI commands will work as if you extracted NDepend to this path.</p>
<h3 id="heading-step-3-install-net-sdk-if-you-havent-already">Step 3: Install .NET SDK (If You Haven't Already)</h3>
<p>NDepend requires the .NET SDK to run. If you don't have it installed, you can download it from the <a target="_blank" href="https://dotnet.microsoft.com/download">.NET website</a>. Follow the installation instructions specific to macOS.</p>
<h3 id="heading-step-4-register-evaluation-or-a-license">Step 4: Register Evaluation or a license</h3>
<p>This can be done using the <code>dotnet</code> command with the path to your NDepend file.</p>
<pre><code class="lang-bash">dotnet ~/Ndepend/net8.0/NDepend.Console.MultiOS.dll --RegEval
</code></pre>
<p>After you should see a message like this:</p>
<pre><code class="lang-bash">Evaluation registered on this machine

Evaluation 14 days left
</code></pre>
<h2 id="heading-creating-a-ndepend-project">Creating a NDepend Project</h2>
<p>Recently, I decided to try NDepend on my <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">VerticalSliceArchitecture</a> project. I was pleasantly surprised by the detailed HTML report I received within seconds. The steps I followed were pretty straightforward.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXc3QIjq0UPmgdCX_XEzHstgU2iXUvCTpuiyYyksUxSYzp9Z7SJU-bL-C9Mv1Woizn6XrhLZBB9iWNygzLWWzV6q0-nGeDtRo7zwjECmAimtJVng-Lzf8p5ptxDfTgr5eEphZJpFqg?key=AAOimGJbPO2bMmy0WvPuizBc" alt /></p>
<p>With NDepend and the .NET SDK installed, you're ready to create your first NDepend project. Here's how to do it:</p>
<h3 id="heading-step-1-open-terminal">Step 1: Open Terminal</h3>
<p>Fire up your Terminal app. We'll be using the command line to run NDepend.</p>
<h3 id="heading-step-2-create-a-ndepend-project-file">Step 2: Create a NDepend project file</h3>
<p>NDepend uses project files (.ndproj) to know what assemblies to analyze. <code>NDepend.Console.MultiOS.dll</code> can be used to create an NDepend project file (.ndproj extension). I created a project by pointing at my solution file e.g.:</p>
<pre><code class="lang-bash">dotnet ~/NDepend/net8.0/NDepend.Console.MultiOS.dll --CreateProject ./VerticalSliceArchitecture.ndproj ~/code/VerticalSliceArchitecture/VerticalSliceArchitecture.sln
</code></pre>
<p>If successful, you’ll get a confirmation message and the default NDepend project file will be created for analysis of your solution or project.</p>
<h2 id="heading-running-the-analysis">Running the Analysis</h2>
<p>Follow these steps to run your analysis and review the results.</p>
<h3 id="heading-step-1-run-ndepend-analysis">Step 1: Run NDepend Analysis</h3>
<p>From the project directory, I run the following command:</p>
<pre><code class="lang-bash">dotnet ~/NDepend/net8.0/NDepend.Console.MultiOS.dll VerticalSliceArchitecture.ndproj
</code></pre>
<p>Replace <code>VerticalSliceArchitecture.ndproj</code> with the path to your NDepend project file.</p>
<h3 id="heading-step-2-review-the-results">Step 2: Review the Results</h3>
<p>Once the analysis completes, NDepend will generate a nice HTML report in the NDependOut folder. Open the generated <code>/NDependOut/NDependReport.html</code> report in your browser to explore various metrics, code rules, and potential issues found in your codebase. <strong>Since my VerticalSliceArchitecture project code broke some rules, the command line exited non-zero.</strong> This is great for build integration where I want to fail if rules get violated.</p>
<p>Here’s a quick glimpse of the detailed insights and analysis you can expect from NDepend:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733902362225/371893f2-23b3-48d5-a22d-567e6c942c70.gif" alt class="image--center mx-auto" /></p>
<p>For a deeper look, explore <a target="_blank" href="https://www.ndepend.com/sample-reports/">sample reports</a> from other projects to see the breadth of data and actionable recommendations NDepend provides.</p>
<h2 id="heading-potential-enhancement">Potential Enhancement</h2>
<p>I believe there's always room for improvement. Here are a few suggestions that could refine the user experience further.</p>
<h3 id="heading-standardize-installation">Standardize Installation</h3>
<p>Incorporating an installation method that aligns with usage of package managers, like Homebrew, or maybe if it is available as <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools">dotnet tool</a> would be great. For example executing <code>dotnet tool install ndepend-tool -g</code> sounds quite handy.</p>
<h3 id="heading-simplifying-command-line-usage">Simplifying Command Line Usage</h3>
<p>Running the cross-platform executable currently requires a somewhat lengthy command:</p>
<pre><code class="lang-bash">dotnet ~/path/to/net8.0/NDepend.Console.MultiOS.dll
</code></pre>
<p>The command line executable is used for various actions like creating projects, registering licenses, and running analyses. It could be more intuitive if commands followed a hierarchical command structure e.g. <code>&lt;tool&gt; &lt;command&gt; [subcommand] [options]</code>.</p>
<p>These suggestions aim to enhance the efficiency and user experience of NDepend, and that usage is intuitive and self-explanatory.</p>
<h3 id="heading-missing-exploring-code-architecture-diagrams-on-macos">Missing Exploring Code Architecture Diagrams on macOS</h3>
<p>If you are working on Windows and using Visual Studio you will benefit of having graphs and code architecture diagrams like this <a target="_blank" href="https://www.ndepend.com/docs/visual-studio-dependency-graph">dependency graph</a>. These graphs are really helpful, and they helped me to improve code architecture of <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">VerticalSliceArchitecture</a> and resolve coupling issues, as noted here on <a target="_blank" href="https://blog.ndepend.com/vertical-slice-architecture-in-asp-net-core/#Note_on_this_implementation">NDepend blog on comparing Clean Architecture and Vertical Slice approach</a>. Here is the example of dependency graph of VerticalSliceArchitecture project:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733510542365/50fdb9db-0e28-459f-ac0e-078a569897ea.webp" alt class="image--center mx-auto" /></p>
<p>Luckily, NDepend have plans to add SVG graphs to HTML reports like these:</p>
<p><a target="_blank" href="https://www.ndepend.com/Res/v2020.1/AspNetCore3.1.html">https://www.ndepend.com/Res/v2020.1/AspNetCore3.1.html</a></p>
<p><a target="_blank" href="https://www.ndepend.com/Res/v2020.1/AspNetCore3.1.html">https://www.ndepend.com/Res/v2020.1/DependencyGraph.html</a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>And there you have it! You've successfully set up NDepend on your Mac, analyzed your .NET codebase.</p>
<p>NDepend is a powerful tool in maintaining high-quality code. By regularly analyzing your code, you can catch issues early, adhere to best practices. The tool's analysis capabilities and detailed reports ensure your code remains clean, efficient, well-structured.</p>
<p>While there are opportunities for enhancing the tool's user experience, such as streamlining installation and command structures, availability of graphs in HTML reports, NDepend remains a robust tool in the pursuit of code excellence.</p>
<h2 id="heading-bonus-explore-ndepend-in-action-with-verticalslicearchitecture">Bonus: Explore NDepend in Action with VerticalSliceArchitecture</h2>
<p>If you're looking for a practical example of setting up and running NDepend, check out my <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture/blob/ndepend-analysis/ndepend.md">VerticalSliceArchitecture GitHub repository</a>.</p>
<p>I've included:</p>
<ul>
<li><p>A complete step-by-step instruction guide for integrating NDepend.</p>
</li>
<li><p>A helper shell <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture/blob/ndepend-analysis/run-ndepend.sh">run-ndepend.sh</a> script to streamline the process.</p>
</li>
</ul>
<p>This example demonstrates how NDepend can be used effectively to analyze and enhance a real-world project.</p>
]]></content:encoded></item><item><title><![CDATA[Vertical Slice Architecture in .NET 10: The Ultimate Guide (2026)]]></title><description><![CDATA[I shared a GitHub template for Vertical Slice Architecture back in 2022. It reached more then 500 stars and helped developers move past the rigidity of Clean Architecture.
But tools change. .NET 10 and C# 14 make vertical slices cleaner and easier to...]]></description><link>https://nadirbad.dev/vertical-slice-architecture-dotnet</link><guid isPermaLink="true">https://nadirbad.dev/vertical-slice-architecture-dotnet</guid><category><![CDATA[software architecture]]></category><category><![CDATA[clean code]]></category><category><![CDATA[asp.net core]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Clean Architecture]]></category><category><![CDATA[.NET]]></category><category><![CDATA[software design]]></category><category><![CDATA[dotnet9]]></category><category><![CDATA[#dotnet8]]></category><category><![CDATA[dotnetcore]]></category><category><![CDATA[Vertical Slice Architecture]]></category><category><![CDATA[dotnet-10]]></category><dc:creator><![CDATA[Nadir Badnjevic]]></dc:creator><pubDate>Fri, 22 Apr 2022 13:30:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1765114328106/63b0867a-9beb-4be1-ad37-e55c0c67459e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I shared a <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">GitHub template for Vertical Slice Architecture</a> back in 2022. It reached more then 500 stars and helped developers move past the rigidity of Clean Architecture.</p>
<p>But tools change. .NET 10 and C# 14 make vertical slices cleaner and easier to write. I updated my approach for 2026. This guide shows you how to organize a monolith for speed.</p>
<h2 id="heading-traditional-layered-architecture">Traditional Layered Architecture</h2>
<p>The first approach is the traditional layered architecture. This is a very common way to organize code and has been a standard for decades. I’m sure you have seen and used this on many projects before. A traditional layered/onion architecture approach organizes code around technical concerns in layers. In this approach, different layers are defined based on their responsibilities in the system. The layers then depend on each other so that they can collaborate and achieve their responsibilities. The dependency flow is guaranteed by forcing each layer to only depend on the ones below them (e.g., the presentation layer can only call code in the business logic layer).</p>
<p>For example, in a web application, we might define these four layers:</p>
<ul>
<li><p>Presentation Layer (controllers)</p>
</li>
<li><p>Business Logic Layer (services)</p>
</li>
<li><p>Data Access Layer (repositories)</p>
</li>
<li><p>Infrastructure Layer (database)</p>
</li>
</ul>
<h2 id="heading-the-problem-with-a-traditional-layered-architecture">The Problem with a traditional layered architecture</h2>
<p>The most significant drawback of a layered architecture is that each layer is highly coupled to the layers it depends on. This coupling makes a layered architecture rigid and difficult to maintain.</p>
<p>The tight coupling also makes it more difficult for developers working on different parts of the application to make changes in parallel, because one developer's work might cause problems with another developer's work. Having tight coupling between the layers, when changes are made to a feature, all the layers must be changed.</p>
<p>Typically if I need to change a feature in a layered application, I end up touching different layers of the application and navigating through piles of projects, folders and files. For example for a simple change in a given feature, you could be editing more than 5 files in all the layers:</p>
<ul>
<li><p><code>Domain/TodoItem.cs</code></p>
</li>
<li><p><code>Repositories/TodoItemsRepository.cs</code></p>
</li>
<li><p><code>Services/TodoItemsService.cs</code></p>
</li>
<li><p><code>ViewModels/TodoItemsViewModel.cs</code></p>
</li>
<li><p><code>Controllers/TodoItemsController.cs</code></p>
</li>
</ul>
<p>Layered architecture is great for some things, but it does have major drawbacks:</p>
<ul>
<li><p>Tight coupling between layers. You can't easily swap out a layer without rewriting code in other layers. This means that if you want to make a change to one feature, you might have to touch several different layers.</p>
</li>
<li><p>Each layer is aware of the next layer down (and sometimes even a few more). This makes it very difficult to understand the "big picture" at any given time and can lead to unexpected side effects when we make changes in one part of our application.</p>
</li>
<li><p>It's often unclear where some components belong; should they be placed in Business Logic or Data Access? Do they go in both? Or maybe in the Presentation as well? These are questions that we need answers to before writing any code or else we will end up with big messes of tightly coupled spaghetti code.</p>
</li>
</ul>
<p>Instead of separating based on technical concerns, Vertical Slices are about focusing on features.</p>
<h2 id="heading-vertical-slice-architecture">Vertical Slice Architecture</h2>
<p>Implementing a vertical slice architecture is a good step in designing a robust software system. It's important to be familiar with it because it provides the foundation for how to structure your codebase and establish the roles and responsibilities of each part of your application.</p>
<p>First, let's clarify what vertical slice architecture means. A vertical slice is an architectural pattern that organizes your code by feature instead of organizing by technical concerns. For example, you can have different features for creating an admin user account versus a normal user account. Or you could have different features for checking if something has been created versus an existing item. This method helps keep track of exactly what your application does for a particular use case within the application.</p>
<p>It's about the idea of grouping code according to the business functionality and putting all the relevant code close together. It improves maintenance, testability, and clean separation of concerns and is easier to adapt to changes.</p>
<p>The Vertical Slice architecture approach is a good starting point that can be evolved later when an application becomes more sophisticated:</p>
<blockquote>
<p>We can start simple (Transaction Script) and simply refactor to the patterns that emerge from code smells we see in the business logic.</p>
<ul>
<li>Jimmy Bogard.</li>
</ul>
</blockquote>
<h2 id="heading-vertical-slice-vs-clean-architecture-key-differences">Vertical Slice vs. Clean Architecture: Key Differences</h2>
<p>While Clean Architecture emphasizes horizontal layers (UI, Application, Domain, Infrastructure), Vertical Slice Architecture emphasizes features.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td>Clean Architecture (Layers)</td><td>Vertical Slice Architecture</td></tr>
</thead>
<tbody>
<tr>
<td>Primary Unit</td><td>Layers (Projects)</td><td>Features (Slices)</td></tr>
<tr>
<td>Coupling</td><td>High logical coupling within layers</td><td>Low coupling between features</td></tr>
<tr>
<td>Code Sharing</td><td>Encouraged (Shared Services)</td><td>Discouraged (Duplicate &gt; Wrong Abstraction)</td></tr>
<tr>
<td>Change Impact</td><td>Touches multiple projects/files</td><td>Contained within one slice</td></tr>
<tr>
<td>Best For</td><td>Enterprise standardization</td><td>Rapid development &amp; Domain complexity</td></tr>
</tbody>
</table>
</div><p><strong>For a deeper comparison including migration strategies, hybrid approaches, and team-based recommendations, see <a target="_blank" href="/vertical-slice-vs-clean-architecture">Vertical Slice vs. Clean Architecture: Which Should You Choose?</a></strong></p>
<p><strong>Looking for folder organization patterns?</strong> See the <a target="_blank" href="/vertical-slice-architecture-folder-structure">VSA folder structure guide</a> comparing 4 different approaches.</p>
<h2 id="heading-example-vertical-slice-architecture-project-solution-in-c-net-9">Example Vertical Slice Architecture Project solution in C# .NET 9</h2>
<p>Most applications start simple but they tend to change and evolve. Because of this, I wanted to create a simpler example project template that showcases the <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">Vertical Slice Architecture</a> approach.</p>
<p>The goal is to stop thinking about horizontal layers and start thinking about vertical slices and organize code by Features. When the code is organized by feature you get the benefits of not having to jump around projects, folders and files. Things related to given features are placed close together.</p>
<p>When moving toward the vertical slices we stop thinking about layers and abstractions. The reason is the vertical slice doesn't necessarily need shared layer abstractions like repositories, services and controllers. We are more focused on concrete Feature implementation and what is the best solution to implement. With this approach, every Feature (vertical slice) is in most cases self-contained and not coupled with other slices. The features relate and share the same domain model in most cases.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660464625999/uAQq-c-7U.png" alt="vertical-slices.png" /></p>
<h2 id="heading-the-change-from-clean-architecture-to-vertical-slice-architecture">The change from Clean Architecture to Vertical Slice Architecture</h2>
<p>This project repository is created based on the Clean Architecture solution template by Jason Taylor, and it uses technology choices and application business logic from this template, like:</p>
<ul>
<li><p>ASP.NET API with .NET 9</p>
</li>
<li><p>CQRS with MediatR</p>
</li>
<li><p>FluentValidations</p>
</li>
<li><p>EF Core 9</p>
</li>
<li><p>xUnit, FluentAssertions, Moq</p>
</li>
<li><p>Result pattern for handling exceptions and errors using</p>
</li>
</ul>
<p>I used the Clean Architecture template because it uses the CQRS pattern with the MediatR library and vertical slices naturally fit into the commands and queries.</p>
<p>nother approach I took (taken from Derek Comartin) about organizing code, is to put all code related to a given feature in a single file in most cases. With this approach we are having self-explanatory file names <code>BookAppointment.cs</code> and all related code close together: Api controller action methods, MediatR requests, MediatR handlers, validations, and DTOs. This is what it looks like:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BookAppointment</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">Command</span>(<span class="hljs-params">
        Guid PatientId,
        Guid DoctorId,
        DateTimeOffset Start,
        DateTimeOffset End,
        <span class="hljs-keyword">string</span>? Notes</span>) : IRequest&lt;ErrorOr&lt;Result&gt;&gt;</span>;

    <span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">Result</span>(<span class="hljs-params">Guid Id, DateTime StartUtc, DateTime EndUtc</span>)</span>;

    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Validator</span> : <span class="hljs-title">AbstractValidator</span>&lt;<span class="hljs-title">Command</span>&gt;
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Validator</span>(<span class="hljs-params"></span>)</span>
        {
            RuleFor(v =&gt; v.PatientId).NotEmpty();
            RuleFor(v =&gt; v.DoctorId).NotEmpty();
            RuleFor(v =&gt; v.Start).LessThan(v =&gt; v.End)
                .WithMessage(<span class="hljs-string">"Start time must be before end time"</span>);
            RuleFor(v =&gt; v.Start)
                .Must(start =&gt; start &gt; DateTimeOffset.UtcNow.AddMinutes(<span class="hljs-number">30</span>))
                .WithMessage(<span class="hljs-string">"Appointment must be scheduled at least 30 minutes in advance"</span>);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">Handler</span>(<span class="hljs-params">ApplicationDbContext context</span>)
        : IRequestHandler&lt;Command, ErrorOr&lt;Result&gt;&gt;</span>
    {
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;ErrorOr&lt;Result&gt;&gt; Handle(Command request, CancellationToken ct)
        {
            <span class="hljs-keyword">var</span> startUtc = request.Start.UtcDateTime;
            <span class="hljs-keyword">var</span> endUtc = request.End.UtcDateTime;

            <span class="hljs-comment">// Check for overlapping appointments</span>
            <span class="hljs-keyword">var</span> hasConflict = <span class="hljs-keyword">await</span> context.Appointments
                .AnyAsync(a =&gt; a.DoctorId == request.DoctorId
                    &amp;&amp; a.Status == AppointmentStatus.Scheduled
                    &amp;&amp; a.StartUtc &lt; endUtc &amp;&amp; a.EndUtc &gt; startUtc, ct);

            <span class="hljs-keyword">if</span> (hasConflict)
                <span class="hljs-keyword">return</span> Error.Conflict(<span class="hljs-string">"Appointment.Conflict"</span>, 
                    <span class="hljs-string">"Doctor has a conflicting appointment"</span>);

            <span class="hljs-comment">// Domain factory method enforces invariants and raises domain events</span>
            <span class="hljs-keyword">var</span> appointment = Appointment.Schedule(
                request.PatientId, request.DoctorId, startUtc, endUtc, request.Notes);

            context.Appointments.Add(appointment);
            <span class="hljs-keyword">await</span> context.SaveChangesAsync(ct);

            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Result(appointment.Id, appointment.StartUtc, appointment.EndUtc);
        }
    }
}
</code></pre>
<p><strong>Want step-by-step setup instructions?</strong> See the <a target="_blank" href="/vertical-slice-architecture-template-quickstart">Quick Start Guide</a> to clone, configure, and run the template in minutes.</p>
<h2 id="heading-vertical-slice-architecture-and-clean-architecture">Vertical Slice Architecture and Clean Architecture</h2>
<p>For a detailed comparison between two prominent approaches of organizing code check this <a target="_blank" href="https://blog.ndepend.com/vertical-slice-architecture-in-asp-net-core/">blog post from NDepend</a> where Clean Architecture is compared to Vertical Slice Architecture and <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">Vertical Slice Architecture template .NET</a>.</p>
<h2 id="heading-why-vertical-slices-work">Why Vertical Slices Work</h2>
<p>Moving to a feature-first mindset fixes specific pain points for growing teams.</p>
<ul>
<li><p><strong>Faster Delivery.</strong> You keep all code for a feature in one place. You build, test, and ship without getting tangled in shared layers. You get business value out the door quicker.</p>
</li>
<li><p><strong>Easier Maintenance.</strong> Co-locating code reduces cognitive load. You don't have to hunt through five different projects to trace how a single button works. It’s all right there.</p>
</li>
<li><p><strong>Safer Changes.</strong> Slices stay isolated. You can change one feature without the risk of breaking an unrelated part of the app. Your team gains the confidence to deploy more often.</p>
</li>
<li><p><strong>Intuitive Codebase.</strong> The folder structure matches the business capabilities. A new developer can look at the project and immediately understand what the application actually does.</p>
</li>
</ul>
<h2 id="heading-the-trade-offs">The Trade-offs</h2>
<p>VSA isn't a magic fix. It relies heavily on discipline.</p>
<p>The flexibility is both the biggest strength and the biggest risk. As Jimmy Bogard notes, VSA "does assume that your team understands code smells and refactoring."</p>
<p>If you ignore this, you invite chaos. The risk of code duplication turns into reality fast. You have to actively manage shared logic. If your team doesn't know how to spot code smells, you will lose the benefits of the architecture entirely.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Benefit</td><td>Corresponding Challenge / Trade-off</td></tr>
</thead>
<tbody>
<tr>
<td>Feature Isolation &amp; Speed</td><td>Potential for Code Duplication: Without careful management, common logic (like validation or mapping) might be repeated across different slices.</td></tr>
<tr>
<td>Implementation Flexibility</td><td>Risk of Inconsistency: Each slice can solve problems differently. Without team discipline and good code reviews, this can lead to inconsistent patterns across the application.</td></tr>
<tr>
<td>Reduced Abstractions</td><td>Requires Stronger Refactoring Skills: Since there are fewer mandatory layers, developers must be skilled at identifying code smells and refactoring complex logic inside a handler to keep it clean and maintainable.</td></tr>
</tbody>
</table>
</div><h2 id="heading-example-vertical-slice-architecture-project-solution-source-code">Example Vertical Slice Architecture project solution source code</h2>
<p>Check out my source code <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">Vertical Slice Architecture Template</a> for more info. If you like this please give a star to the repository :).</p>
<h2 id="heading-related-reading">Related Reading</h2>
<p>This article is part of the <strong>Vertical Slice Architecture Series</strong>:</p>
<ul>
<li><strong><a target="_blank" href="/vertical-slice-vs-clean-architecture">Vertical Slice vs. Clean Architecture: Which Should You Choose?</a></strong> — Detailed comparison with real code examples, migration strategies, and decision framework</li>
<li><strong><a target="_blank" href="/vertical-slice-architecture-folder-structure">VSA Folder Structure: 4 Approaches Compared</a></strong> — How to organize your feature folders for maintainability</li>
<li><strong><a target="_blank" href="/vertical-slice-architecture-template-quickstart">Getting Started Guide</a></strong> — Clone, configure, and run the template in minutes</li>
</ul>
<p><strong>Source Code:</strong> <a target="_blank" href="https://github.com/nadirbad/VerticalSliceArchitecture">Vertical Slice Architecture Template</a> on GitHub (540+ stars)</p>
<h2 id="heading-inspired-by">Inspired by</h2>
<ul>
<li><p><a target="_blank" href="https://github.com/jasontaylordev/CleanArchitecture">Clean Architecture solution template by Jason Taylor</a></p>
</li>
<li><p><a target="_blank" href="https://jimmybogard.com/vertical-slice-architecture/">Vertical slice architecture by Jimmy Bogard</a></p>
</li>
<li><p><a target="_blank" href="https://codeopinion.com/organizing-code-by-feature-using-vertical-slices/">Organize code by Feature using Vertical Slices by Derek Comartin</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Serverless Payments with Braintree and Netlify Functions]]></title><description><![CDATA[Serverless payments are made simply with Braintree and Netlify Lambda Functions. Detailed blog post coming soon. In the meantime check out the working example: https://github.com/nadirbad/netlify-functions-example]]></description><link>https://nadirbad.dev/serverless-payments-with-braintree-and-netlify-functions</link><guid isPermaLink="true">https://nadirbad.dev/serverless-payments-with-braintree-and-netlify-functions</guid><category><![CDATA[serverless]]></category><category><![CDATA[Netlify]]></category><category><![CDATA[payment]]></category><category><![CDATA[braintree]]></category><dc:creator><![CDATA[Nadir Badnjevic]]></dc:creator><pubDate>Sat, 30 Mar 2019 23:46:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/4737e0a9ae5213d710cfdb056e2b344b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Serverless payments are made simply with Braintree and Netlify Lambda Functions. Detailed blog post coming soon. In the meantime check out the working example: <a target="_blank" href="https://github.com/nadirbad/netlify-functions-example">https://github.com/nadirbad/netlify-functions-example</a></p>
]]></content:encoded></item><item><title><![CDATA[First Post]]></title><description><![CDATA[This would be my obligatory first post. I hope to return and add more content soon :)]]></description><link>https://nadirbad.dev/first-post</link><guid isPermaLink="true">https://nadirbad.dev/first-post</guid><dc:creator><![CDATA[Nadir Badnjevic]]></dc:creator><pubDate>Wed, 09 Jan 2019 23:46:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/b9b1da7f01743cee8cbdd14516d98aa5.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This would be my obligatory first post. I hope to return and add more content soon :)</p>
]]></content:encoded></item></channel></rss>