DEV Community

Cover image for From Messy Code to Clean Architecture: How I Finally Organized My Backend Projects
Farzad Mohebi
Farzad Mohebi

Posted on

From Messy Code to Clean Architecture: How I Finally Organized My Backend Projects

The story of how I went from spaghetti code to a maintainable, scalable backend architecture


The Problem That Kept Me Up at Night

I used to be that developer. You know the one – writing everything in the controller, mixing business logic with data access, and creating dependencies so tangled that changing one line of code felt like defusing a bomb.

My projects worked, but they were nightmares to maintain. Adding new features was painful, testing was nearly impossible, and don't even get me started on trying to explain my code to teammates.

Then I discovered Clean Architecture, and everything changed.

The Game-Changing Patterns I Learned

After rebuilding my latest school management system using proper architectural patterns, I want to share the exact approach that transformed my development process. Think of it like renovating a house – instead of having everything crammed into one room, I learned to create proper rooms for different purposes.

Here are the key patterns that made all the difference:

🏛️ Clean Architecture (The Foundation)

This became my north star. The golden rule is simple but powerful: all dependencies point inward. The core business logic never knows about databases, APIs, or external services.

Think of it like this:

  • Domain Layer: The brain (pure business logic)
  • Application Layer: The nervous system (orchestrates use cases)
  • Infrastructure Layer: The hands and feet (handles external concerns)
  • API Layer: The face (communicates with the outside world)

🤝 CQRS + Mediator Pattern

Imagine your application as a busy restaurant. Before CQRS, I had one overworked waiter (my controllers) trying to take orders, cook food, serve dishes, and handle payments. With CQRS, I separated the kitchen staff – some specialize in reading the menu to customers (Queries), others focus on preparing orders (Commands).

The Mediator pattern is like having a head waiter who delegates tasks instead of letting customers shout directly at the kitchen staff:

// Clean, focused handlers - like specialized kitchen staff
public class GetStudentListQuery : IRequest<List<StudentDto>> { }
public class CreateStudentCommand : IRequest<StudentDto> { }
Enter fullscreen mode Exit fullscreen mode

Using MediatR meant my controllers became like a polite receptionist – they just take the request and pass it to the right department!

📦 Repository + Unit of Work

Think of the Repository pattern like a librarian. Instead of letting everyone rummage through the book stacks (database) directly, the librarian provides a clean interface: "I need information about students" and they know exactly where to find it.

The Unit of Work is like a shopping cart – you collect all your changes, and then either checkout everything successfully or abandon the cart entirely. No more half-completed transactions corrupting your data!

public interface IRepository<T>
{
    Task<T> GetByIdAsync(int id);
    Task<List<T>> GetAllAsync();
    // ... other operations
}
Enter fullscreen mode Exit fullscreen mode

✉️ DTOs (Data Transfer Objects)

DTOs are like having a translator at the border between countries. Your internal entities speak "Database Language," but your API needs to speak "Frontend Language." The DTO translator ensures everyone understands each other without forcing anyone to change their native tongue.

My Clean Architecture Structure Tree

Here's the actual project structure I implemented – it's like having a well-organized office building where each floor has a specific purpose:

📁 CTN_School (The Building)
├── 📁 src (Main Floors)
│   ├── 🌐 Api (Reception Floor - 1st Contact)
│   │   ├── 📁 Controllers (Reception Desks)
│   │   ├── 📁 Extensions (Office Utilities)  
│   │   ├── 📁 Hubs (Communication Center)
│   │   ├── 📁 Services (Front Desk Services)
│   │   └── 📄 Program.cs (Building Manager)
│   │
│   ├── 🧠 Core (Executive Floors - The Brains)
│   │   ├── 📁 CTN_School.Application (Management Floor)
│   │   │   ├── 📁 Features (Department Offices)
│   │   │   │   ├── 📁 Auth (Security Department)
│   │   │   │   ├── 📁 Classes (Academic Department)  
│   │   │   │   ├── 📁 Grades (Assessment Department)
│   │   │   │   ├── 📁 Students (Student Affairs)
│   │   │   │   │   ├── 📁 Commands (Action Items)
│   │   │   │   │   ├── 📁 Notifications (Communication)
│   │   │   │   │   └── 📁 Queries (Information Requests)
│   │   │   │   └── 📁 Teachers (Faculty Department)
│   │   │   └── 📁 Common (Shared Resources)
│   │   │
│   │   └── 📁 CTN_School.Domain (CEO Floor - Core Rules)
│   │       ├── 📁 Entities (The Company Constitution)
│   │       └── 📁 Interfaces (Department Contracts)
│   │
│   ├── 🔧 Infrastructure (Basement - Technical Services)
│   │   ├── 📁 Migrations (Building Renovations)
│   │   ├── 📁 Persistence (Document Storage)
│   │   └── 📁 Search (Information Retrieval)
│   │
│   └── 🧪 tests (Quality Assurance Floor)
       └── 📁 CTN_School.Application.UnitTests
           └── 📁 Features (Department Testing)
Enter fullscreen mode Exit fullscreen mode

The Layer-by-Layer Breakdown

Let me walk you through how I organized everything:

Domain Layer (The Core) - The CEO's Office

This is like the CEO's corner office where all the important business decisions are made. Everyone respects these rules, but the CEO doesn't worry about implementation details:

  • Entities: The company's core assets - Student.cs, Teacher.cs, Grade.cs
  • Interfaces: The contracts that everyone must follow - IRepository<T>, IUnitOfWork
  • Business Rules: The fundamental "what" that never changes, regardless of technology

Zero dependencies - The CEO doesn't need to know about databases or websites to make business decisions.

Application Layer (The Orchestrator) - The Management Team

Like department managers who coordinate between the CEO's vision and the workers on the ground:

  • CQRS Handlers: Each manager specializes in specific tasks (GetStudentListQuery, CreateStudentCommand)
  • DTOs: The formatted reports managers create for different audiences
  • Validation: The quality control checklists (FluentValidation rules)
  • Service Interfaces: The contracts with external vendors

Depends only on Domain - Managers take orders from the CEO but don't get their hands dirty with implementation.

Infrastructure Layer (The Implementation) - The Workers & Tools

Like the maintenance crew, IT department, and administrative staff who make everything work:

  • Data Access: The filing system (EF Core context, repositories, migrations)
  • External Services: Connections to outside vendors (Redis, email services)
  • Concrete Implementations: The actual tools and processes that get work done

Depends on Application - Workers follow the managers' specifications.

API Layer (The Gateway) - The Reception Desk

Like the friendly receptionist who greets visitors and directs them to the right department:

  • Controllers: The reception desks that handle specific types of visitors
  • Configuration: The office policies and security systems (JWT, CORS, SignalR)
  • Web-specific Services: The tools receptionists use (JWT tokens, real-time updates)

Depends on both Application and Infrastructure - Receptionists need to know company policies and have access to all departments.

The Dependency Injection Magic ✨

Instead of a messy Program.cs that looked like a junk drawer, I created extension methods. Think of it like having specialized teams handle different aspects of setting up the office:

// Clean and organized - like having department managers handle their own setup
builder.Services.AddApplicationServices();    // Management team setup
builder.Services.AddInfrastructureServices(); // IT and maintenance setup
builder.Services.AddApiServices();            // Reception and security setup
Enter fullscreen mode Exit fullscreen mode

Each layer organizes its own services, like departments managing their own budgets and resources.

What Changed for Me

Before Clean Architecture:

  • 😰 Scared to make changes
  • 🐛 Bug fixes breaking other features
  • 🧪 Testing was a nightmare
  • 📈 Technical debt growing daily

After Clean Architecture:

  • ✅ Confident refactoring
  • 🎯 Features are isolated and focused
  • 🚀 Easy to test individual components
  • 📚 New team members understand the codebase quickly

The Bottom Line

Clean Architecture isn't just about following patterns – it's about creating code that you'll thank yourself for writing six months later. Yes, it requires more initial setup, but the payoff in maintainability and scalability is enormous.

Your future self (and your teammates) will thank you for taking the time to structure your projects properly from the start.


What architectural patterns have saved your sanity? Drop a comment below – I'd love to hear about your experiences with Clean Architecture or other patterns that have transformed your development workflow!

Top comments (0)