Software architecture aims to minimize the human resources required to build and maintain the required system. The software was invented to be “soft.” It was intended to be a way to easily change the behaviour of machines. If we’d wanted the behaviour of devices to be hard to change, we would have called it hardware.
Good software systems begin with clean code. On the one hand, if the bricks aren’t well made, the architecture of the building doesn’t matter much. On the other hand, you can make a substantial mess with well-made bricks.
The architecture of a software system is the shape given to that system by those who build it. The form of that shape is in the division of that system into components, the arrangement of those components, and how those components communicate with each other. Good architecture must support: • The use cases and operation of the system. • The maintenance of the system. • The development of the system. • The deployment of the system.
A component is a grouping of related functionality behind a nice clean interface, which resides inside an execution environment like an application. If the SOLID principles tell us how to arrange the bricks into walls and rooms, then the component principles tell us how to arrange the rooms into buildings. Large software systems, like large buildings, are built out of smaller components. • REP: The Reuse/Release Equivalence Principle. The granule of reuse is the granule of release. This means that the classes and modules that are formed into a component must belong to a cohesive group. • CCP: The Common Closure Principle. Gather into components those classes that change for the same reasons and at the same times. Separate into different components those classes that change at different times and for different reasons. • CRP: The Common Reuse Principle. Don’t force users of a component to depend on things they don’t need.
The SOLID principles tell us how to arrange our functions and data structures into classes, and how those classes should be interconnected. SRP: The Single Responsibility Principle. Each software module has one, and only one, reason to change. (What do we mean by the word “module”? The simplest definition is just a source file.)
The architect can employ the Single Responsibility Principle and the Common Closure Principle to separate those things that change for different reasons, and to collect those things that change for the same reasons—given the context of the intent of the system. What changes for different reasons? User interfaces change for reasons that have nothing to do with business rules. Business rules themselves may be closely tied to the application, or they may be more general. The database, the query language, and even the schema are technical details that have nothing to do with the business rules or the UI. Thus we find the system divided into decoupled horizontal layers— the application-independent business rules, Application-specific business rules, UI, and the Database. OCP: The Open-Closed Principle. For software systems to be easy to change, they must be designed to allow the behaviour of those systems to be changed by adding new code, rather than changing existing code. LSP: The Liskov Substitution Principle. To build software systems from interchangeable parts, those parts must adhere to a contract that allows those parts to be substituted one for another. ISP: The Interface Segregation Principle. This principle advises software designers to avoid depending on things that they don’t use. DIP: The Dependency Inversion Principle. The code that implements high-level policy should not depend on the code that implements low-level details. Rather, details should depend on policies.
Application-independent business rules are rules or procedures that make or save the business money. Irrespective of whether they were implemented on a computer, they would make or save money even if they were executed manually. An Entity is an object within our computer system in a single and separate module that embodies a small set of critical business rules operating on Critical Business Data.
A use case is a description of the way that an automated system is used. It specifies the input to be provided by the user, the output to be returned to the user, and the processing steps involved in producing that output. A use case describes application-specific business rules as opposed to the Critical Business Rules within the Entities.
Entities do not know of the use cases that control them. The use cases of the system will be visible within the structure of that system. Those elements will be classes or functions or modules that have prominent positions within the architecture, and they will have names that clearly describe their function. From the use case, it is impossible to tell whether the application is delivered on the web, on a thick client, on a console or is a pure service.
The software in the interface adapters layer is a set of adapters that convert data from the format most convenient for the use cases and entities to the format most convenient for some external agency such as the database or the web. The presenters, views, and controllers all belong in the interface adapters layer. No code inward of this circle should know anything at all about the database.
The frameworks and drivers layer is where all the details go. The web is a detail. The database is a detail. We keep these things on the outside where they can do little harm. Between the use case interactors and the database are the database gateways. These gateways are polymorphic interfaces that contain methods for every create, read, update, or delete operation that can be performed by the application on the database.
The Main component is the ultimate detail—the lowest-level policy. It is the initial entry point of the system. Nothing, other than the operating system, depends on it. It is in this Main component that dependencies should be injected by a Dependency Injection framework. The Main is a dirty low-level module in the outermost circle of the clean architecture. Think of Main as a plugin to the application—a plugin that sets up the initial conditions and configurations, gathers all the outside resources, and then hands control over to the high-level policy of the application. When Main is released, it has utterly no effect on any of the other components in the system. They don’t know about Main, and they don’t care when it changes.
If the application wants to display money on the screen, it might pass a Currency object to the Presenter. The Presenter will format that object with the appropriate decimal places and currency markers, creating a string that it can place in the View Model. If that currency value should be turned red if it is negative, then a simple boolean flag in the View model will be set appropriately. Anything and everything that appears on the screen, and that the application has some kind of control over, is represented in the View Model as a string, a boolean, or an enum. Nothing is left for the View to do other than to load the data from the View Model into the screen. If the application needs to know the last names of all the users who logged in yesterday, then the UserGateway interface will have a method named getLastNamesOfUsersWhoLoggedInAfter that takes a Date as its argument and returns a list of last names.
Typically the data that crosses the boundaries consist of simple data structures. You can use basic structs or simple data transfer objects if you like. Or the data can simply be arguments in function calls. Or you can pack it into a hash-map, or construct it into an object. Warning! Many database frameworks return a convenient data format in response to a query. We might call this a “row structure.” We don’t want to pass that row structure inward across a boundary. Doing so would violate the Dependency Rule because it would force an inner circle to know something about an outer circle.
Architects often fall into a trap—a trap that hinges on their fear of duplication. When code is truly duplicated, we are honour-bound as professionals to reduce and eliminate it. But there are different kinds of duplication. There is true duplication, in which every change to one instance necessitates the same change to every duplicate of that instance. If two duplicated sections of code evolve along different paths—if they change at different rates, and for different reasons—then they are false or accidental duplication.
When you are vertically separating use cases from one another, you will run into this issue too, and your temptation will be to couple the use cases because they have similar screen structures, similar algorithms, or similar database queries and/or schemas. Be careful. Resist the temptation to sin knee-jerk elimination of duplication. Make sure the duplication is real. By the same token, when you are separating layers horizontally, you might notice that the data structure of a particular database record is very similar to the data structure of a particular screen view. You may be tempted to simply pass the database record up to the UI, rather than to create a view model that looks the same and copy the elements across. Be careful: This duplication is almost certainly accidental. Creating the separate view model is not a lot of effort, and it will help you keep the layers properly decoupled.
The software was invented to be “soft.” It was intended to be a way to easily change the behaviour of machines. If we’d wanted the behaviour of devices to be hard to change, we would have called it hardware.
Good software systems begin with clean code. On the one hand, if the bricks aren’t well made, the architecture of the building doesn’t matter much. On the other hand, you can make a substantial mess with well-made bricks.
The architecture of a software system is the shape given to that system by those who build it. The form of that shape is in the division of that system into components, the arrangement of those components, and how those components communicate with each other.
Good architecture must support:
• The use cases and operation of the system.
• The maintenance of the system.
• The development of the system.
• The deployment of the system.
A component is a grouping of related functionality behind a nice clean interface, which resides inside an execution environment like an application.
If the SOLID principles tell us how to arrange the bricks into walls and rooms, then the component principles tell us how to arrange the rooms into buildings. Large software systems, like large buildings, are built out of smaller components.
• REP: The Reuse/Release Equivalence Principle. The granule of reuse is the granule of release. This means that the classes and modules that are formed into a component must belong to a cohesive group.
• CCP: The Common Closure Principle. Gather into components those classes that change for the same reasons and at the same times. Separate into different components those classes that change at different times and for different reasons.
• CRP: The Common Reuse Principle. Don’t force users of a component to depend on things they don’t need.
The SOLID principles tell us how to arrange our functions and data structures into classes, and how those classes should be interconnected.
SRP: The Single Responsibility Principle. Each software module has one, and only one, reason to change. (What do we mean by the word “module”? The simplest definition is just a source file.)
The architect can employ the Single Responsibility Principle and the Common Closure Principle to separate those things that change for different reasons, and to collect those things that change for the same reasons—given the context of the intent of the system.
What changes for different reasons?
User interfaces change for reasons that have nothing to do with business rules. Business rules themselves may be closely tied to the application, or they may be more general. The database, the query language, and even the schema are technical details that have nothing to do with the business rules or the UI.
Thus we find the system divided into decoupled horizontal layers— the application-independent business rules, Application-specific business rules, UI, and the Database.
OCP: The Open-Closed Principle. For software systems to be easy to change, they must be designed to allow the behaviour of those systems to be changed by adding new code, rather than changing existing code.
LSP: The Liskov Substitution Principle. To build software systems from interchangeable parts, those parts must adhere to a contract that allows those parts to be substituted one for another.
ISP: The Interface Segregation Principle. This principle advises software designers to avoid depending on things that they don’t use.
DIP: The Dependency Inversion Principle. The code that implements high-level policy should not depend on the code that implements low-level details. Rather, details should depend on policies.
Application-independent business rules are rules or procedures that make or save the business money. Irrespective of whether they were implemented on a computer, they would make or save money even if they were executed manually.
An Entity is an object within our computer system in a single and separate module that embodies a small set of critical business rules operating on Critical Business Data.
A use case is a description of the way that an automated system is used. It specifies the input to be provided by the user, the output to be returned to the user, and the processing steps involved in producing that output. A use case describes application-specific business rules as opposed to the Critical Business Rules within the Entities.
Entities do not know of the use cases that control them.
The use cases of the system will be visible within the structure of that system. Those elements will be classes or functions or modules that have prominent positions within the architecture, and they will have names that clearly describe their function.
From the use case, it is impossible to tell whether the application is delivered on the web, on a thick client, on a console or is a pure service.
The software in the interface adapters layer is a set of adapters that convert data from the format most convenient for the use cases and entities to the format most convenient for some external agency such as the database or the web.
The presenters, views, and controllers all belong in the interface adapters layer.
No code inward of this circle should know anything at all about the database.
The frameworks and drivers layer is where all the details go. The web is a detail. The database is a detail. We keep these things on the outside where they can do little harm.
Between the use case interactors and the database are the database gateways. These gateways are polymorphic interfaces that contain methods for every create, read, update, or delete operation that can be performed by the application on the database.
The Main component is the ultimate detail—the lowest-level policy. It is the initial entry point of the system. Nothing, other than the operating system, depends on it. It is in this Main component that dependencies should be injected by a Dependency Injection framework. The Main is a dirty low-level module in the outermost circle of the clean architecture. Think of Main as a plugin to the application—a plugin that sets up the initial conditions and configurations, gathers all the outside resources, and then hands control over to the high-level policy of the application.
When Main is released, it has utterly no effect on any of the other components in the system. They don’t know about Main, and they don’t care when it changes.
If the application wants to display money on the screen, it might pass a Currency object to the Presenter. The Presenter will format that object with the appropriate decimal places and currency markers, creating a string that it can place in the View Model. If that currency value should be turned red if it is negative, then a simple boolean flag in the View model will be set appropriately.
Anything and everything that appears on the screen, and that the application has some kind of control over, is represented in the View Model as a string, a boolean, or an enum. Nothing is left for the View to do other than to load the data from the View Model into the screen.
If the application needs to know the last names of all the users who logged in yesterday, then the UserGateway interface will have a method named getLastNamesOfUsersWhoLoggedInAfter that takes a Date as its argument and returns a list of last names.
Typically the data that crosses the boundaries consist of simple data structures. You can use basic structs or simple data transfer objects if you like. Or the data can simply be arguments in function calls. Or you can pack it into a hash-map, or construct it into an object.
Warning! Many database frameworks return a convenient data format in response to a query. We might call this a “row structure.” We don’t want to pass that row structure inward across a boundary. Doing so would violate the Dependency Rule because it would force an inner circle to know something about an outer circle.
Architects often fall into a trap—a trap that hinges on their fear of duplication.
When code is truly duplicated, we are honour-bound as professionals to reduce and eliminate it.
But there are different kinds of duplication.
There is true duplication, in which every change to one instance necessitates the same change to every duplicate of that instance.
If two duplicated sections of code evolve along different paths—if they change at different rates, and for different reasons—then they are false or accidental duplication.
When you are vertically separating use cases from one another, you will run into this issue too, and your temptation will be to couple the use cases because they have similar screen structures, similar algorithms, or similar database queries and/or schemas. Be careful. Resist the temptation to sin knee-jerk elimination of duplication. Make sure the duplication is real.
By the same token, when you are separating layers horizontally, you might notice that the data structure of a particular database record is very similar to the data structure of a particular screen view. You may be tempted to simply pass the database record up to the UI, rather than to create a view model that looks the same and copy the elements across. Be careful: This duplication is almost certainly accidental. Creating the separate view model is not a lot of effort, and it will help you keep the layers properly decoupled.