Head First Architecture

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:

All these four dimensions are dependent on each other and a good architecture design always includes all these four dimensions.

Architectural characteristics

|800

Architectural decisions

|800

Logical Components

|800

Q: What is the difference between the system functionality and the domain?

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

|800

Software Architecture vs. Designer

Q: What are the differences between software architecture and design or both are the same?

  • 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.

!800
|800

Summary

Chapter 2. Architectural Characteristics: Know Your Capabilities

|800

We define architectural characteristics in three parts, as shown here.
|800

Caution

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.

  1. 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.
  2. 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?
  3. 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


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.

|400

Tradeoff

  1. 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.
  2. 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)

|400

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.

|800
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

Title|800

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

|500
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:

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:

Key Points

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.

Logical Component|800

Step 1: Identifying initial core components

There are two common approaches to finding the initial logical components:

  1. The Workflow approach
  2. 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.
|500

Entity Trap

|500

Tip

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.

|700

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.

|800

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.

Note

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.
|500

Note

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.

|500

Measuring coupling

You can measure a particular component’s amount of coupling in three ways:

  1. by considering its total afferent coupling (CA),
  2. its total efferent coupling (CE), and
  3. 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.

Coupling level|800

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.

A tightly coupled system|800

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.

A loosely coupled system|800

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.

Note

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

Chapter 5. Architectural Styles: Categorization and Philosophies

Styles|800

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.

|700
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


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/

Thoughts đŸ€” by Soumendra Kumar Sahoo is licensed under CC BY 4.0