My personal notes on the book Fundamentals of Software Architecture by Mark Richards and Neal Ford.
Introduction
- There are eight core expectations placed on a software architect, irrespective of any given role, title, or job description:
- Make architecture decisions
- Continually analyze the architecture
- Keep current with latest trends
- Ensure compliance with decisions
- Diverse exposure and experience
- Have business domain knowledge
- Possess interpersonal skills
- Understand and navigate politics
- Everything in software architecture is a trade-off. If an architect thinks they have discovered something that isn’t a trade-off, more likely they just haven’t identified the trade-off yet. Corollary
- Why is more important than how.
Architectural Thinking
- To make architecture work, both the physical and virtual barriers that exist between architects and developers must be broken down, thus forming a strong bidirectional relationship between architects and development teams.
- The scope of technological detail differs between developers and architects. Unlike a developer, who must have a significant amount of technical depth to perform their job, a software architect must have a significant amount of technical breadth to think like an architect and see things with an architecture point of view.
- As an architect, breadth is more important than depth. Because architects must make decisions that match capabilities to technical constraints, a broad understanding of a wide variety of solutions is valuable.
- Thinking like an architect is all about seeing trade-offs in every solution, technical or otherwise, and analyzing those trade-offs to determine what is the best solution.
- Everything in architecture is a trade-off, which is why the famous answer to every architecture question in the universe is “it depends”.
- The decision between different solutions will always depend on the business drivers, environment, and a host of other factors.
- Thinking like an architect is understanding the business drivers that are required for the success of the system and translating those requirements into architecture characteristics (such as scalability, performance, and availability).
Modularity and Cohesion
- 95% of the words [about software architecture] are spent extolling the benefits of “modularity” and that little, if anything, is said about how to achieve it.
- Modularity is an organizing principle. We use modularity to describe a logical grouping of related code, which could be a group of classes in an object-oriented language or functions in a structured or functional language.
- Cohesion refers to what extent the parts of a module should be contained within the same module. In other words, it is a measure of how related the parts are to one another.
- It can be measured by metrics such as coupling, abstractness and cyclomatic complexity.
Architecture Characteristics Defined
- An architecture characteristic meets three criteria:
- Specifies a nondomain design consideration
- Influences some structural aspect of the design
- Is critical or important to application success
- Architects may collaborate on defining the domain or business requirements, but one key responsibility entails defining, discovering, and otherwise analyzing all the things the software must do that isn’t directly related to the domain functionality: the architectural characteristics or nonfunctional requirements.
- Requirements specify what the application should do; architecture characteristics specify operational and design criteria for success, concerning how to implement the requirements and why certain choices were made.
- The primary reason architects try to describe architecture characteristics on projects concerns design considerations: does this architecture characteristic require special structural consideration to succeed?
- Applications could support a huge number of architecture characteristics… but shouldn’t. Support for each architecture characteristic adds complexity to the design. Thus, a critical job for architects lies in choosing the fewest architecture characteristics rather than the most possible.
- Implicit characteristics rarely appear in requirements, yet they’re necessary for project success. For example, availability, reliability, and security underpin virtually all applications, yet they’re rarely specified in design documents. Explicit architecture characteristics appear in requirements documents or other specific instructions.
- Operational architecture characteristics cover capabilities such as performance, scalability, elasticity, availability, and reliability.
- Structural architecture characteristics concern code structure. In many cases, the architect has sole or shared responsibility for code quality concerns, such as good modularity, controlled coupling between components, readable code, and a host of other internal quality assessments.
- Never shoot for the best architecture, but rather the least worst architecture.
- An architect uncovers architecture characteristics in at least three ways by extracting from domain concerns, requirements, and implicit domain knowledge.
- Understanding the key domain goals and domain situation allows an architect to translate those domain concerns to “-ilities,” which then forms the basis for correct and justifiable architecture decisions.
- Architects shouldn’t stress too much about discovering the exactly correct set of architecture characteristics — developers can implement functionality in a variety of ways. However, correctly identifying important structural elements may facilitate a simpler or more elegant design. Architects must remember: there is no best design in architecture, only a least worst collection of trade-offs.
- Architects must also prioritize these architecture characteristics toward trying to find the simplest required sets.
- Objective definitions for architecture characteristics solve all three problems: by agreeing organization-wide on concrete definitions for architecture characteristics, teams create a ubiquitous language around architecture. Also, by encouraging objective definitions, teams can unpack composite characteristics to uncover measurable features they can objectively define.
- The architecture characteristics can be measured and monitored with fitness functions, but the architect must ensure that developers understand the purpose of a fitness function before imposing it on them.
Scope of Architecture Characteristics
- When evaluating many operational architecture characteristics, an architect must consider dependent components outside the code base that will impact those characteristics. Thus, architects need another method to measure these kinds of dependencies.
- Connascence: two components are connascent if a change in one would require the other to be modified in order to maintain the overall correctness of the system.
- Component-level coupling isn’t the only thing that binds software together. Many business concepts semantically bind parts of the system together, creating functional cohesion.
- To successfully design, analyze, and evolve software, developers must consider all the coupling points that could break.
- Architecture quantum: an independently deployable artifact with high functional cohesion and synchronous connascence.
Component-Based Thinking
- Components offer a language-specific mechanism to group artifacts together, often nesting them to create stratification.
- Nothing requires an architect to use components — it just so happens that it’s often useful to have a higher level of modularity than the lowest level offered by the language.
- Components form the fundamental modular building block in architecture, making them a critical consideration for architects. In fact, one of the primary decisions an architect must make concerns the top-level partitioning of components in the architecture.
- Typically, the architect defines, refines, manages, and governs components within an architecture.
Foundations
- Architecture styles, sometimes called architecture patterns, describe a named relationship of components covering a variety of architecture characteristics.
- Big Ball of Mud: architects refer to the absence of any discernible architecture structure as a Big Ball of Mud.
- Client/Server: over time, various forces required partitioning away from a single system; how to do that forms the basis for many of these styles. Many architecture styles deal with how to efficiently separate parts of the system.
- Monolithic Versus Distributed Architectures: Architecture styles can be classified into two main types: monolithic (single deployment unit of all code) and distributed (multiple deployment units connected through remote access protocols).
Layered Architecture Style
- The layered architecture, also known as the n-tiered architecture style, is one of the most common architecture styles.
- The layered architecture style also falls into several architectural anti-patterns, including the architecture by implication anti-pattern and the accidental architecture anti-pattern.
- Components within the layered architecture style are organized into logical horizontal layers, with each layer performing a specific role within the application (such as presentation logic or business logic).
- Tis separation of concerns concept within the layered architecture style makes it easy to build effective roles and responsibility models within the architecture.
- The layered architecture is a technically partitioned architecture (as opposed to a domain-partitioned architecture). Groups of components, rather than being grouped by domain (such as customer), are grouped by their technical role in the architecture (such as presentation or business). As a result, any particular business domain is spread throughout all of the layers of the architecture.
- The layers of isolation concept means that changes made in one layer of the architecture generally don’t impact or affect components in other layers, providing the contracts between those layers remain unchanged.
- The layered architecture makes for a good starting point for most applications when it is not known yet exactly which architecture style will ultimately be used.
Pipeline Architecture Style
- One of the fundamental styles in software architecture that appears again and again is the pipeline architecture (also known as the pipes and filters architecture).
- The pipes and filters coordinate in a specific fashion, with pipes forming one-way communication between filters, usually in a point-to-point fashion.
- The pipeline architecture pattern appears in a variety of applications, especially tasks that facilitate simple, one-way processing.
- ETL tools (extract, transform, and load) leverage the pipeline architecture as well for the flow and modification of data from one database or data source to another.
- The pipeline architecture style is a technically partitioned architecture due to the partitioning of application logic into filter types (producer, tester, transformer, and consumer).
Microkernel Architecture Style
- The microkernel architecture style is a natural fit for product-based applications (packaged and made available for download and installation as a single, monolithic deployment, typically installed on the customer’s site as a third-party product).
- The microkernel architecture style is a relatively simple monolithic architecture consisting of two architecture components: a core system and plug-in components. Application logic is divided between independent plug-in components and the basic core system, providing extensibility, adaptability, and isolation of application features and custom processing logic.
- The core system is formally defined as the minimal functionality required to run the system.
Service-Based Architecture Style
- Service-based architecture is a hybrid of the microservices architecture style and is considered one of the most pragmatic architecture styles, mostly due to its architectural flexibility.
- The basic topology of service-based architecture follows a distributed macro layered structure consisting of a separately deployed user interface, separately deployed remote coarse-grained services, and a monolithic database.
- Services within this architecture style are typically coarse-grained “portions of an application” (usually called domain services) that are independent and separately deployed.
- Services are accessed remotely from a user interface using a remote access protocol. While REST is typically used to access services from the user interface, messaging, remote procedure call (RPC), or even SOAP could be used as well.
- One important aspect of service-based architecture is that it typically uses a centrally shared database.
- Similarly, opportunities may exist to break apart a single monolithic database into separate databases, even going as far as domain-scoped databases matching each domain service (similar to microservices). In these cases it is important to make sure the data in each separate database is not needed by another domain service. This avoids interservice communication between domain services (something to definitely avoid with service-based architecture) and also the duplication of data between databases.
- Because domain services in a service-based architecture are generally coarse-grained, each domain service is typically designed using a layered architecture style consisting of an API facade layer, a business layer, and a persistence layer. Another popular design approach is to domain partition each domain service using sub-domains similar to the modular monolith architecture style.
- Domain services, being coarse-grained, allow for better data integrity and consistency, but there is a trade-off. With service-based architecture, a change made to the order placement functionality in the OrderService would require testing the entire coarse-grained service (including payment processing), whereas with microservices the same change would only impact a small OrderPlacement service (requiring no change to the PaymentService).
- The database coupling in this architecture can present an issue with respect to database table schema changes. One way to mitigate the impact and risk of database changes is to logically partition the database and manifest the logical partitioning through federated shared libraries.
- Service-based architecture is a domain-partitioned architecture, meaning that the structure is driven by the domain rather than a technical consideration (such as presentation logic or persistence logic).
- Service-based architecture is also a natural fit when doing domain-driven design. Because services are coarse-grained and domain-scoped, each domain fits nicely into a separately deployed domain service.
Event-Driven Architecture Style
- The event-driven architecture style is a popular distributed asynchronous architecture style used to produce highly scalable and high-performance applications.
- Event-driven architecture is made up of decoupled event processing components that asynchronously receive and process events.
- There are two primary topologies within event-driven architecture: the mediator topology and the broker topology. The mediator topology is commonly used when you require control over the workflow of an event process, whereas the broker topology is used when you require a high degree of responsiveness and dynamic control over the processing of an event.
- The broker topology differs from the mediator topology in that there is no central event mediator. Rather, the message flow is distributed across the event processor components in a chain-like broadcasting fashion through a lightweight message broker. This topology is useful when you have a relatively simple event processing flow and you do not need central event orchestration and coordination.
- The mediator topology of event-driven architecture addresses some of the shortcomings of the broker topology. Central to this topology is an event mediator, which manages and controls the workflow for initiating events that require the coordination of multiple event processors. The architecture components that make up the mediator topology are an initiating event, an event queue, an event mediator, event channels, and event processors. Unlike the broker topology, event processors within the mediator topology do not advertise what they did to the rest of the system. In the mediator topology, processing occurrences such as place-order, send-email, and fulfill-order are commands (things that need to happen) as opposed to events (things that have already happened). Also, in the mediator topology, a command must be processed, whereas an event can be ignored in the broker topology.
- The choice between the broker and mediator topology essentially comes down to a trade-off between workflow control and error handling capability versus high performance and scalability.
- The main issue with asynchronous communications is error handling. While responsiveness is significantly improved, it is difficult to address error conditions, adding to the complexity of the event-driven system.
Space-Based Architecture Style
- The space-based architecture gets its name from the concept of tuple space, the technique of using multiple parallel processors communicating through shared memory. High scalability, high elasticity, and high performance are achieved by removing the central database as a synchronous constraint in the system and instead leveraging replicated in-memory data grids. Application data is kept in-memory and replicated among all the active processing units.
- When a processing unit updates data, it asynchronously sends that data to the database, usually via messaging with persistent queues. Processing units start up and shut down dynamically as user load increases and decreases, thereby addressing variable scalability. Because there is no central database involved in the standard transactional processing of the application, the database bottleneck is removed, thus providing near-infinite scalability within the application.
Microservices Architecture
- Microservices is heavily inspired by the ideas in domain-driven design (DDD), a logical design process for software projects.
- Architects expect each service to include all necessary parts to operate independently, including databases and other dependent components.
- The driving philosophy of microservices is the notion of bounded context: each service models a domain or workflow. Thus, each service includes everything necessary to operate within the application, including classes, other subcomponents, and database schemas.
- The purpose of service boundaries in microservices is to capture a domain or workflow.
- Another requirement of microservices, driven by the bounded context concept, is data isolation. Many other architecture styles use a single database for persistence. However, microservices tries to avoid all kinds of coupling, including shared schemas and databases used as integration points.
- The sidecar component handles all the operational concerns that teams benefit from coupling together. Thus, when it comes time to upgrade the monitoring tool, the shared infrastructure team can update the sidecar, and each microservices receives that new functionality.
- Once teams know that each service includes a common sidecar, they can build a service mesh, allowing unified control across the architecture for concerns like logging and monitoring. The common sidecar components connect to form a consistent operational interface across all microservices,
- Architects use service discovery as a way to build elasticity into microservices architectures. Rather than invoke a single service, a request goes through a service discovery tool, which can monitor the number and frequency of requests, as well as spin up new instances of services to handle scale or elasticity concerns.
- Architects aspire to extreme decoupling in microservices, but then often encounter the problem of how to do transactional coordination across services. Because the decoupling in the architecture encourages the same level for the databases, atomicity that was trivial in monolithic applications becomes a problem in distributed The best advice for architects who want to do transactions across services is: don’t! Fix the granularity components instead. Often, architects who build microservices architectures who then find a need to wire them together with transactions have gone too granular in their design. Transaction boundaries is one of the common indicators of service granularity.
Choosing the Appropriate Architecture Style
- Choosing an architecture style represents the culmination of analysis and thought about trade-offs for architecture characteristics, domain considerations, strategic goals, and a host of other things.
- When choosing an architectural style, an architect must take into account all the various factors that contribute to the structure for the domain design. Fundamentally, an architect designs two things: whatever domain has been specified, and all the other structural elements required to make the system a success.
Architecture Decisions
- Making architecture decisions involves gathering enough relevant information, justifying the decision, documenting the decision, and effectively communicating that decision to the right stakeholders.
- One of the most effective ways of documenting architecture decisions is through Architecture Decision Records (ADRs).
- An ADR consists of a short text file (usually one to two pages long) describing a specific architecture decision.
- The basic structure of an ADR consists of five main sections: Title, Status, Context, Decision, and Consequences. We usually add two additional sections as part of the basic structure: Compliance and Notes.
- The title of an ADR is usually numbered sequentially and contains a short phrase describing the architecture decisions. It might read: “42. Use of Asynchronous Messaging Between Order and Payment Services.” The title should be descriptive enough to remove any ambiguity about the nature and context of the decision but at the same time be short and concise.
- The status of an ADR can be marked as Proposed, Accepted, or Superseded.
- The context section of an ADR specifies the forces at play. In other words, “what situation is forcing me to make this decision?” This section of the ADR allows the architect to describe the specific situation or issue and concisely elaborate on the possible alternatives. It provides a way to document the architecture. By describing the context, the architect is also describing the architecture.
- The Decision section of the ADR contains the architecture decision, along with a full justification for the decision.
- Perhaps one of the most powerful aspects of the Decision section of ADRs is that it allows an architect to place more emphasis on the why rather than the how. Understanding why a decision was made is far more important than understanding how something works.
- Knowing why a decision was made and the corresponding justification for the decision helps people better understand the context of the problem and avoids possible mistakes through refactoring to another solution that might produce issues.
- The Consequences section of an ADR is another very powerful section. This section documents the overall impact of an architecture decision. Having to specify the impact of an architecture decision forces the architect to think about whether those impacts outweigh the benefits of the decision.
- The compliance section of an ADR is not one of the standard sections in an ADR, but it’s one we highly recommend adding. The Compliance section forces the architect to think about how the architecture decision will be measured and governed from a compliance perspective.
- The architect must decide whether the compliance check for this decision must be manual or if it can be automated using a fitness function. If it can be automated using a fitness function, the architect can then specify in this section how that fitness function would be written and whether there are any other changes to the code base are needed to measure this architecture decision for compliance.
- The notes section includes various metadata about the ADR, such as the following: Original author Approval date Approved by Superseded date Last modified date Modified by Last modification
- Architecture Decision Records can be used an an effective means to document a software architecture.
Analyzing Architecture Risk
- By continually analyzing risk, the architect can address deficiencies within the architecture and take corrective action to mitigate the risk.
- Risk Matrix: the first issue that arises when assessing architecture risk is determining whether the risk should be classified as low, medium, or high.
- The architecture risk matrix uses two dimensions to qualify risk: the overall impact of the risk and the likelihood of that risk occurring. Each dimensions has a low (1), medium (2), and high (3) rating.
- These numbers are multiplied together within each grid of the matrix, providing an objective numerical number representing that risk. Numbers 1 and 2 are considered low risk (green), numbers 3 and 4 are considered medium risk (yellow), and numbers 6 through 9 are considered high risk (red).
- When leveraging the risk matrix to qualify the risk, consider the impact dimension first and the likelihood dimension second.
- Risk Assessments: the risk matrix can be used to build what is called a risk assessment. A risk assessment is a summarized report of the overall risk of an architecture with respect to some sort of contextual and meaningful assessment criteria.
- The direction of risk can be determined by using continuous measurements through fitness functions.
- Risk storming is a collaborative exercise used to determine architectural risk within a specific dimension. Common dimensions (areas of risk) include unproven technology, performance, scalability, availability (including transitive dependencies), data loss, single points of failure, and security. While most risk storming efforts involve multiple architects, it is wise to include senior developers and tech leads as well.
Diagramming and Presenting Architecture
- When visually describing an architecture, the creator often must show different views of the architecture.
- Representational consistency is the practice of always showing the relationship between parts of an architecture, either in diagrams or presentations, before changing views.
- Building very ephemeral design artifacts early prevents architects from becoming overly attached to what they have created, an anti-pattern we named the Irrational Artifact Attachment anti-pattern.
- C4 is a diagramming technique developed by Simon Brown to address deficiencies in UML and modernize its approach. The four C’s in C4 are as follows:
- Context: represents the entire context of the system, including the roles of users and external dependencies.
- Container: the physical (and often logical) deployment boundaries and containers within the architecture. This view forms a good meeting point for operations and architects.
- Component: the component view of the system; this most neatly aligns with an architect’s view of the system.
- Class: C4 uses the same style of class diagrams from UML, which are effective, so there is no need to replace them.
- ArchiMate (an amalgam of Architecture-Animate) is an open source enterprise architecture modeling language to support the description, analysis, and visualization of architecture within and across business domains.
- Invisibility is a simple pattern where the presenter inserts a blank black slide within a presentation to refocus attention solely on the speaker. If someone has two information channels (slides and speaker) and turns one of them off (the slides), it automatically adds more emphasis to the speaker. Thus, if a presenter wants to make a point, insert a blank slide — everyone in the room will focus their attention back on the speaker because they are now the only interesting thing in the room to look at.
Negotiation and Leadership Skills
- Leverage the use of grammar and buzzwords to better understand the situation.
- Gather as much information as possible before entering into a negotiation.
- When all else fails, state things in terms of cost and time.
- Money and time (effort involved) are certainly key factors in any negotiation but should be used as a last resort so that other justifications and rationalizations that matter more be tried first.
- Leverage the “divide and conquer” rule to qualify demands or requirements.
- Always remember that demonstration defeats discussion.
- Avoid being too argumentative or letting things get too personal in a negotiation — calm leadership combined with clear and concise reasoning will always win a negotiation.
- When convincing developers to adopt an architecture decision or to do a specific task, provide a justification rather than “dictating from on high.”
- If a developer disagrees with a decision, have them arrive at the solution on their own.
- An effective way of avoiding accidental complexity is what we call the 4 C’s of architecture: communication, collaboration, clarity, and conciseness.
Developing a Career Path
- Technology breadth is more important to architects than depth. However, maintaining breadth takes time and effort, something architects should build into their day. One technique we use to maintain this balance is something we call the 20-minute rule. The idea of this technique is to devote at least 20 minutes a day to your career as an architect by learning something new or diving deeper into a specific topic.
- The point of this technique is to be able to carve out some time for developing a career as an architect and continuously gaining technical breadth.
- We strongly recommend leveraging the 20-minute rule first thing in the morning, as the day is starting.
- Doing this will increase an architect’s technical breadth and help develop the knowledge required to become an effective software architect.
- When heavily invested in a technology, a developer lives in a memetic bubble, which also serves as an echo chamber.
- Creating a technology radar helps an architect formalize their thinking about technology and balance opposing decision criteria (such as the “more cool” technology factor and being less likely to get a new job versus a huge job market but with less interesting work). Architects should treat their technology portfolio like a financial portfolio: in many ways, they are the same thing. What does a financial planner tell people about their portfolio? Diversify!
- Architects should choose some technologies and/or skills that are widely in demand and track that demand. But they might also want to try some technology gambits,
- Architects should set aside time to broaden their technology portfolio, and building a radar provides a good scaffolding.
- Practice is the proven way to build skills and become better at anything in life… including architecture.
- There are not right or wrong answers in architecture — only trade-offs.