"The First Law of Software Architecture is: Everything in software architecture is a trade-off"

《Head First Software Architecture》

Chapter 1: Software Architecture Demystified

  • 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

  • Architectural characteristics represent one part of the structural analysis that architects use to design software systems.
  • Architectural characteristics describe a system's capabilities.
  • Some architectural characteristics overlap with operational concerns (such as availability, scalability, and so on).
  • There are many categories 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.
  • 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

  • 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.
  • For every decision, you will be faced with multiple solutions. 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: Everything in software architecture is a trade-off.
  • The answer to every question in software architecture is "it depends." To learn which solutions are best for your situation, you'll need to identify the top priorities and goals. What are the requirements? What's most important to your stakeholders and customers? Are you in a rush to get to market, or hoping to get things stable in growth mode?
  • 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 the memory store of your project.
  • 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 abiding by 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

  • 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, be sure to provide a descriptive name to clearly identify what the component does.
  • Creating a logical architecture involves four continuous steps: identify components, assign requirements, analyze component responsibilities, and analyze 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 actors in the system and assigning their actions to components.
  • The entity trap is an approach that models components after major entities in the system. 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 another to perform a business function.
  • Afferent coupling, also known as incoming coupling, occurs when other components are dependent on a target component.
  • Efferent coupling, also known as outgoing coupling, occurs when a target component is dependent on other components.
  • Components having too much knowledge about what needs to happen in the system increases 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 harder to manage and control that knowledge.
  • Determining the total coupling (CT) of a logical architecture involves adding the afferent and efferent coupling levels for each component (CA+CE).

Chapter 5: Architectural Styles

  • 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.
  • Another way to categorize architectural styles is by their deployment model. Monolithic architectural styles deploy all the logical components that make up an application as a single unit. Distributed architectural styles deploy the logical components separately from one another, as multiple units.
  • 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

  • A layered architecture is monolithic: the entire system (code and database) is deployed as a single unit.
  • The layers are separated by technical capabilities. Typical layers in this architecture include presentation (for the user interface), business rules (for the workflow and logic of the application), and persistence (facilities to support databases for systems that need persistent storage).
  • The layered architecture supports feasibility well; it is easy to understand and it lets you build simple systems quickly.
  • The layered architecture supports excellent separation of technical concerns, making it easy to add new capabilities like user interfaces or databases.
  • The layered architecture mimics some of the same concerns as the Model-View-Controller design pattern, but translated into physical layers and subject to real-world constraints.
  • User requests flow through the user interface and through each layer before a response is returned to the user.
  • Each request in this architecture goes through each layer.
  • A layered architecture's capabilities degrade over time if teams continue to add functionality due to eventual resource limits (for example, they run out of memory).
  • The layered architecture provides excellent support for specialization (user interface designers, coders, database experts, and so on).
  • Logical components represent the problem domain, yet layers focus on technical capabilities, requiring translation between the domain and architecture.
  • A layered architecture may manifest in several physical architectures, including two-tier (also known as client/server), three-tier (web), and embedded/mobile.
  • Changing and adding to the technical capabilities represented in layers is easy; the layered architecture facilitates this.
  • Changing the problem domain requires coordination across the layers of the architecture, making domain changes more difficult.

Chapter 7: Modular Monoliths

  • A modular monolith is a monolithic architectural style that is partitioned by domains and subdomains that reflect business concerns, not technical concerns.
  • Each subdomain makes up one module of the application. Each module can contain multiple business use cases.
  • Each module can be made up of layers to provide better organization. A module may be technically partitioned as a means to organize its functionality.
  • Avoid having code in one module directly access any functionality in other modules. Allowing this can reduce or eliminate the boundaries between modules.
  • Each module should have a public API that communicates with other modules while shielding the module's internal implementation from the rest of the world.
  • Avoiding intermodule communication allows modules to change internally without affecting other modules.
  • It takes time and effort to ensure that the modules in a modular monolith remain separate and distinct.
  • You can govern a modular monolith using a variety of techniques. Some languages have built-in support for building modules.
  • Another approach is to physically break up the codebase into separate subprojects or even different repositories. This usually involves using a build tool to bring all the modules back together when you build the monolith.
  • Third-party tools can also help with architectural governance.
  • You may choose to use several techniques in combination to ensure the boundaries of individual modules are maintained.
  • You can extend modularity all the way to the database, keeping the data for each module separate.
  • Watch that you don't accidentally couple modules when inserting or fetching data (for example, when using a SQL join statement across tables that belong to different modules).

Chapter 8: Microkernel Architecture

  • The microkernel architectural style provides a structured way to handle customizations via plugins.
  • Microkernel architectures consist of two main parts: the core and one or more plugins.
  • The core system in a microkernel contains minimal functionality and has low volatility.
  • Architects design plugins to customize and/or add behaviors to a system.
  • Generally, plugins only communicate with the core system, not with each other.
  • If plugins do need to communicate with each other, the core must mediate the communication and handle issues like versions and dependencies. It essentially serves as an integration layer.
  • Microkernel architectures can be monolithic architectures or can be implemented as services in a distributed architecture.
  • When built as a monolithic architecture, the core and plugins must be written in the same language.
  • Plugin calls may be synchronous (for example, using REST in a distributed architecture) or asynchronous (using threads in a monolithic architecture or messaging in a distributed one). Whether remote calls are synchronous or asynchronous, architects can implement the plugins in a variety of technology stacks.
  • Monolithic plugins generally offer better performance because calls take place in the same process.
  • Monolithic microkernels suffer from the typical limitations of all monoliths, including limited operational capabilities such as scalability and elasticity.
  • Microkernels that use distributed plugins may offer better scalability because they use multiple processes and offer scalable communications (events).
  • Microkernel architectures are best suited for problems with distinct categories of volatility.
  • If a microkernel's core system changes often, its architects may have chosen the wrong architectural style or may have partitioned the work incorrectly.
  • The microkernel style shows up in lots of places: IDEs, text processing tools, build and deployment tools, integrations, translation layers, insurance applications, and electronics recycling applications, just to name a few.

Chapter 9: Do It Yourself

  • When analyzing requirements for a business problem, always gather additional information from the business stakeholders or project sponsors.
  • While there's no "checklist" for creating an architecture, the four dimensions of software architecture (introduced in Chapter 1) provide a good framework.
  • Identifying driving architectural characteristics requires you to analyze the business requirements and technical constraints.
  • Implicit architectural characteristics become driving characteristics if they are critical or important to the success of the system.
  • Make sure you can tie each driving characteristic back to some sort of requirement or business goal.
  • When identifying logical components and creating a corresponding logical architecture, try to avoid adding physical details such as services, databases, queues, and user interfaces—those artifacts go into the physical architecture.
  • When choosing an architectural style, make sure you consider the characteristics of the architectural style, the problem domain, and the driving architectural characteristics you identified.
  • Hybrid architectures (those combining two or more different architectural styles) are very common. If you use one, be sure to verify that it addresses your critical architectural characteristics.
  • Architectural decision records (ADRs) are a great way to document your choices. They communicate the reasons for your architectural decisions as well as your trade-off analysis.
  • When diagramming your physical architecture, be sure to include all the components you identified in your logical architecture.
  • Remember, there are no right or wrong answers in software architecture. As long as you can provide a reasonable justification for your architectural decisions, you're on the right track.

Chapter 10: Microservices Architecture

  • A microservice is a single-purpose, separately deployed unit of software that does one thing really well.
  • A physical bounded context means that a microservice owns its own data and is the only microservice that can access that data. If a microservice needs data that is owned by another microservice, it must ask for it.
  • The granularity of a microservice is a measure of its size—not physically, but the scope of what it does.
  • Forces that guide you to make your microservices smaller are called granularity disintegrators.
  • Forces that guide you to make your microservices bigger are called granularity integrators.
  • Balance granularity disintegrators and integrators to find the most appropriate level of granularity for a microservice.
  • You can make microservices coarse-grained to start with, then finer-grained as you learn more about the problem domain.
  • Two techniques for sharing functionality in microservices are shared services and shared libraries.
  • A shared service is a microservice that contains a functionality shared by multiple microservices. It's deployed separately and each microservice calls it remotely. Shared services are more agile overall and are good for heterogeneous environments. However, they are not good for scalability, fault tolerance, or performance.
  • A shared library is an independent artifact (like a JAR or DLL file) that is bound to a microservice at compile time. Shared libraries offer better operational characteristics, like scalability, performance, and fault tolerance, but make it harder to manage dependencies and control access.
  • A workflow is when multiple microservices are needed for a single business request or business process.
  • Workflows that use orchestration require a central orchestrator microservice, which works like a conductor in a symphony orchestra.
  • In workflows that use choreography, the services talk to each other, like dancers performing a routine.
  • Scalability, fault tolerance, evolvability, and overall agility (maintainability, testability, and deployability) are the superpowers of the microservices architectural style.
  • Performance, complexity, cost, monolithic databases that can't be broken apart, and high semantic coupling are kryptonite to microservices.
  • Microservices should be as independent as possible; too much communication between them will degrade the benefits of this architectural style.

Chapter 11: Event-Driven Architecture

  • An event is something that happens in the system. Events are the fundamental way services communicate with each other in event-driven architecture.
  • Events are not the same thing as messages—events broadcast some action a service just performed to other services in the system, whereas messages are commands or requests directed to a single service.
  • An initiating event originates from a customer or end user and kicks off a business process.
  • A derived event is generated by a service in response to an initiating event.
  • Any action a service performs should trigger a derived event to provide architectural extensibility—the ability to extend the system to add new functionality.
  • Event-driven architecture (EDA) is fast because it generally uses asynchronous (async) communication—services don't wait for a response or acknowledgment from other services when sending them information.
  • Asynchronous communication is sometimes called fire-and-forget.
  • Architects usually use a dotted line to represent async communication between services and a solid line to represent sync communication.
  • Unlike microservices, event-driven architecture can use a variety of database topologies:
  • With the monolithic database topology, all services share a single database.
  • With the domain-partitioned databases topology, each domain in the system has its own database, shared by all of the services within that domain.
  • With the database-per-service pattern, each service has its own database.
  • Event-driven architecture and microservices are very different architectural styles:
  • EDA relies mostly on asynchronous communication between services, whereas microservices typically rely on synchronous communication using REST.
  • EDA is built on event processing—processing things that have already happened. Microservices architecture is built on request processing—processing a command or request about something that needs to happen.
  • Microservices are fine-grained and single-purpose, whereas services in EDA can be any size.
  • In the database-per-service pattern, each microservice owns its own data, whereas in EDA services can (and usually do) share data.
  • You can combine microservices and EDA to create a hybrid architecture called event-driven microservices.
  • EDA is very complex because it uses asynchronous communication and parallel event processing, and has varied database topologies.
  • It's really hard to test asynchronous processing and parallel tasks, making testability a weakness in EDA.
  • EDA is highly scalable because of asynchronous processing and service decoupling.

Chapter 12: Summary

  • When analyzing requirements for a business problem, always gather additional information from the business stakeholders or project sponsors.
  • While there's no "checklist" for creating an architecture, the four dimensions of software architecture (introduced in Chapter 1) provide a good framework.
  • Identifying driving architectural characteristics requires you to analyze the business requirements and technical constraints.
  • Implicit architectural characteristics become driving characteristics if they are critical or important to the success of the system.
  • Make sure you can tie each driving characteristic back to some sort of requirement or business goal.
  • When identifying logical components and creating a corresponding logical architecture, try to avoid adding physical details such as services, databases, queues, and user interfaces—those artifacts go into the physical architecture.
  • When choosing an architectural style, make sure you consider the characteristics of the architectural style, the problem domain, and the driving architectural characteristics you identified.
  • Hybrid architectures (those combining two or more different architectural styles) are very common. Just be sure to verify that the hybrid architecture still addresses your critical architectural characteristics.
  • Architectural decision records (ADRs) are a great way to document your choices. They communicate the reasons for your architectural decisions as well as your trade-off analysis.
  • When diagramming your physical architecture, be sure to include all the components you identified in your logical architecture.
  • Remember that there are no right or wrong answers in software architecture. As long as you can provide a reasonable justification for your architectural decisions, you are on the right track.