Core Principles of Clean Architecture: From Entities to Frameworks

Maxim Gorin
7 min readJul 4, 2024

--

Welcome to the 22nd article in our series on Clean Architecture. Last time, in “Screaming Architecture: Making Software Design Clear and Adaptable”, we focused on how to design software that clearly communicates its intent. In this article, we are going to delve into the specifics of the Dependency Rule and the architectural layers that form the foundation of clean architecture.

“Core Principles of Clean Architecture”, generated by OpenAI’s DALL-E

Clean Architecture is a methodology for designing software that is both flexible and maintainable. At its core, it emphasizes the separation of concerns by organizing the system into layers with specific roles and responsibilities. This approach ensures that changes in one part of the system have minimal impact on other parts, making the software easier to understand, modify, and extend.

Let’s dive in and see how these principles can help you build robust, scalable, and maintainable mobile applications.

Dependency Rule

What is a Clean Frontend Architecture?

Concept of Dependency

In clean architecture, dependencies are fundamental to maintaining flexibility and integrity in mobile applications. The Dependency Rule dictates that source code dependencies should always point inward, towards more abstract layers of the system. This approach ensures that the core business logic is insulated from external elements like UI frameworks, databases, or external services, making the system more resilient to changes and easier to maintain.

Concentric Circles Principle

Imagine your architecture as a series of concentric circles. Each circle represents a different layer of the application, with the innermost circles containing the most abstract elements (like business rules) and the outer circles containing more concrete elements (like user interfaces and databases). The further you move outward, the more specific and implementation-related the components become. The key is that inner layers should not depend on outer layers.

Direction of Dependencies

The overriding rule is that dependencies must only point inward. This means that nothing in an inner circle should know about the existence of anything in an outer circle. For instance, your business logic (inner circle) should not be aware of the database or the UI components (outer circles). This separation ensures that changes in outer layers (like switching from one database to another) do not affect the inner layers, preserving the core functionality and making the system easier to adapt and evolve.

Areas and Layers of Architecture

Clean architecture layers — what they are and the benefits

Entities

Entities are the core components that encapsulate the most fundamental business rules and logic of an application. They represent the primary data and behaviors that define the business domain. The key characteristic of entities is their independence from external systems and changes, ensuring that they remain stable regardless of modifications in other parts of the application.

Entities should be designed to hold critical business logic that is unique and central to the application. This includes rules and constraints that define how data can be created, modified, and validated. By encapsulating these rules within entities, you ensure that the core functionality of the application is protected from external influences and remains consistent even as the application evolves.

Entities are the most abstract components in the architecture. They do not depend on any other layers and are unaware of the specifics of the infrastructure, such as databases or user interfaces. This independence allows entities to be reused across different applications and contexts, providing a robust foundation for the business logic.

Use Cases

Use cases define the application-specific business rules and outline how the system behaves in different scenarios. They manage the interactions between entities and other system components, ensuring that the business logic is applied consistently. Use cases act as an intermediary between the user inputs and the core business logic, maintaining the integrity of the system’s operations.

The primary role of use cases is to orchestrate the flow of data to and from the entities, ensuring that the business rules are executed correctly. Use cases encapsulate the application logic that is specific to particular operations, such as processing transactions, managing user interactions, or executing complex workflows.

Use cases are designed to be independent of the user interface and infrastructure layers. This separation ensures that changes in the way data is presented to the user or stored in the database do not affect the core business logic. By isolating the application logic in use cases, you create a flexible and maintainable system that can adapt to changing requirements and technologies.

Interface Adapters

Interface adapters serve as the bridge between the use cases and entities, and the external systems such as databases, web services, and user interfaces. They handle the data translation and communication, ensuring that the internal layers of the application remain unaffected by changes in the external systems. This separation allows for a more maintainable and flexible architecture.

The primary function of interface adapters is to convert data formats and protocols used by external systems into a format that can be understood by the use cases and entities. This includes tasks such as mapping database records to entity objects, transforming user inputs into a standardized format, and converting API responses into a usable format.

Interface adapters ensure that the core business logic is isolated from the specifics of the external systems. By managing the interactions with external systems, interface adapters allow the application to remain flexible and adaptable. Changes in external systems, such as updating a web service or migrating to a new database, can be handled within the interface adapters without affecting the core business logic.

Frameworks and Drivers

Frameworks and drivers form the outermost layer of the architecture. This layer consists of external tools, libraries, and frameworks that support the application but do not influence the core business logic. The primary goal of this layer is to facilitate interactions with external systems and provide necessary infrastructure support without imposing constraints on the internal architecture.

The frameworks and drivers layer includes components such as web frameworks, database management systems, and third-party services. These components provide essential functionality and infrastructure support but should be treated as replaceable and independent from the core business logic.

By isolating the frameworks and drivers in the outermost layer, you ensure that the core business logic remains unaffected by changes in the underlying infrastructure. This allows for greater flexibility in selecting and updating the tools and technologies used to support the application. The core business logic remains stable and consistent, regardless of changes in the external environment.

Crossing Boundaries

‘Smartphone-shaped factory, workers installing pipes’, generated by DALL-E

As we discussed earlier in “Defining and Managing Boundaries: Enhancing Scalability in Mobile Development”, effective boundary management is vital for building scalable and maintainable applications. In this section, we will take a closer look at how the Dependency Inversion Principle and proper data handling contribute to maintaining these boundaries in your architecture.

Dependency Inversion Principle

The Dependency Inversion Principle (DIP) helps keep the architecture flexible and maintainable. According to DIP, high-level modules shouldn’t rely on low-level modules but both should depend on abstractions. This principle ensures that the core business logic remains separate from the details of lower-level implementations.

To implement DIP, use interfaces or abstract classes to define interactions between different layers. These interfaces act as contracts, ensuring that dependencies always point inward towards higher-level abstractions. For example, instead of having a use case directly call a specific database method, define an interface for data access. The use case interacts with this interface, and the actual implementation is provided by an outer layer. This decoupling ensures that the core business logic is not tied to specific details of database implementation.

Data Crossing Boundaries

When data crosses boundaries between different layers of the application, maintaining a clean separation and avoiding tight coupling is essential. The data should be in simple, standardized formats, such as Data Transfer Objects (DTOs) or plain data structures. These formats are straightforward to understand and manipulate, ensuring that the core business logic remains unaffected by changes in the data representation.

Using simple data structures ensures that the core business logic is isolated from external changes. For example, if a database framework returns a complex data object, map it to a simpler DTO before passing it to the inner layers. This practice helps maintain the independence of the core business logic from external changes and simplifies testing and maintenance.

Avoid passing database entities or framework-specific objects directly into the business logic layer, as this creates tight coupling between the business logic and the data source. This tight coupling makes it challenging to change the underlying database or framework without affecting the entire application. Additionally, allowing external data formats, such as JSON from a web service, to propagate through the core layers without transformation can lead to security vulnerabilities and hard-to-maintain code.

Conclusion

In this article, we’ve explored the intricacies of the Dependency Rule and the structural layers of Clean Architecture. By keeping dependencies directed inward, we maintain the integrity of our core business logic, allowing the system to remain adaptable and easy to manage.

Understanding the distinct roles of entities, use cases, interface adapters, and frameworks and drivers is key to building a well-organized system. Entities encapsulate business rules, use cases handle specific operations, interface adapters manage communication between layers, and frameworks and drivers support external interactions.

By clearly defining and maintaining boundaries between these layers, we can update or replace parts of the system with minimal disruption. This approach not only enhances flexibility but also makes it easier to scale and maintain the application over time.

If you found this article helpful, make sure to subscribe for more insights on clean architecture and mobile development. Share your thoughts or questions in the comments below — let’s continue the conversation!

--

--

Maxim Gorin
Maxim Gorin

Written by Maxim Gorin

Team lead in mobile development with a passion for Fintech and Flutter. Sharing insights and stories from the tech and dev world on this blog.

Responses (1)