This page contains my notes, thoughts and remarks of the book Fundamentals of Software Architecture: An Engineering Approach. A Comprehensive Guide to Patterns, Characteristics, and Best Practices. I’ve taken these notes as part of Scalable Capitals weekly reading group where we read technical books and discuss chapters on a weekly basis.
There’s no clear path to become a software architect.
- Industry does not have a good definition
- Role embodies a lot of different skills
- Software architecture (and engineering in general) is a moving target – definitions would need constant updates
Definition of Software Architecture
Software architecture consists of
- structure of the system
- type of system (microservice, monolith with layers, …)
- architectural characteristics
- next the functional criteria
- define success criteria of the system
- “-ilities”, e.g. availability, elasticity, performance, …
- architecture decisions
- rules for how the system should be constructed
- variance: explicit exception from the rule(s)
- design principles
- guidelines (not rules)
Eight core expectations
- Make architecture decisions
- guide instead of specify
- (Continoually) analyze the architecture
- Keep current with latest trends
- Ensure compliance with made decisions
- Diverse experience
- Business domain knowledge
- Interpersonal skills
- Able to handle politics
Architecture and Engineering
- Unknown unknowns lead to iterative architectures.
- Build architectures that survive implementation and change
- -> evolutionary architecture uses fitness functions to protect architectural characteristics
Laws of Software Architecture
- Everything in software architecture is a trade-off.
- Why is more important than how.
- -> documenting decisions (ADRs)
(2) Architectural Thinking
Four main aspects of thinking like an architect:
- Understand the difference between architecture and design
- Have a wide breadth of technical knowledge and a certain level of technical depth
- Understand, analyze and reconcile trade-offs
- Understand the importance of business drivers
Architecture Versus Design
Responsiblities of architect and development team
|Analyze business requirements||Create class diagrams for component|
|Select architectural patterns||Create user interface|
|Creating components||Develop and test|
This is challenging, if architecture is independent of design (“unidirectional design flow”). Instead, architect and development team must be on the same virtual team to facilitate bidirectional communication.
- Developers must have a significant amount of technical depth
- Architects must have a significant amount of technical breadth
- (A) Stuff you know
- (B) Stuff you know you don’t know
- (C) Stuff you don’t know you don’t know
In beginning of career, focus on (A). Be aware, that one must maintain knowledge of (A), otherwise it becomes obsolete.
Value of an architect: broad understanding of technology and when to use a particular set of technologies to solve a particular problem. Hence, as an architect,
- breadth is more important than depth
- sacrifice some technical depth in favour of technical breadth (to broaden one’s portfolio)
Transitioning into an architect role means to accept a shift in perspective. Difficultiers arise:
- trying to maintain expertise in a wide variety of areas -> not possible
- stale expertise -> information gets outdated (see above, maintaining knowledge)
Frozen Caveman Anti-Pattern. An architect, who always reverts back to their irrational concern in every acrchitectural discussion based on a single bad experience in the past => Risk assessment should be realistic.
Thinking like an architect is
- seeing trade-offs in every solution
- analyzing those trade-offs to find the best solution for a particular context (environment).
While anaylzing trade-offs, it’s important to look at both the benefits but also negatives of a solution.
(Omitting extensive example of topics vs. queues for a bidding system)
Understanding Business Drivers
An architect has to understand the business drivers and need to translate them into “-ilities” such as scalability and availablity.
Two important aspects
- business domain knowledge
- collaborative relationsships with business stakeholders
Balancing architecture and coding
Ideally, every architect should still code (to maintain technical depth, understand developer concerns, …).
Danger: Becoming the bottleneck, hence don’t take ownership of code of a critical path in a project. Instead, delegate critical functionality to full time development teams and work on some non-critical business functionality.
In addition or as an alternative, to remain hands-on at work, architect can
- do frequent PoCs with a focus on high-quality (production-grade) code
- tackle technica debt stories (also helping the development team)
- work on bug fixes
- leverage automation, i.e. create simple command line tools
- do frequent code reviews
Modularity is an organizing principle; good Modularity is an implicit architecture characteristic.
Definition: Modularity describes a logical grouping of related code.
(a measure of how related the parts are to one another)
- Functional (best)
- Coincidental (worst)
Larry Constantine: “Attempting to divide a cohesive module would only result in increased coupling and decreased readability.”
Definition: LCOM: The sum of sets of methods not shared via sharing fields.
Disadvantage: only able to find structural lack of cohesion; not possible to determine if parts fit together logically.
Definition: afferent coupling: number of incoming connections; efferent coupling: number of outgoing connections.
Definition: Abstractness is the ratio of abstract artifacts to concrete ones.
Definition: Instability is the ratio of efferent (outgoing) coupling to the sum of efferent and afferent coupling (outgoing and incoming).
Instability measures the volatility of a codebase; a high value means that the system breaks more easily; high values occur when you have more outgoing than incoming connections.
Combining abstractness and instability leads to Distance from the main sequence (
D=|A+I-1|). Ideally, classes are near the ideal main sequence line, i.e. have a healty mix of abstract and concrete classes as well as outgoing and incoming connections.
Zone of uselessness are classes with high instability and abstractness – too abstract to be useful; classes with few abstractions and few outgoing connections are in the zone of pain: too much implementation, therefore brittle and hard to maintain.
Limitations of metrics
Metrics can’t distinguish between essential and accidental complexity; nevertheless it’s helpful to have metrics to establish baselines, create awareness, etc.
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.
- Static (source-code level coupling)
- of Name (weakest)
- of Type
- of Meaning / of Convention
- of Position
- of Algorithm
- Dynamic (calls at runtime)
- of Execution
- of Timing
- of Values
- of Identity (strongest)
Static connascene is preferred, since source code analysis (tooling) allows to improve way easier.
Strength of connascene: how easy can a developer refactor a particual type of coupling? Locality of connascene: how proximal are the modules to each other in the code base. When refactoring, one has to evaluate strength and locality together.
Degree of connascene: size of its impact.
Connascene and coupling are related, see Figure 3-6 on p. 79.
… to use connascene to improve code base:
- Minimize overall connascene -> encapsulate elements
- Minimize remaining connascene which break encapsulation
- Maximize connascene inside encapsulation boundaries
- Convert strong forms of connascene into weaker ones
- The greater the distance, the weaker the used forms should be
(4) Architecture Characteristics Defined
Architecture Characteristic (abbreviated AC from now on) meets three criteria:
- specifies nondomain design consideration
- specify a) operational and b) design criteria
- are concerned about implementation and reasons
- influences structural aspect of design
- critical for application success
- choose fewest AC as possible to reduce overcall complexity
- implicit: don’t appear in requirements, nonetheless are necessary for success
Overlaps with Operations and DevOps
- Reliability / Safety
Concern about code structure (modularity, controlled coupling, readable code, …)
Note that there is no commonly agreed definition of ACs.
ISO defined some
- Performance efficiency
- Functional suitability (does not really belong in this list?)
Fun fact: (from Wikipedia): The letters ISO do not represent an acronym or initialism. The organization provides this explanation of the name: Because ‘International Organization for Standardization’ would have different acronyms in different languages (IOS in English, OIN in French), our founders decided to give it the short form ISO. ISO is derived from the Greek word isos (ίσος, meaning “equal”). Whatever the country, whatever the language, the short form of our name is always ISO.
Applications can (and should) only support a few ACs:
- every AC implies more design, structural changes and additional implementation
- ACs influence each other and increase complexity
Quote: “Never shoot for the best architecture, but rather the least worst architecture.”
Again: Architecture should be as iterative as possible.