Featured image of post Refactoring for Clean Architecture

Refactoring for Clean Architecture

Disclaimer: This is how I structure my projects. It works for me, it might work for you. Or not.

Let’s be honest. We have all been there. You start a new project, promising yourself that this time, this time it will be different. No spaghetti code, no 2000-line controllers, just pure, unadulterated software engineering bliss.

Fast forward six months, and you’re staring at a UserController that handles validation, database queries, email notifications and… wait, is that PDF generation?

In my recent work on an Enterprise Logistics Platform (fancy name, I know), we faced exactly this challenge. We needed a backend that was robust, testable, and most importantly, didn’t make us want to cry every time we opened Visual Studio.

The Approach

We moved away from grouping by technical concerns (Controllers, Models) and went full Clean Architecture.

Here is the folder structure we landed on. It’s simple, it’s clean, and it saved my sanity:

1
2
3
4
5
6
/src
  /MyProject.Api          <-- Dumb integration layer
  /MyProject.Application  <-- The Brains (Business Logic)
  /MyProject.Contracts    <-- Shared DTOs (NuGet)
  /MyProject.Infrastructure <-- The Plumbing (DB, Email, Azure)
  /IAC                    <-- Bicep (because friends don't let friends click in Portal)

1. The API Layer (.Api)

This layer is dumb. Like, really dumb. Its only job is to accept HTTP requests, maybe validate them a bit, and hand them off to the Application layer. If you find logic here, you’re doing it wrong.

2. The Application Layer (.Application)

This is where the magic (and the pain) happens. It contains our Services and Interfaces for everything. But notice: No implementations. The Application layer defines what needs to be done, not how.

3. The Contracts Layer (.Contracts)

This was a game-changer. We stripped out all the DTOs into their own library. Why? Because now we can package this as a NuGet and share it with other teams. No more copy-pasting C# classes via Teams chat (don’t look at me like that, you’ve done it too).

Why bother?

Refactoring to this structure was a pain in the backend (pun intended), but:

  • Testing is a breeze: I can mock everything in the Application layer because it relies on Interfaces, not concrete classes.
  • Onboarding is faster: New devs don’t have to guess where the shipping calculation logic lives. It’s in Application. It makes sense.

It takes time to set up, but trust me, your future self will thank you.

All rights reserved
Built with Hugo
Theme Stack designed by Jimmy