1. What is Architecture?
- Layers Access: In software development, architecture refers to more than just how code is structured or organised. It describes the choreography of how different layers within a system interact with each other and how information flows through these layers. When you delineate access between these layers, an architecture is created.
- Principles Architecture: Although architecture often incorporates design principles like SOLID and embraces different design patterns, these elements by themselves do not constitute architecture.
However: guiding principles and patterns are inevitable for a robust architecture. These patterns are just a tool to help realise a broader structural vision. They do not in themselves constitute the architecture.
Purpose of architecture? = Separation of concerns
Why? It makes the system more extensible, maintainable and easier for new contributors to get into. Moreover, if an application follows the separation of concerns principle, the cost of iterations (the time it takes to add a new feature) is lower because the code base is structured and its components decoupled.
2. Quest for an architecture
2.1 Basic Architecture
You create an empty project, create some directories and thus structure your files. No libraries, layers or well-defined rules for splitting responsibilities. Just make sure it works. Perfect for Proof of Concepts (POC) or demo applications.
You can choose a basic architecture if you want to launch an MVP (Minimum Viable Product) as soon as possible and don’t want to spend extra time and resources learning principles from other architecture models. Always opt for separation of concerns. Don’t develop bad habits as a programmer! Your code is art, make it a work of art.
2.2 N-Tier Architecture

= dividing your code into layers (tiers), with each layer managing a separate area of interest. The ‘N’ of n-tier indicates that you can have as many layers as you want, e.g. 3-tier, 4-tier (Sidenote: some say it stands for Neuropsychopharmacology).
Presentation layer: this is the interface used by the client to interact with your application (client-facing-layer). It can take different forms, from mobile or desktop applications to an API or a command line.
For example: if you have a separate UI application, built with Angular or React for example, that communicates with a backend API, then the API itself will be your presentation layer (You treat the Angular app as a client, since it is a completely different application).
- Presentation
- It calls a business logic layer, which takes care of processing requests;
- Only things directly related to the presentation (API configurations, routes, …) are located here;
- Don’t let business logic penetrate to this layer, keep it clean of business rules !
- Business Logic Layer (BLL)
- Your application’s Business Logic resides here, along with everything but the ORM and other things related to getting data, because they are part of your next layer => DAL;
- Includes: services used for communication with external systems, such as sending an e-mail, pushing a message to a topic and the like;
- If the BLL needs to store, retrieve, update or delete data, it will address the DAL.
- Data Access Layer (DAL)
- Theoretically: it knows nothing about other layers and can be changed at any time with a different implementation;
- Because of the dependence of the BLL on DAL, the Business Logic will be affected and changes may propagate upwards, and that is a bit strange because only the business should be able to, change the BLL;
- Changing the database provider is not determined by the business, it is an infrastructure decision.
Note the dependencies: presentation => BLL => DAL.
Our entire application depends on DAL as the core of the system (= Database Driven Design). It doesn’t really seem like a good idea to make this application dependent on DAL.
Starting from the idea that our application solves a business problem, wouldn’t it be better to elevate the BLL and make it the core of our system and make it independent of other layers?
2.3 Hexagonal Architecture

= BLL is the core. Within the BLL, we define interfaces implemented by other external components.
Example: Abstractions are captured in the BLL (Irepository). Implementations are provided by the components (in this case, Data Access).
Dependency Inversion Dependency Injection help us to provide the implementation of Irepository at runtime, preventing DA from being called during compilation. Zero references to other components of our application. It depends on nothing except the BL.
Take out all other dependencies on external systems (e.g.: IEmailService, IFileManager, …). Abstract any dependency on another system by having an interface within the BL, and its implementation as part of another component.
In this context, the interface defined by BL, is called a Port and the implementation provided by the other components, Adapter (which is why in some documentation you find the Hexagonal architecture referred to as Ports and Adapters). Primary adapters (called in the BL) and secondary adapters (called at runtime by the BL, such as DA, E-mail). The presentation layer from the N-tier model is represented by primary adapters in the context of the Hexagonal model. They act as an ingress controller and are client-facing adapters (API, UI, Command Line). Secondary adapters are the ones called by the BL and handle communication with external systems.
- Primary adapters float – they make calls to the BL
- Secondary adapters are driven – BL calls them because of reverse control
We call this a Domain Driven Design (DDD)
Problem: That’s cool, but having dozens of components is actually not the best idea. Let’s find a way to group them together!
2.4 Onion Architecture

= because it mimics the onion structure. The layers go around the core. Dependencies go inwards – and from the outer layers to the inner ones. The whole idea of the Onion structure is to put the Business Logic in the core, and make other layers dependent on it by using Dependency Inversion Dependency Injection.
Two particulars:
- Primary adapters (client-facing-ones) are now part of the presentation layer, while secondary adapters (communicating with external systems) are part of the infrastructure layer.
- De Business Logic (BL) is now divided into N layers. N because the number of layers may vary from case to case. The core is represented by a domain layer. This layer has no dependencies and contains business models, entities, value objects, enumerations and other business-related objects (following DDD principles is good practice for good domain layer design). The layers in between can vary. Usually, you would put Domain Services (another concept of DDD) in a separate layer around the domain, with some ports (interfaces) whose implementation is in the outer infrastructure layer – (If you were to follow CQRS, command query might have their own layer around Domain Services and if you use the Use-Case-Pattern, you would again put these in a separate layer etc). There are no strict rules about how many layers you can have.
2.5 Clean Architecture

Adds a bit of regulation to the onion structure. It states that the domain layer is for enterprise-wide business rules that are least likely to change (entities, enumerations, value objects, core domain objects). Around this is the application layer, which covers everything to do with how the business operates. This is also where the ports (interfaces) are located. The infrastructure layer contains adapters (implementations) that act as gateways to external systems.
KEY Principles:
- Dependency Rule: dependencies should always point inwards, towards the core of the business logic. Inner layers should not depend on outer layers.
- Separation of Concerns: each layer has a specific responsibility and the concerns of one layer should not trickle down to another.This separation helps maintain modularity and testability..
- Independence of Frameworks: the core business logic should not depend on the specific frameworks or tools. Frameworks and external tools should adapt to the application and not the other way around.
- Can lead to better maintainable, scalable and testable code.
PROS:
- Modularity and Maintainability
Benefit: Clean Architecture promotes modularity by separating responsibilities into separate layers. This makes the code base more maintainable and makes it easier to make updates or changes to specific components without affecting the entire application. - Testablity
Benefit: separation of Concerns facilitates unit testing. The Business Logic in the core layer can be tested independently of external dependencies, leading to more robust and reliable tests. - Independence of Frameworks
Benefit: the core business logic is not tightly coupled to specific frameworks or libraries. This independence makes it easier to change or upgrade frameworks without affecting core functionalities. - Flexibility
Benefit: Clean Architecture offers flexibility in choosing technologies for different layers.For example: you can switch from different web frameworks, databases or UI frameworks without major changes to the core business logic.. - Scalability
Benefit: Clean Architecture’s modular structure allows for better scalability. It is easier to scale different parts of the application independently, and teams can work on specific layers without hindering others.
CONS:
- Complexity
Disadvantage: implementing Clean Architecture can introduce additional complexity, especially in the early stages of development. The separation of responsibilities can lead to more files and folders, which can be overwhelming for smaller projects. - Learning Curve
Disadvantage: Developers new to Clean Architecture may face a learning curve. Understanding the principles and implementing the architecture correctly can take time and effort. - Boilerplate code
Disadvantage: Clean Architecture may involve writing more boilerplate code, especially when mapping data between layers. This can lead to longer development time, although it is claimed that the maintainability benefits outweigh this drawback. - Over-Engineering for simple projects
Disadvantage: Clean Architecture can be excessive for simple or small projects where the benefits of separation of concerns and modularity are not so obvious. In such cases, a simpler architecture is more appropriate. - Performance overhead
Disadvantage: the extra layers and abstractions in Clean Architecture can introduce some performance overhead. In applications where performance is critical, careful consideration and optimisation may be necessary.
Conclusion:
Advantages in terms of maintainability, testability and flexibility. But it is essential to weigh these advantages against possible disadvantages, especially in the context of the specific size and complexity of the project. It is not a one-size-fits-all solution, and Developers need to consider trade-offs based on the requirements and constraints of the project.
The Essence:
Clean Architecture is not just another architectural style; it is a philosophy, an in-depth approach to designing systems with priority on maintainability and scalability. At the heart of Clean Architecture are the business rules – immaculate and pure – with external functionalities, such as databases and UI at the edge, ensuring that changes to one component do not negatively affect the others.
Clean Architecture advocates a principle-driven approach over an authoritarian one, encouraging software developers to internalise its essence to create resilient systems from it.
3. Common mistakes and pitfalls
3.1 Entity: Misunderstanding clarifications
Both Clean Architectrure and DDD use the term ‘Enity’ in software design. Yet they differ from each other. In Clean Architecture, entities encapsulate crucial business logic and are kept pure, i.e. free of external influences such as database. In DDD, entities emphasise the importance of a unique identity within the bounded context and are able to encapsulate richer behaviour and even complex relationships with other entities.
Common mistakes their consequences
Many developers, for those familiar with Object-Relation Mapping (ORM) frameworks, see entities as simple data structures that reflect database records. This perception leads them to create Anemic Entities. Anemic Entity: objects that contain no behaviour and merely serve as a collection of getters and setters.
Why is this wrong?
Reduces cohesion: Anemic Entities are decoupled from the operations performed on them. In contrast, rich entities in Clean Architecture strive to group data and relevant operations together, providing higher cohesion.
Here, the operation applyDiscount is decoupled from the OrderAnemic class and placed in a service. This reduces cohesion because the data (attributes of orderAnemic) and the operations on the data (applyDiscount) are separated.
Violates Persistense Agnosticism:
The core business logic is implicitly linked to a specific persistence mechanism by creating entities that directly reflect database structures. This contrasts with the Clean Architecture principle of separating core logic from external details.

The databaseRowVersion attribute is directly linked to a common database structure used for optimistic concurrency. By including it in our entity, we link the entity to a specific persistence mechanism.
Dilutes Business Logic: instead of centralising the business logic within the entities themselves, anemic entities often spread the logic across services or controllers, making the system more difficult to understand and maintain.
Entity’s Value Judgement:It is crucial to clarify that I am not condemning anemic entities. They find their place in specific scenarios, especially in streamlined applications with less complicated domain logic or when combined with certain frameworks. The important thing is to recognise the difference and make an informed choice. If you choose an anemic entity, make sure it is because it adds tangible value to your scenario, and not just as a default choice or an adopted habit. In software architecture, as in so many domains, context is paramount.
3.2 Application Business Rules vs Enterprise Business Rules

The fundamental strength of Clean Architecture lies in the clear delineation of responsibilities, especially when talking about application Business Rules and Enterprise Business Rules. Yet common misconceptions often blur their boundaries, leading to design errors. Clean Architecture makes the distinction between application and business logic. The former focuses on specific application conditions, often involving multiple entities or external factors. The latter revolves around universal truths about entities, unaffected by external conditions. Recognising these differences is essential for a resilient and adaptable system.
3.2.1 Enterprise Business Rules: Heart of the Entity

Consider the entity ‘Credit Card’. At this level, we are interested in the inherent properties and behaviours of the credit card itself, regardless of external factors or specific application conditions. Here we would:
- Validating the card number format;
- Checking the expiry date;
- Verifying the format of the security code.
All operations revolve exclusively around the credit card entity, focusing on its intrinsic properties or behaviours. We are dealing with universal truths about what constitutes a valid credit card, uninfluenced by external circumstances or specific application scenarios.
3.2.2 Application Business Rules (Use Cases)
One layer higher we find the Use Cases that orchestrates our Application Business rules. Here, the logic is driven by specific application conditions and scenarios, often involving multiple entities or external factors.
Using the same credit card example, we would here:
- Check that the user applying for the card is at least 18 years old;
- Determine the applicant’s country and apply region-specific rules or offers;
- Consider integrating with external services to perform e.g. credit checks.Here it is called service but in Clean Architecture they usually speak (and name) Use Cases.

This layer goes beyond the boundaries of a single entity. It orchestrates interactions, tracks processes and applies contextual business logic that may be unique to a particular application or scenario. It is where the broader business logic relevant to the specific application comes to life.
3.3 Common Mistakes
- Blurring the boundaries: Developers often confuse Application Business Rules with all-encompassing truths, which overshadow Enterprise Business Rules. This misconception is compounded by a misconception about entities, which ideally should be more in line with immutable facts of Enterprise Business Rules.
- Why this is wrong:
Lacks flexibility: Treating Application Business Rules as constant truths hinders system adaptability to future changes.
Integrity risks: Failure to distinguish the difference can lead to unintended violations of core business logic during application changes. - Like our example code above, adding the user’s age verification directly to the credit card class would be a mistake. This would be an inappropriate mixing of Application Business Rules (age verification for applying for a credit card) with Enterprise Rules (what makes a credit card valid). And in a simple context: the credit card model does not care about the user’s age.
- Why this is wrong:
- Entities crossing boundaries: because of the mistaken belief that entities encompass all business rules, Developers can execute them directly from controllers, causing entities to cross boundaries where they should not..
- Why this is wrong:
Breach of Separtion: calling entities directly from controllers undermines the principle of Separation of Concernss.
Dependency Rule Violation: this approach violates the rule that outer layers should depend on the inner ones and not the other way around.
- Why this is wrong:
- Misinterpreting Control Flow: another pitfall is reading arrows in the Clean Architecture diagram as control flow indicators rather than dependencies.
- Why this is wrong:
Control Flow Inversion: although the dependencies point inwards, the actual flow of control is from outer layers (such as controllers) inwards and outwards again.
Design Flaws: Misinterpreting the control flow can lead to design choices that compromise adaptability and scalability. - In the incorrect approach, the entity is intertwined with external services, making the system more difficult to test, understand and maintain. It also violates the principle of dependency inversion because the core entity is now dependent on external details. This is exactly the kind of design error Clean Architecture wants to avoid.
- Why this is wrong:
Have you ever felt trapped in a web of code, not knowing which thread to follow? Clean Architecture is the compass that guides you out of the labyrinth. It’s not about the platform … it’s about the purity of design. Clean Architecture is not just a method; it is a movement (and it calls out to developers like you who are hungry for clarity and excellence).
Written by: Ides VH.