DEV Community

Cover image for 1 - Clean Architecture: A Simpler Approach to Software Design
Daniel Azevedo
Daniel Azevedo

Posted on

1 - Clean Architecture: A Simpler Approach to Software Design

Hey everyone, welcome to the first post in a series where we’ll dive into Clean Architecture and how it can help us write more maintainable, scalable, and testable code. If you’ve ever struggled with tangled dependencies, rigid code, or difficulty in testing, Clean Architecture could be a game-changer for you. In this series, I’ll break down the concepts step by step, using examples in C#—specifically around a payroll processing system.

Let’s start from the basics: What is Clean Architecture?

What is Clean Architecture?

Clean Architecture is a set of guidelines for designing software that ensures that business logic is independent from things like the user interface, database, or external frameworks. The idea is to build systems that can evolve over time without requiring massive rewrites or introducing bugs into unrelated parts of the system. You can think of it as a way to create clear boundaries between different parts of the system.

The term Clean Architecture was popularized by Uncle Bob (Robert C. Martin), and its core idea is organizing code in layers, with the most important layer (your business logic) in the center, and less critical components (UI, databases) on the outer edges.

The further you move away from the core, the less stable and less important the code should be. The central layers should be the most stable, and things like databases and UI frameworks, which are likely to change, are kept on the outside.

The Layers of Clean Architecture

Clean Architecture is often depicted as a set of concentric circles, with each circle representing a layer. Here’s a quick rundown of these layers:

  1. Entities: These are the core business rules. They are the most important part of the system and should not depend on anything outside of the business logic.

  2. Use Cases: This layer defines the application-specific business rules. It’s all about what the application does. This layer interacts with the entities and defines the flows of the application.

  3. Interface Adapters: This layer contains the code that converts data from the outside world into a format that the use cases and entities can understand, and vice-versa. It can include controllers, presenters, or gateways to communicate with external systems.

  4. Frameworks & Drivers: This is the outermost layer, which contains the web framework, database, and other details. These are tools that are often subject to change, and they should have minimal impact on the inner layers.

Why Use Clean Architecture?

Now, you might be asking yourself, "Why go through all this effort?" Good question! Here are some of the benefits of using Clean Architecture:

  • Testability: Since the core logic is independent of the UI and databases, it’s much easier to write tests for it.
  • Maintainability: Changes to one part of the system, like the UI or the database, don’t cascade into the core business logic. This isolation helps reduce bugs and makes the system easier to understand.
  • Flexibility: Need to switch from a SQL database to NoSQL? Or maybe migrate from an old UI framework to a new one? Clean Architecture makes these changes easier since the core of your system is decoupled from these details.

Where Does the Business Logic Go?

A common misconception is that we should put business logic directly into our entities. However, in Clean Architecture, this is discouraged. The entities should only represent the essential business rules, while the complex application logic—such as executing a workflow like processing payroll—should reside in the use cases.

Entities should remain simple and self-contained, with minimal business logic. The real "meat" of the application logic, like calculating payroll or validating input, belongs in the Use Cases layer. This separation allows us to keep our core business rules clean and reusable, while also allowing flexibility in how the application logic operates.

Starting Small: A Simple Payroll Example

To make this more concrete, let’s start with a simple example. Over the course of this series, we’ll be building a payroll processing system using Clean Architecture principles. In this first post, we’ll create a basic entity: Employee.

Here’s how the first layer, Entities, might look in C#:

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Salary { get; set; }
    public decimal TaxRate { get; set; }

    public Employee(int id, string name, decimal salary, decimal taxRate)
    {
        Id = id;
        Name = name;
        Salary = salary;
        TaxRate = taxRate;
    }

    public void ValidateTaxRate()
    {
        if (TaxRate < 0 || TaxRate > 1)
        {
            throw new ArgumentException("Invalid tax rate");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

What’s happening here?

We’ve created a simple Employee class that represents an entity in our payroll system. The Employee class defines the basic attributes like Id, Name, Salary, and TaxRate. It also includes a method ValidateTaxRate() to ensure the tax rate is within valid bounds.

Notice, though, that we haven’t included any business logic here—like calculating net salary. That’s intentional. The business logic should live in a Use Case.

Handling Business Logic in a Use Case

Now, let’s take the business logic that calculates the net salary and place it in a use case. Here’s what that might look like:

public class ProcessPayrollUseCase
{
    public decimal CalculateNetSalary(Employee employee)
    {
        employee.ValidateTaxRate();
        return employee.Salary - (employee.Salary * employee.TaxRate);
    }
}
Enter fullscreen mode Exit fullscreen mode

In this ProcessPayrollUseCase, we handle the calculation of the employee's net salary after taxes. The use case is responsible for executing the business logic that makes use of our Employee entity, and it validates the tax rate before performing the calculation.

This separation ensures that entities remain simple, and the business logic is organized in use cases where it belongs.

What’s Next?

In this post, we’ve covered the very basics of Clean Architecture and set the stage with our first entity. In the next post, we’ll dive deeper into use cases—the application-specific business rules that control how the system behaves. Specifically, we’ll look at how to process payroll using Clean Architecture principles and build on this Employee class.

Stay tuned!

Top comments (0)