Software Architecture
Author: Neal Ford, Mark Richards, Pramod Sadalage, and Zhamak Dehghani
Last Accessed on Kindle: Apr 28 2024
Ref: Amazon Link
Donât try to find the best design in software architecture; instead, strive for the least worst combination of trade-offs.
One of the most effective ways of documenting architecture decisions is through Architectural Decision Records (ADRs). ADRs
We will be using the following ADR format with the assumption that each ADR is approved: ADR: A short noun phrase containing the architecture decision Context In this section of the ADR we will add a short one- or two-sentence description of the problem, and list the alternative solutions. Decision In this section we will state the architecture decision and provide a detailed justification of the decision. Consequences In this section of the ADR we will describe any consequences after the decision is applied, and also discuss the trade-offs that were considered.
Architectural fitness function: any mechanism that performs an objective integrity assessment of some architecture characteristic or combination of architecture characteristics. Here is a point-by-point breakdown of that definition:
By codifying rules about code quality, structure, and other safeguards against decay into fitness functions that run continually, architects build a quality checklist that developers canât skip. A
The second law of software architecture: why is more important than how.
Meilir Page-Jones made the astute observation that coupling in architecture may be split into static and dynamic coupling. Static coupling refers to the way architectural parts (classes, components, services, and so on) are wired together: dependencies, coupling degree, connection points, and so on. An architect can often measure static coupling at compile time as it represents the static dependencies within the architecture. Dynamic coupling refers to how architecture parts call one another: what kind of communication, what information is passed, strictness of contracts, and so on.
All things are poison, and nothing is without poison; the dosage alone makes it so a thing is not a poison. Paracelsus
Similar, architects must distinguish two important differences. An easy way to think about the difference is that static coupling describes how services are wired together, whereas dynamic coupling describes how services call one another at runtime.
Static coupling analyzes operational dependencies, and dynamic coupling analyzes communication dependencies.
If your microservices must be deployed as a complete set in a specific order, please put them back in a monolith and save yourself some pain.
As the flowchart in Figure 4-1 illustrates, the first step in an architecture decomposition effort is to first determine whether the codebase is even decomposable. We cover this topic in detail in the next section. If the codebase is decomposable, the next step is to determine if the source code is largely an unstructured mess with no clearly definable components. If thatâs the case, then tactical forking (see âTactical Forkingâ) is probably the right approach. However, if the source code files are structured in a way that combines like functionality within well-defined (or even loosely defined) components, then a component-based decomposition approach (see âComponent-Based Decompositionâ) is the way to go.
Afferent coupling measures the number of incoming connections to a code artifact (component, class, function, and so on). Efferent coupling measures the outgoing connections to other code artifacts.
A component as a building block of the application that has a well-defined role and responsibility in the system and a well-defined set of operations. Components in most applications are manifested through namespaces or directory structures and are implemented through component files (or source files).
Service-based architecture is a hybrid of the microservices architecture style where an application is broken into domain services, which are coarse-grained, separately deployed services containing all of the business logic for a particular domain. Moving to a service-based architecture is suitable as a final target or as a stepping-stone to microservices:
The tactical forking pattern was named by Fausto De La Torre as a pragmatic approach to restructuring architectures that are basically big balls of mud.
The first step in tactical forking involves cloning the entire monolith, and giving each team a copy of the entire codebase,
Each team receives a copy of the entire codebase, and they start deleting (as illustrated previously in Figure 4-7) the code they donât need rather than extract the desirable code.
Now the restructuring is complete, leaving two coarse-grained services as the result.
It has its share of trade-offs:
The name of this pattern is apt (as all good pattern names should be)âit provides a tactical rather than strategic approach for restructuring architectures, allowing teams to quickly migrate important or critical systems to the next generation (albeit in an unstructured way).
These decomposition patterns are summarized as follows:
One metric weâve found useful for component sizing is calculating the total number of statements within a given component (the sum of statements within all source files contained within a namespace or directory). A statement is a single complete action performed in the source code, usually terminated by a special character (such as a semicolon in languages such as Java, C, C++, C#, Go, and JavaScript; or a newline in languages such as F#, Python, and Ruby). While not a perfect metric, at least itâs a good indicator of how much the component is doing and how complex the component is.
The following fitness functions can assist in finding common domain functionality.
The following terms and corresponding definitions are important for understanding and applying the Flatten Components decomposition pattern:
Our advice when moving shared code to a separate component (leaf node namespace) is to pick a word that is not used in any existing codebase in the domain, such as .sharedcode, .commoncode, or some such unique name. This allows the architect to generate metrics based on the number of shared components in the codebase, as well as the percentage of source code that is shared in the application. This is a good indicator as to the feasibility of breaking up the monolithic application.
Identifying and understanding the level of component coupling not only allows the architect to determine the feasibility of the migration effort, but also what to expect in terms of the overall level of effort.
Creating component domains is an effective way of determining what will eventually become domain services in a service-based architecture.
Once components have been properly sized, flattened, and grouped into domains, those domains can then be moved to separately deployed domain services, creating what is known as a service-based architecture (see Appendix A). Domain services are coarse-grained, separately deployed units of software containing all of the functionality for a particular domain (such as Ticketing, Customer, Reporting, and so on).
A word of advice, however: donât apply this pattern until all of the component domains have been identified and refactored. This helps reduce the amount of modification needed to each domain service when moving components (and hence source code) around.