It’s been a year since I read this book that sparked my interest in software architecture, and only now I am finally ready to share my opinion on it. Maybe I’m not objective, since it was my journey’s beginning, but I cannot see any reason why not to read this book if you are a software developer. In the post, you will find my review and my reading notes.
Table of Contents
Review
🟢🟢🟢🟢🟢: 5 out of 5
It provides a very focused and dense view of Software Architecture from the high-level technical side. Software requirements, soft skills, and the design process are not here. It is all about the code and how to work with it. And I enjoyed this focus very much.
Reading Notes
WHAT IS ARCHITECTURE?
- ARCHITECTURE = DESIGN, they are the same
- The goal: is to minimize the human resources required to build and maintain the system.
- Making a mess is slower than cleaning.
- Devs should spend time cleaning the mess.
Who is an architect?
- An architect is a programmer
- His main goal is to keep as many options possible as long as possible
- They care about:
- Simplicity
- Deployment
- Development
- Maintainability
Behavior vs Structure
Function or architecture?
- If you have a working program that cannot be changed
- it will not be useful when the requirements change.
- Opposite
- the program will be useful for any requirements
⚠️ Changing the behavior of the SW should be easy! Because it is SOFT.
- 💡 Notes
- Each new request with time is more difficult to fit into the system
- A lot of systems are impossible to change because the cost of the change is less than the benefit
- Architecture should be as shape-agnostic as possible!
Programming Paradigms
- There are 3 paradigms:
- Structural – separate data of functions
- OO – Classes, Encapsulation, polymorphism
- Functional – all data is const, no obj
Paradigms – Architectural benefits
- Structural – forbids
goto
that makes code not decomposable into testable pieces, only control structures (if, while, etc.) - OO – plugin architecture, dependency inversion, control over deps
- Functional – eliminates concurrency as we don’t update variables, only store transactions
Design Principles
Code
Solid Principles:
- Single responsibility
- Open/closed
- Liskov substitution
- Interface segregation
- Dependency inversion
SOLID Principles
- Single responsibility (SRP)
- one module, one reason to change, one actor requiring the change
- Open/closed (OCP)
- extension through adding new code, rather than modification of the old one
- Liskov substitution (LSP)
- The subtype of a rectangle cannot be a square. Setting a side for a square behaves differently, setting both sides altogether.
- Interface segregation (ISP)
- Avoid depending on an interface that is not used.
- Dependency inversion (DIP)
- No direct dependencies. Dependencies should be only on abstractions. We should rely on interfaces rather than on implementations.
Components
- Components are units for deployment
- Components can be linked together into a single executable
- Components should be independently deployable and developable
Component Cohesion
Which class belongs to which component?
- Reuse-Release Equivalence
- Functions and classes should be shaped in collections under specific topics
- Related things should be released under common release, under a new version number with meaningful update change log
- Common Closure
- Gather functions and classes that should be changed together for the same reason in a single place. Separate parts that can be changed by a different reason in a different time
- Common Reuse
- Don’t force users to depend on the components they don’t need
Component Coupling
- Acyclic dependencies principle
- Top-Down Design
- Stable dependency principle
- Stable abstractions principle
Component Coupling. Acyclic dependencies principle
- Componentization solves the integration
- must avoid circular dependencies!
Component Coupling. Top-Down Design
- Structures like this cannot be done in the top-down direction.
- Components should be formed during class development and reflect buildability and maintainability
Component Coupling. Stable dependency principle
- Depend – in the direction of stability
- A stable component – is one with many incoming deps.
- The incoming dependencies should be bigger than the outgoing ones!
- If necessary, we must create common stable components with no outcoming deps
Component Coupling. Stable abstractions principle
- The component is as abstract as it is stable
- Dependencies run toward abstraction
- The more dependencies on the class the more abstract it is
Architecture
Policies and Details
- The software can be always decomposed into:
- Policies – behavior
- Details – assets to achieve the behavior: lbs, devices, etc.
- Policies should be separated from the details
Independence
- Components should be independent
- A use case is a vertical slice of the application and another way of dividing it
- Development should be independent for each component
- It should be possible to swap layers and use cases in running systems
- Duplications
- It is bad, generally
- For vertical slices, it may be desirable to keep use cases/layers not coupled
Screaming Architecture
- The architecture supports use cases
- A. should scream about the use case
- Frameworks should be delayed, they are options.
- Having separated business rules of the details should allow to unit test all the rules without details!!!!
Clean Architecture
- Architecture does not depend on frameworks or libraries
- It is testable
- Independent on the UI, database, etc.
- Layers:
- Entities
- Use cases – app business rules
- Gateways, controllers, presenters – interface adapters
- Devices, Databases, Web, UI, external interfaces
Boundaries
- Drawing boundaries allows components, systems, and layers not to know about each other
- A good architecture allows decisions about details to be made at the latest possible time.
- We want the asymmetric relationship between components – one of them cannot be affected by another, while the second one is fully dependent
Partial boundaries
- It is when several components are compiled as a single component.
- Facade – it encapsulates some classes from the user
Layers and Boundaries
- With the growth of the project, the amount of boundaries increases.
- At some point, we can come to the micro-services
- Some boundaries can be very expensive to implement. It is up to us to decide what to do
- If some boundaries are ignored, it can be VERY expensive to implement them later
- Over-engineering is as bad as under-engineering
Services
- It is popular because services are very decoupled from each other
- And it is not 100% true because they are strongly coupled by the data interpretation
- Services should not define the architectural boundaries
Test Boundaries
- Tests are part of the system!
- They are the outermost circle of the system
- Fragile Test Problem.
- Tests should depend on non-volatile components
- Tests should not have structural coupling (e.g. to the code file structure)
Test Boundaries: Humble Objects
- Humble objects pattern – objects with hard-to-test behavior
- Testability is an attribute of good architecture
The main component
- Main is a plugin that sets the initial conditions and then hands over the control
- You can have many mains for many configs, dev, production, etc.
- The Main is the dirtiest component!
Clean Embedded Architecture
- The software does not wear out, and firmware and hardware become obsolete
- The Firmware – is what depends on the hardware
- The software does not depend on hardware!
- Embedded Software is testable!
- Target-Hardware bottleneck – the code cannot be tested without specific HW
- HAL can help solve the problem of conditional compilation with many
#ifdef BOOARD_X
- Three layers
- A. Software
- Opt: OSAL – isolates OS
- Opt: OS
- Opt: HAL – expresses services needed by the app
- B. Firmware
- C. Hardware
Approaches Overview
Layered architecture
- Good for fast development.
- Problematic for scale
- It does not scream anything about the use case
Feature Packages
- It is a vertical slice
- Each package named represents a concept/feature
- It screams about the purpose of the code
Ports and Adapters
- Split between a domain and an infrastructure
- Inside – contains all business logic
- Outside (depends on Inside) – interaction with the world
Package by Component
- A component – a grouping of related functionality behind a nice clean interface, which resides inside an execution environment like an application.
- You can group entities in the components in a way, so you cannot skip a layer