There have been multiple architectural paradigms over the year, but all of them have one key goal: managing complexity. How can we package code into components and work with these components as abstract entities to infer about and build chunks of behavior?
These components divide the system into partitions, so that each partition has a specific concern and role. Each component has well defined interfaces and responsibilities and is segregated from the rest of the components. Having this abstraction allows us to not worry about the inner workings of the components.
System decomposition needs to be a well thought-out activity. There are two key metrics for assessing how good your components are, named cohesion and coupling:
- High cohesion means a component performs a single related task.
- Low coupling means components should have less dependency between themselves.
A component can easily be extended to add more functionality or data to it. And, if needed, it should be completely replaceable, without that affecting the rest of the system.
Robert Cecil Martin (more commonly known as Uncle Bob) is a software engineer and author. He paints a beautiful picture through his clean architecture blog, describing the component/layering idea:
The concentric circles represent different layers (that is, different sets of components or higher-order components) of software.
In general, the inner circles are more abstract, and deal with things such as business rules and policies. They are the least likely to change when something external changes. For example, you would not expect your employee entity to change just because you want to show employee details on a mobile application, in addition to an existing web product.
The outer circles are mechanisms. They define how the inner circles are fulfilled using the mechanisms available. They are composed of things such as the database and web framework. This is generally code that you re-use, rather than write fresh.
The Controllers (or Interface Adaptors) layer converts data from the formats available in the mechanisms to what is most convenient for the business logic.
The rule that is key to making this architecture successful is the dependency rule. This rule says that source code dependencies can only point inward. Nothing in an inner circle (variables, classes, data, and private functions) can know anything at all about something in an outer circle. The interfaces between the layers and the data that crosses these boundaries are well defined and versioned. If a software system follows this rule, then any layer can be replaced or changed easily, without affecting the rest of the system.
These four layers are just indicative—different architectures will bring out different numbers and sets of layers (circles). The key is to have a logical separation of the system so that, as new code needs to be written, developers have crisp ideas on what goes where.
Here is a quick summary of main architectural paradigms that are commonly used:
Package-based |
The system is broken down into packages (here, the component is the package), where each package has a well-defined purpose and interface. There is clear separation of concerns in terms of the components. However, the level of independence and enforcement of segregation between modules is variable: in some contexts, the parts have only logical separation, and a change in one component might require another component to be re-built or re-deployed.
|
Layering/N-tier/3-tier
|
This segregates functionality into separate layers, where components are hosted in a specific layer. Generally, layers only interact with the layer below, thereby reducing complexity and enabling reusability. Layers might be packages or services. The most famous example of layered architecture is the networking stack (7 layer OSI or the TCP/IP stack).
|
Async / message-bus / actor model / Communicating Sequential Processes (CSP)
|
Here, the key idea is that systems communicate with each other through messages (or events). This allows for clean decoupling: the system producing the event does not need to know about the consumers. This allows allows for 1-n communication.
In Unix, this paradigm is employed via pipes: simple tools, such as cat and grep, are coupled through pipes to enable more complex functionality such as search for cat in words.txt.
In a distributed system, the messages exist over the network. We shall look at distributed systems in detail in a later chapter. If you're wondering what the actor model or CSP is, these paradigms are explained later in this chapter.
|
Object-oriented
|
This is an architectural style where components are modeled as objects that encapsulate attributes and expose methods. The methods operate on the data within the object. This approach is discussed in detail in Chapter 3, Design Patterns.
|
Model-View-Controller (MVC) / separated presentation
|
Here, the logic for handling user interaction is placed into a view component, and the data that powers the interaction goes into a model component. The controller component orchestrates the interactions between them. We shall look at this in more detail in Chapter 6, Messaging.
|
Mircoservices / service-oriented architecture (SOA) |
Here, the system is designed as a set of independent services that collaborate with each other to provide the necessary system behavior. Each service encapsulates its own data and has a specific purpose. The key difference here from the other paradigms is the existence of independently running and deployable services. There is a deep dive on this style further on in this chapter.
|