Chapter 1. Software Architecture Demystified: Let’s Get Started!
Architectural Dimensions
In civil engineering, there are three dimensions to the building architecture. However, in Software engineering, there are four dimensions:
- Architectural characteristics
This dimension describes what aspects of the system the architecture needs to support—things like scalability, testability, availability, and so on. - Architectural decisions
This dimension includes important decisions that have long-term or significant implications for the system—for example, the kind of database it uses, the number of services it has, and how those services communicate with each other. - Logical components
This dimension describes the building blocks of the system’s functionality and how they interact with each other. For example, an e-commerce system might have components for inventory management, payment processing, and so on. - Architectural style
This dimension defines the overall physical shape and structure of a software system in the same way a building plan defines the overall shape and structure of your home.
All these four dimensions are dependent on each other and a good architecture design always includes all these four dimensions.
Architectural characteristics
- Collectively, things like performance, scalability, reliability, and availability are also known as nonfunctional requirements, system quality attributes, and simply “the -ilities” because most end with the suffix -ility.
- We like the term architectural characteristics because these qualities help define the character of the architecture and what it needs to support.
Architectural decisions
- These decisions are the structural guide to the dev teams.
- Here we define how many services, which DB, etc.
Logical Components
- Logical components are the building blocks of a system, much in the same way rooms are the building blocks of your home. A logical component performs some sort of function, such as processing the payment for an order, managing item inventory, or tracking orders.
A: The domain is the problem you are trying to solve, and the system functionality is how you are solving that problem. In other words, the domain is the “what,” and the system’s functionality is the “how.”
Architectural styles
- Architectural styles define the overall shape and structure of a software system, each with its own unique set of characteristics.
- For example, the microservices architectural style scales very well and provides a high level of agility—the ability to respond quickly to change—whereas the layered architectural style is less complex and less costly.
- The event-driven architectural style provides high levels of scalability and is very fast and responsive.
Software Architecture vs. Designer
- A: Architecture is less about appearance and more about structure, while design is less about structure and more about appearance.
- Think about a typical business application. The architecture, or structure, is all about how the web pages communicate with backend services and databases to retrieve and save data, whereas the design is all about what each page looks like the colors, the placement of the fields, which design patterns you use, and so on.
- Again, it becomes a matter of structure versus appearance.
- The more strategic (long term) a decision is it will be more architecturial. On the other way, the more tactical a decision is (which is made keeping a particular context) it will be designed.
Summary
- Software architecture is less about appearance and more about structure, whereas design is more about appearance and less about structure.
- You need to use four dimensions to understand and describe software architecture: architectural characteristics, architectural decisions, logical components, and architectural style.
- Architectural characteristics form the foundational aspects of software architecture. You must know which architectural characteristics are most important to your specific system, so you can analyze trade-offs and make the right architectural decisions.
- Architectural decisions serve as guideposts to help development teams understand the constraints and conditions of the architecture.
- The logical components of a software architecture solution make up the building blocks of the system. They represent things the system does and are implemented through class files or source code.
- Like with houses, with software, there are many different architectural styles you can use. Each style supports a specific set of architectural characteristics, so it’s important to make sure you select the right one (or combination of them) for your system.
- It’s important to know if a decision is about architecture or design because that helps determine who should be responsible for the decision and how important it is.
Chapter 2. Architectural Characteristics: Know Your Capabilities
We define architectural characteristics in three parts, as shown here.
Beware of resume-driven development (RDD)! It’s fun to play with new stuff, and we should keep learning, but trying to support too many architectural characteristics in our systems will not align with larger priorities or help the application succeed.
Explicit architectural characteristics are specified in the requirements for the application. For example we need to support 1000 concurrent users.
Implicit architectural characteristics are factors that influence an architect’s decisions but aren’t explicitly called out in the requirements. Security is often an implicit architectural characteristic: even if it isn’t mentioned in the requirements, architects know that we shouldn’t design an insecure system. For example we need to take care of the security and privacy of the 1000 concurrent users.
Architectural characteristics don’t just appear out of thin air. In fact, there are three different sources from which you should look to derive them.
- The problem domain: Part of your job is analyzing a problem to determine what architectural characteristics the system requires. Many structural design decisions come directly from the problem domain.
- Environmental awareness: Many requirements come from having a good understanding of the environment in which you’re operating. For example, are you working for a fast-moving startup, or a large enterprise with a lot at stake?
- Holistic domain knowledge: Sure, you’re working with a particular problem domain—but we can assure you that the domain is a lot bigger than your particular focus. Let’s say you’re building out a payment system. While understanding what’s required of you is important, you’ll reveal architectural characteristics if you understand the financial world, finance industry regulations, and customers’ habits.
When users bring us solutions rather than requirements, it’s architects’ job to imitate an annoying toddler and keep asking “But why?!?” enough times to uncover the actual requirements hidden within the solutions.
More requirements are NOT better
What happens when an architect takes a list of possible architectural characteristics for Lafter to a group of business users and asks them, “Which of these do you want for the system?”
They invariably answer: “All of them!”
As nice as it would be to be able to accommodate that request, it’s not a good idea to try.
Remember, architectural characteristics are synergistic with each other and with the problem domain. That means the more architectural characteristics the system must support, the more complex its design must be.
When undertaking structural design for a system, architects must find a balance between domain priorities and the architectural characteristics necessary for success.
Summary
- Architectural characteristics represent one part of the structural analysis that architects use to design software systems. (We’ll talk abou the other part, logical components, in Chapter 4.)
- Architectural characteristics describe a system’s capabilities.
- Some architectural characteristics overlap with operational concerns (such as availability, scalability, and so on).
- There are many catagories of architectural characteristics. No one can make a comprehensive list, because the software development ecosystem is constantly changing.
- When identifying architectural characteristics, architects look for factors that influence structural design.
- Architects should be careful not to specify too many architectural characteristics, because they are synergistic—changing one requires other parts of the system to change.
- Some architectural characteristics are implicit: not explicitly stated in requirements, yet part of an architect’s design considerations.
- Some architectural characteristics may appear in multiple categories.
- Many architectural characteristics are cross-cutting: they interact with other parts of (and decisions in) the organization.
- Architects must derive many architectural characteristics from requirements and other domain design considerations.
- Some architectural characteristics come from domain and/or environmental knowledge, outside of the requirements of a specific application.
- Some architectural characteristics are composites: they consist of a combination of other architectural characteristics.
- Architects must learn to translate “business speak” into architectural characteristics.
- Architects should limit the number of architectural characteristics they consider to some small number, such as seven.
Chapter 3. The Two Laws of Software Architecture: Everything’s a Trade-Off
What happens when there are no “best practices”? The nice thing about best practices is that they’re relatively risk-free ways to achieve certain goals. They’re called “best” (not “better” or “good”) for a reason—you know they work, so why not just use them? But one thing you’ll quickly learn about software architecture is that it has no best practices. You’ll have to analyze every situation carefully to make a decision, and you’ll need to communicate not just the “what” of the decision, but the “why.”
So, how do you navigate this new frontier? Fortunately, you have the laws of software architecture to guide you. This chapter shows you how to analyze trade-offs as you make decisions. We’ll also show you how to create architectural decision records to capture the “hows” and “whys” of decisions. By the end of this chapter, you’ll have the tools to navigate the uncertain territory that is software architecture.
This may be the chapter where we will learn why AI can not replace software architects.
Tradeoff
- ATAM
- ATAM is a popular method of trade-off analysis. With ATAM, you start by considering the business drivers, the “-ilities,” and the proposed architecture, which you present to the stakeholders. Then, as a group, you run through a bunch of scenarios to produce a “validated architecture.” While ATAM offers a good approach, we believe it comes with certain limitations—one being that it assumes the architecture is static and doesn’t change.
- CBAM
- Another popular approach is the Cost Benefit Analysis Method (CBAM). In contrast to ATAM, CBAM focuses on the cost of achieving a particular “-ility.”
We recommend you look at both methods and perhaps consider combining them—ATAM can help with trade-off analysis, while CBAM can help you get the best return on investment (ROI).
Remember—the process is not as important as the goal, which is to arrive at an architecture that satisfies the business’s needs.
Some people pick a particular technique, approach, or tool regardless of the problem. Often, they choose something they’ve had a lot of success with. Sometimes, they have what we affectionately call “shiny object syndrome,” where they think some new technology or method will solve all their problems.
Regardless of past achievements or future promises, remember—for every upside, there’s a downside. The questions you need to answer are “Will the upsides help you implement a successful application?” and “Can you live with the downsides?” Whenever someone praises a certain approach, your response should be: “What are the trade-offs?”
Architectural Decision Records (ADRs)
Future architects (or even “future you”) might be able to discern what you did and even how you did it—but it’ll be tough for them to tell why you did it that way. They might waste time exploring solutions you’ve already rejected for good reasons or miss a critical factor that swayed your decision.
This is why we have the Second Law. You need to understand and record the “why” of each decision so it doesn’t get lost in the sands of time.
An ADR has seven sections: Title, Status, Context, Decision, Consequences, Governance, and finally Notes. Every aspect of an architectural decision, including the decision itself, is captured in one of these sections.
Title
The title should consist mostly of nouns. Keep it terse: you’ll have plenty of opportunities to go into detail later. It should describe what the ADR is about, much like the headline of a news article or blog post. Get that right, and the rest will follow.
The title should start with a number—we suggest using three digits, with leading zeros where needed. This allows you to number your ADRs sequentially, starting with 001 for your first ADR, all the way to 999. Every time you add a new ADR, you increment the number. This makes it easy for anyone reading your records to know which decisions came before others.
Status
These are the possible status of an ADR.
An ADR can be replaced/superseded by another ADR due to a change in requirements.
Context
The Context section in the ADR template is your place to explain the circumstances that drove you to make the decision the ADR is capturing. It should also capture any factors that influenced your decision. While technological reasons will usually find their way onto this list, it’s not unusual to include cultural or political factors to help the reader understand where you’re coming from.
Decision
If this ADR’s status is RFC or Proposed, the decision hasn’t been made (yet). Even so, the Decision section starts by clearly expressing the decision being made. The tone of the writing should reflect that. It’s best to use an authoritative voice when stating the decision, with active phrases like “we will use” (as opposed to “we believe” or “we think”).
The Decision section also explains why you’re making this decision, paying tribute to the Second Law of Software Architecture: “Why is more important than how.” Future you, or anyone else who reads the ADR, will then understand not just the decision but the justification for it.
Consequences
It’s important to realize the consequences—good and bad—of architectural decisions and document them. This increases transparency by ensuring everyone understands what the decision entails, including the team(s) affected by it. Most importantly, it allows everyone to assess whether the decision’s positive consequences outweigh its negative ones.
The consequences of an ADR can be limited in scope or have huge ramifications. Architectural decisions can affect all kinds of things—teams, infrastructure, budgets, and even the implementation of the ADR itself. Here’s an incomplete list of questions to ask:
- How does this ADR affect the implementing team? For instance, does it change the algorithms? Does it make testing harder or easier? How will we know when we’re “done” implementing it?
- Does this ADR introduce or decommission infrastructure? What does that entail?
- Are cross-cutting concerns like security or observability affected? If so, what effects will that have across the organization?
- How will the decision affect your time and budget? Does it introduce costs or save money? Will it take an arduous effort to implement or make things easier?
Governance
You and your team spent a bunch of time analyzing trade-offs and writing an ADR to record the decision. Now what? How do you ensure the decision is correctly implemented—and that it stays that way?
This is the role of the Governance section, which is vital in any ADR. Here, you outline how you’ll ensure that your organization doesn’t deviate from the decision—now or in the future. You could use manual techniques like pair programming, code reviews, or automated means like specialized testing frameworks.
Closing Notes
The Notes section contains metadata about the ADR itself. Here’s a list of fields we like to include in our ADRs:
- Original author
- Approval date
- Approved by
- Superseded date
- Last modified date
- Modified by
- Last modification
Key Points
- There is nothing “static” about architecture. It’s constantly changing and evolving.
- Requirements and circumstances change. It’s up to you to modify your architecture to meet new goals.
- You will be faced with multiple solutions for every decision. To find the best (or least worst), do a trade-off analysis. This collaborative exercise helps you identify the pros and cons of every possible option.
- The First Law of Software Architecture is that everything in software architecture is a trade-off.
- The product of a trade-off analysis is an architectural decision: one of the four dimensions needed to describe any architecture.
- An architectural decision involves looking at the pros and cons of every choice in light of other constraints—such as cultural, technical, business, and customer needs—and choosing the option that serves these constraints best.
- Making an architectural decision isn’t just about choosing; it’s also about why you’re choosing that particular option.
- The Second Law of Software Architecture is: Why is more important than how?
- To formalize the process of capturing architectural decisions, use architectural decision records (ADRs). These documents have seven sections: Title, Status, Context, Decision, Consequences, Governance, and Notes.
- Over time, your ADRs will build into a log of architectural decisions that will serve as your project's memory store.
- An ADR’s title should consist of a three-digit numerical prefix and a noun-heavy, succinct description of the decision being made.
- An ADR can be assigned one of many statuses, depending on the kind of ADR and its place in the decision workflow.
- Once all parties involved in the decision sign off on the ADR, its status becomes Accepted.
- If a future decision supplants an Accepted ADR, you should write a new ADR. The supplanted ADR’s status is marked as Superseded and the new ADR becomes Accepted.
- The Context section of an ADR explains why the decision needed to be made to begin with.
- The Decision section documents and justifies the actual decision being made. It always includes the “why.”
- The Consequences section describes the decision’s expected impact, good and bad. This helps ensure that the good outweighs the bad, and aids the team(s) implementing the ADR.
- The Governance section lists ways to ensure that the decision is implemented correctly and that future actions do not stray away from the decision.
- The final section is Notes, which mostly records metadata about the ADR itself—like its author and when it was created, approved, and last modified.
- ADRs are important tools for adhering to the Second Law of Software Architecture because they capture the “why” along with the “what.”
- ADRs are necessary for building institutional knowledge and helping teams learn from one another.
Chapter 4. Logical Components: The Building Blocks
A logical architecture shows a system's logical building blocks and their interaction (called coupling).
On the other hand, physical architecture shows things like the architectural style, services, protocols, databases, user interfaces, API gateways, etc.
The logical architecture of a system is independent of the physical architecture—meaning the logical architecture doesn’t care about databases, services, protocols, and so on.
Identifying logical components isn’t as easy as it sounds. To help, we’ve created this flowchart. Don’t worry—we’ll cover these steps in detail in the following pages.
Step 1: Identifying initial core components
There are two common approaches to finding the initial logical components:
- The Workflow approach
- The Actor/Action approach
Workflow approach
The workflow approach is an effective way to identify an initial set of core components by considering the system's significant workflows—in other words, the journey a user might take through the system. Don’t worry about capturing every step; start with the primary processing steps, then work down to more details.
Actor/action approach
The actor/action approach is constructive if multiple actors (users) are in the system.
You start by identifying the various actors. Then, you identify some of the primary actions they might take and assign each action to a new or existing component.
Entity Trap
- Naming like a
manager
needs to be more specific and clear. - A component should have a few responsibilities.
- This can become too big and difficult to maintain, scale, and make fault tolerant.
Watch out for words like manager or supervisor when naming your logical components—those are good indicators that you might be in the entity trap.
Step 2: Assign requirements
In this step, you’ll take functional user stories or requirements and determine which component should be responsible for each. Remember, a directory structure represents each component. Your source code resides in that directory, so it contains that requirement.
Step 3: Analyze roles and responsibilities
As you start assigning functionality (in other words, user stories or requirements) to logical components, the roles and responsibilities of each component will begin to grow naturally.
The purpose of this step is to make sure that the component to which you are assigning functionality should be responsible for that functionality and that it doesn’t end up doing too much.
Sticking to cohesion
When you analyze a component’s role and responsibility statement or operations, check if the functionality is closely related. This is known as cohesion: the degree and manner to which the operations of a component are interrelated. Cohesion is a valuable tool for ensuring a component has the proper responsibilities.
When analyzing the role and responsibilities of a component, it’s common to find an outlier (an odd piece of functionality) or a component that is doing too much. Shifting some of the responsibility to other components is usually a good idea in these cases.
Step 4: Analyze characteristics
The final step in identifying initial core components is to verify that each component aligns with the driving architectural characteristics critical for success. In most cases, this involves breaking apart a component for better scalability, elasticity, or availability, but it could also involve putting components together if their functionalities are tightly coupled.
Component coupling
As you identify the initial core components, it’s important to determine how they interact. This is known as component coupling: the degree to which components know about and rely on each other. The more the components interact, the more tightly coupled the system is and the harder it will be to maintain.
Remember this diagram from several pages ago? It’s called a “big ball of mud” because there are so many component interactions and dependencies that the diagram looks like a ball of mud (or maybe like a bowl of spaghetti).
That’s why it’s important to consider how components interact and what dependencies exist between them.
You need to be concerned about two types of coupling when creating logical components: afferent coupling and efferent coupling. Don’t be concerned if you’ve never heard these formal terms before—we will explain them in the following pages.
Coupling is all about how much knowledge components have about the rest of the system.
Afferent coupling
Children depend on their parents for many things, like making sure they have plenty of food to eat and a safe place to live, driving them to soccer practice, or even giving them an allowance to buy candy or a cool comic book. As it turns out, parents are afferently coupled to their children, and even to the family dog, because they all depend on the parents for something.
Afferent coupling is the degree and manner to which other components depend on some target component (in this case, Mom). It’s sometimes referred to as fan-in, or incoming, coupling. In most code analysis tools, it’s denoted as CA.
Did you know that the odd-sounding word afferent means “carrying toward”? It gets its roots from the Latin words ad (meaning “to” or “toward”) and ferre (“to carry”). In the medical field, the word afferent refers to nerves that carry impulses to the brain (your afferent nerves).
Efferent coupling
Now, let’s look at things from a young child’s point of view. As a child, you might have depended not only on your parents but also on your teachers, friends, classmates, etc. Being dependent on others is known as efferent coupling.
Efferent coupling is exactly the opposite of afferent coupling, and it’s measured by the number of components on which a target component depends. It’s also known as fan-out coupling or outgoing coupling. In static source code analysis tools, it’s usually denoted as CE.
Measuring coupling
You can measure a particular component’s amount of coupling in three ways:
- by considering its total afferent coupling (CA),
- its total efferent coupling (CE), and
- its total coupling (CT), or the sum of the total afferent and efferent coupling.
These measurements tell you which components have the highest and lowest coupling, as well as the entire system’s overall coupling level.
Law of Demeter
Developers are taught to strive for loosely coupled systems, but not how to do it. We’ll show you how by introducing a technique called the Law of Demeter.
The Law of Demeter, also known as the Principle of Least Knowledge, is named after Demeter, the Greek goddess of agriculture.
She produced all grain for mortals to use, but she knew nothing about what they did with it. Because of this, Demeter was loosely coupled to the mortal world.
Logical components work in the same way. The more knowledge a component has about other components and what needs to happen in the system, the more coupled it is to those components. By reducing its knowledge of other components, we reduce that component’s level of coupling.
The total system coupling level didn’t bother us that much. What does bother us is how tightly coupled the Order Placement component is (CT=5), how unbalanced the component coupling is, and how much knowledge the Order Placement component has about the order placement process.
By moving the knowledge of actions to take for a “low stock” condition to Inventory Management, we reduced the amount of knowledge about the system, and hence the coupling, of the Order Placement component.
However, we increased the knowledge of the Inventory Management component and thus increased its coupling.
This is what the Law of Demeter is all about—less knowledge, less coupling; more knowledge, more coupling.
Did you notice that while we reduced the coupling of the Order Placement component, the total system coupling level remained the same? That’s because we didn’t remove the knowledge from the system, we just moved it to another component—Inventory Management.
A balancing act
With the tightly coupled architecture, if you want to know what happens when a customer places an order, you only have to look at the Order Placement component to understand the workflow.
However, changing the Item Pricing and Supplier Ordering components will no longer affect the Order Placement component.
With loose coupling, you distribute the knowledge about what needs to happen so that no one component knows all the steps. To understand the workflow of placing an order, you have to go to multiple components to get the full picture.
However, in this case, the Order Placement component depends on four other components. If any of those components change, it could break the Order Placement component.
Two components are coupled if a change in one component might cause a change in the other component.
Summary
- Logical components are the functional building blocks of a system.
- A logical component is represented by a directory structure—the folder where you put your source code.
- When naming a component, provide a descriptive name to identify what the component does.
- Creating a logical architecture involves four continuous steps: identifying components, assigning requirements, analyzing component responsibilities, and analyzing architectural characteristics.
- You can use the workflow approach to identify initial core logical components by assigning the steps in a primary customer journey to components.
- You can use the actor/action approach to identify initial core logical components by identifying the system's actors and assigning their actions to components.
- The entity trap is an approach that models components after major system entities. Avoid using this approach because it creates ambiguous components that are too large and have too much responsibility.
- When assigning requirements to components, review each component’s role and responsibilities to make sure it should be performing that function.
- Coupling happens when components depend on one other to perform a business function.
- Afferent coupling, or incoming coupling, occurs when other components depend on a target component.
- Efferent coupling, or outgoing coupling, occurs when a target component depends on other components.
- Components needing more knowledge about what must happen in the system increase component coupling.
- The Law of Demeter states that services or components should have limited knowledge of other services or components. This law is useful for creating loosely coupled systems.
- While loose coupling reduces dependencies between components, it also distributes workflow knowledge, making it more difficult to manage and control.
- Determining a logical architecture's total coupling (CT) involves adding the afferent and efferent coupling levels for each component (CA + CE).
Chapter 5. Architectural Styles: Categorization and Philosophies
If you’ve done software development for any time, you may have heard about different architectural styles, like monoliths and microservices. We place them into two categories to help us think systematically about them. The first deals with how the code is divided: technical concerns or domain (business) concerns. The second category concerns how the system is deployed: is all the code delivered as one or multiple units?
In a technically partitioned architecture, code is divvied up by technical concerns—there might be a presentation tier, a business (services) layer, and so on. The principle at play here is separation by concern—which most people think about in horizontal layers. This is like the employees of a fancy restaurant: chef, waiter, and seat provider each have their dedicated roles to play.
On the other hand, imagine a food court. It has many restaurants, each specializing in a particular kind of food: pizza, salads, stir-fry, burgers. In other words, each restaurant has a specific domain.
In domain-partitioned architectures, the system's structure is aligned with the domain. Rather than by roles, the code (and systems) are separated in ways that align with the problem you’re attempting to solve.
Summary
- There are a lot of architectural styles—in fact, too many to count.
- There are multiple ways to categorize architectural styles. One is by their partitioning style. Architectural styles can be either technically partitioned or domain partitioned.
- In technically partitioned architectural styles, the code is split up by technical concern. For example, there might be a presentation layer and a services layer.
- In domain-partitioned architectural styles, the code is instead split up by problem domain.
- Monolithic architectures are easier to understand and debug and are often cheaper to build (at least initially). This makes them great candidates if there is a rush to bring a product to market.
- As monolithic applications grow, scaling them up can become arduous. It’s an all-or-nothing scenario: you either scale up the whole application or nothing at all.
- Monolithic applications can also be unreliable—a bug can make the entire application unusable.
- Distributed architectures are highly scalable since their logical components are deployed separately, allowing different parts of the application to scale independently of one another.
- Distributed architectures encourage a high degree of modularity, which means testing them is easier.
- Distributed architectures are extremely expensive to develop, maintain, and debug.
- Distributed architectures use the network so that different services can talk to one another to complete work. This introduces even more complexity.
Chapter 6. Layered Architecture: Separating Concerns
For simple problems that require quick solutions, you can opt for disposable architectures. But if you want to create something that lasts, choose the simplest architecture that provides organization and benefits without slowing down the delivery process. The layered architecture is a great choice as it's easy to implement and uses familiar design patterns. In this article, we'll explore its layers.
source: https://learning.oreilly.com/library/view/head-first-software/9781098134341/