I want to talk to you today about something that changed the way I build software: Clean Architecture. If you’re like me, you’ve probably worked on projects where the code started small and neat, but slowly became a bit tangled mess as new feature got added. In one of my recent projects, a .NET backend app, I faced this exact problem. It was getting harder to test, harder to add features, and harder to sleep at night π
The I discovered Clean Architecture, especially the way Jayson Taylor explains and applies it in .NET projects. It helped me bring back structure and sanity to my codebase. And in this post, I want to share what I learned in a simple and friendly way. No heavy theory. Just things that make sense and you can actually use!
What is Clean Architecture?
Clean Architecture is a software design pattern that helps you keep your code organized, testable, and scalable. It separates the logic of your application into clear layers so that you don’t end up with business logic inside your controllers or views. It was introduced by Robert C. Martin (Uncle Bob) but gained a lot of popularity in the .NET world thanks to Jayson Taylor.
In short, it’s about
- Separating your concerns (UI, logic, data access)
- Keeping your core business rules isolated
- Making dependencies point inward, toward your logic, never the other way around.
This separation helps when your projects get big, or when you want to replace your database, change frameworks, or write proper tests.
The Project Structure
Before diving into the structure, let’s quickly explain what each major layer is and why it’s important.
Domain Layer
This is the heart of your system. It contains the core business logic and rules things that should never depend on external systems like databases or web frameworks.
Examples:
- Entities (User, Invoice)
- Vale Objects (Email, Money)
- Domain Events
This layer should be stable and rarely change. Everything else in your app should depend on this, not the other way around.
Application Layer
This is where your use cases live. It defines what your app does, orchestrating logic from the domain layer.
Examples:
- Commands and Queries (RegisterUserCommand, GetUserListQuery)
- Interfaces for services
- Business workflows
This layer doesn’t care how the data is stored (like SQL Server, PostgreSQL or in-memory) or how it’s requested (like HTTP API, message queue, or console app). It just defines the what, what the system needs to do and delegates the how to other layers. Think of it as the brain giving instructions without worrying about who will carry them out.
Infrastructure Layer
This is where all the technical details go: databases, file systems, external APIs, etc. It contains actual implementations of interfaces defined in the application layer.
Examples:
- Entity Framework Core implementation
- File Storage adapters
- Email Sending Service
This layer can be replaced if needed (for example, switching from SQL Server to PostgreSQL) without touching business logic.
Here’s the typical folder structure based on Jason Taylorβs Clean Architecture approach for .NET projects:
/src
β
βββ Domain
β βββ (Entities, ValueObjects, Interfaces, Domain Events)
β
βββ Application
β βββ (Use Cases, Commands, Queries, DTOs, Interfaces)
β
βββ Infrastructure
β βββ (Persistence, External Services, Configuration)
β
βββ WebUI
β βββ (Controllers, Views, API Config, Middleware)
β
βββ Shared
β βββ (Common logic shared across layers)
β
/tests
β
βββ UnitTests
βββ IntegrationTests
βββ FunctionalTests
Where Do Models Go?
In my current role, one of the common confusions my team and I often run into is about where to place models in a Clean Architecture project. The term “model” can mean different things depending on context, sometimes it’s a domain entity, sometimes it’s a data transfer object (DTO), sometimes it’s a database schema, or even just a request/response object for the API layer.
So I started breaking it down based on intent and usage. Once we agreed on that, the structure became easier to follow and onboard others into. Here’s the breakdown we use now:
The word “model” can mean different things depending on the context. Hereβs a quick guide to help you decide where to place them:
DTOs / View Models / Request Models
Where: Application or WebUI
- Used internally in the app (e.g. in CQRS commands/queries)? Place in:
/Application/Users/Commands/
or/Application/Common/Models/
- Used for API requests/responses? Place in:
/WebUI/Models/
or/WebUI/Contr
acts/
EF Core Models / Persistence Models
Where: Infrastructure
If you use EF Core and your persistence model is slightly different from your domain model, keep it isolated here: /Infrastructure/Persistence/Models
Summary Table:
Type | Folder |
---|---|
EF Core Models | /Domain/Entities |
Value Objects | /Domain/ValueObjects |
Application DTOs | /Application/... or /Application/Models |
API Request/Response Models | /WebUI/Models or /WebUI/Contracts |
Keeping models where they belong will help you keep things clean and avoid spaghetti code later on.
Is it okay to put the Repository in a Shared Library?
YES. if you put only the repository interfaces in the right place.
In Clean Architecture, your domain and application layers should be independent of everything else. That means:
- You define abstractions (interfaces) for things like repositories in the Application Layer (or sometimes in Domain Layer if itβs purely domain-driven).
- The Infrastructure Layer implements those interfaces.
So, if you want other systems to use the same repository interface, and that interface reflects core business behavior, then you can extract it into a Shared Kernel (shared library) but this shared library should still respect Clean Architecture boundaries.
Recommended Structure in Clean Architecture:
/Domain
- Entities
- ValueObjects
- Maybe: IRepository<T> interface
/Application
- Interfaces (e.g., IUserRepository)
- Use Cases
/Infrastructure
- Repositories (EFCore, Dapper, etc.)
/SharedKernel (optional, reusable by other systems)
- Interfaces (e.g., IRepository<T>)
How Clean Architecture Helped Me (Real Talk)
In my previous .NET project, we had a lot of business logic sitting inside controllers and services. Things were working, but testing was difficult. Every time I changed something, I worried something else would break.
Once I started applying Clean Architecture:
- My use cases became easy to test (no more mocking controllers)
- I could change from SQL Server to PostgreSQL with minimal code changes
- New team members understood the structure faster
- I felt more confident to refactor and improve things
Of course, it took time to learn. But once I got used to it, it felt really natural.
Mistakes I Made (And You Might Too)
- Skipping Domain Layer: I thought I don’t need it. Turns out I was wrong.
- Tightly coupling EF Core in Application better to use interfaces!
- Trying to abstract too early don’t add extra layers until they are needed
- Putting all models in the Application or Domain Layer β I used to just drop every class labeled as “Model” into either Application or Domain, but that led to confusion. It should really depend on the purpose of the model:
- If it’s a core business object, it belongs in Domain.
- If it’s input/output for use cases, put it in Application.
- If it’s tied to API requests/responses, place it in WebUI.
- If it’s related to persistence (like EF models), it goes in Infrastructure.
Clarifying this helped reduce coupling and made each layer cleaner.
Is It Always the Right Choice?
NO. If youβre building a small script or prototype, Clean Architecture might feel like too much. But for:
- Mid-size to large projects
- Teams
- Long-term apps
- Projects that need unit/integration testing
Even in my personal projects, I start with some of these principles like separating logic from infrastructure. It keeps me sane even in small codebases.
Further Reading & Resources
- Jason Taylor – Clean Architecture Template
- Jason Taylorβs Blog
- Clean Architecture by Robert C. Martin
- Microsoft Docs: Clean Architecture
If you’re still reading, thank you. I hope this post helped you understand Clean Architecture a bit better. It changed how I build and think about software.