Book notes: Software Architecture: The Hard Parts
Book notes on "Software Architecture: The Hard Parts" by Neal Ford, Mark Richards, Pramod Sadalage and Zhamak Dehghani
						These are my notes on Software Architecture: The Hard Parts by Neal Ford, Mark Richards, Pramod Sadalage and Zhamak Dehghani.
A book full trade-off tables. The dream of any MinMax RPG player.
Key Insights
- Timeless skill: How architects make decisions, and how to objectively weight trade-offs.
 - SW architecture is in the service of data.
 - Trade-off analysis:
  
- Find what parts are entangles together.
 - Analyze how they are coupled to one another.
 - Assess trade-offs by determining the impact of change on interdependent systems.
 
 - Primary reasons for breaking applications apart is:
  
- Time to market (== agility == deployability + testability + maintainability)
 - Scalability.
 - Availability.
 - Fault tolerance.
 
 - Tactical forking: Copy the entire monolith, and delete the code not needed.
 - Coupling is the most significant factor in determining the overall success and feasibility of breaking a monolith.
 - Size components:
  
- Not too big, not too small == ~ 1-2 standard deviation from average.
 
 - Service granularity disintegrators:
  
- Service Scope and Function:
    
- Subjective, so don’t use alone.
 
 - Code volatility:
    
- If a component changes more frequently than others, consider to split it, so that deployment and testing scope is smaller.
 
 
 - Service Scope and Function:
    
 - Strive for fine-grained libraries.
 - The service that performs writes owns the data.
 - As workflow complexity goes up, the need for an orchestrator raises.
 - Tightly coupling violates one of the aspirational goals fo microservices, hence prefer loose contracts and consider using consumer-driven contracts.
 - Data Mesh:
  
- Like microservices but for analytical data.
 
 - Often, a solution has many beneficial aspects, but lacks critical capabilities that prevent success.
 - Generic solutions are rarely useful in real-world architectures without applying additional situation-specific context.
 - Use concrete use cases.
 
TOC
- Chapter 1 - What Happens When There Are No “Best Practices”?
 - Part I - Pulling Things Apart
 - Part II - Putting Things Back Together
  
- Chapter 8 - Reuse Patterns
 - Chapter 9 - Data Ownership and Distributed Transactions
 - Chapter 10 - Distributed Data Access
 - Chapter 11 - Managing Distributed Workflow
 - Chapter 12 - Transactional Sagas
 - Chapter 13 - Contracts
 - Chapter 14 - Managing Analytical Data
 - Chapter 15 - Build Your Own Trade-off Analysis
 
 
Chapter 1 - What Happens When There Are No “Best Practices”?
- Architects constantly face difficult problems that literally no one has faced before (due to context).
 - Timeless skill: How architects make decisions, and how to objectively weight trade-offs.
 - SW architecture is in the service of data.
 
Part I - Pulling Things Apart
Chapter 2 - Discerning Coupling in SW Architecture
- No best practice exist that can you apply to real-world complex system.
 - Trade-off analysis:
  
- Find what parts are entangles together.
 - Analyze how they are coupled to one another.
 - Assess trade-offs by determining the impact of change on interdependent systems.
 
 - Architecture quantum:
  
- Independent deployable.
 - High functional cohesion.
 - High static coupling.
 - Synchronous dynamic coupling.
 
 - Dynamic coupling dimensions:
  
- Communication: sync or async.
 - Consistency: atomic or eventual.
 - Coordination: orchestration or choreography.
 
 
Chapter 3 - Architectural Modularity
- SW architecture must constantly change and adapt.
 - Primary reasons for breaking applications apart is:
  
- Time to market (== agility == deployability + testability + maintainability)
 - Scalability.
 - Availability.
 - Fault tolerance.
 
 
Chapter 4 - Architectural Decomposition
- Is a codebase decomposable?
  
- Architect to decide. Tools:
 
 - Component-based decomposition:
  
- Enable migration to service-oriented architecture.
 
 - Tactical forking:
  
- When there is little internal structure.
 - Copy the entire monolith, and delete the code not needed.
 
 
Chapter 5 - Component-Based Decomposition Patterns
- Initially, apply in order:
  
- Identify and size components:
    
- Not too big, not too small == ~ 1-2 standard deviation from average.
 - Size == # statements.
 - Too big –> split.
 
 - Gather common domain components:
    
- To eliminate duplication.
 - Shared domain logic, not infrastructure.
 - Mostly a manual process.
 
 - Flatten components:
    
- Component == leaf package/namespace.
 - All code should be in a component.
 - Move shared code to its own component.
 
 - Determine component dependencies:
    
- Coupling is the most significant factor in determining the overall success and feasibility of breaking a monolith.
 - Both afferent and efferent coupling.
 - Consider refactor to reduce coupling (like splitting a component in two).
 
 - Create component domains:
    
- Domains =~ service.
 - Package structure:
      
ss .customer.billing .payments .MonthlyBillingapp.domain .subdomain.component.class
 - Move components to appropriate domain.
 
 - Create domain services:
    
- Service-based architecture.
 - Don’t apply this pattern until all domains have been identified and refactored.
 
 
 - Identify and size components:
    
 
Chapter 6 - Pulling Apart Operational Data
- Evaluate data disintegrators and data integrators.
 - Disintegrators:
  
- Change control:
    
- How many services are impacted by a DB change?
 - All must be deployed at the same time.
 
 - Connection management:
    
- Are we reaching the max number of connections allowed by the DB?
 - Are we running out of DB connections?
 
 - Scalability:
    
- Data is partitioned.
 
 - Fault tolerance:
    
- Avoid DB as a single point of failure.
 
 - Increase Architecture Quantum.
 - Need for different kinds of DBs.
 
 - Change control:
    
 - Integrators:
  
- Data relationships:
    
- Referential integrity.
 
 - DB transactions.
 
 - Data relationships:
    
 - Steps to split a DB:
  
- Analyze DB and create data domains:
    
- Data domain: group of tables/triggers/stored procs related to one domain.
 
 - Assign tabled to data domains:
    
- Move tables to data domain (== DB schema).
 - Allow for cross-schema joins.
 
 - Separate DB connections to data domains:
    
- Disallow cross-schema joins.
 - Replace joins with service calls.
 
 - Move schemas to separate DB servers.
 - Switch over to independent DB servers:
    
- Clean up old DB, do not connect to it.
 
 
 - Analyze DB and create data domains:
    
 - Selecting DB type:
  
- Relational DB:
    
- When not to use:
      
- Graph structures with arbitrary depth.
 - Horizontal scalability required.
 - Availability (over consistency).
 - Reactive stream APIs.
 
 
 - When not to use:
      
 - Key-Value DB:
    
- Key -> blob.
 - Single index.
 
 - Document DB:
    
- Key -> JSON/XML.
 - Multiple indices.
 
 - Column Family DBs.
 - Graph DB:
    
- Beginners tend to add properties to relations, while seasoned add nodes and relations.
 
 - New SQL DB:
    
- Scalability of NoSQL with Relational DB features like ACID.
 - CockroachDB
 
 - Cloud Native DB:
    
- Mix bag of DBs (including Datomic).
 
 - Time-Series DB.
 
 - Relational DB:
    
 
Support means “Programming language support, product maturity, SQ: support, community”.
Chapter 7 - Service Granularity
- Granularity == size == # of statements + # public interfaces.
 - Granularity disintegrators:
  
- Service Scope and Function:
    
- Consider cohesion and size.
 - Single responsibility principle.
 - Subjective, so don’t use alone.
 
 - Code volatility:
    
- If a component changes more frequently than others, consider to split it, so that deployment and testing scope is smaller.
 
 - Scalability and Throughput:
    
- Some component needs to scale more than others.
 
 - Fault tolerance:
    
- One component more likely to crash and affect other component.
 
 - Security:
    
- Different security requirements per component.
 
 - Extensibility:
    
- If a new functionality will be more appropriate as a new service.
 
 
 - Service Scope and Function:
    
 - Granularity integrators:
  
- DB transactions.
 - Workflow and choreography:
    
- Too many inter-service callas:
      
- Less fault tolerant.
 - Less performant.
 
 
 - Too many inter-service callas:
      
 - Shared code (business code, not infrastructure).
 - Data relationships.
 
 
Part II - Putting Things Back Together
Chapter 8 - Reuse Patterns
- Code replication:
  
- Copy and paste between services.
 - Use for simple static code that is unlikely to change.
 
 - Shared library:
  
- Trade-off between dependency management and change control.
 - In general, strive for fine-grained libraries.
 - Use in homogeneous environments where shared code change is low to moderate.
 
 - Shared service:
  
- Easier to deploy changes, but:
    
- Less performance, scalable, fault tolerant.
 - Versioning can be more difficult.
 
 - Good in:
    
- Polyglot environments.
 - High number of changes in shared functionality.
 
 
 - Easier to deploy changes, but:
    
 - Sidecars and service mesh:
  
- Mostly for cross-cutting operational concerns.
 
 
- Reuse is derived via abstraction but operationalized by slow rate of change.
 
Chapter 9 - Data Ownership and Distributed Transactions
- In general, the service that performs writes owns the data.
 - Scenarios:
  
- Simple ownership:
    
- Only one writer.
 - Writer owns the data.
 
 - Common ownership:
    
- Most services write to the table.
 - Solution: create a new service that owns the data.
 
 - Joint ownership:
    
- Solutions:
      
- Split table:
        
- May required data replication/synchronization between services on delete/create of primary entity.
 
 - Data domain techniques:
        
- Both services own the table, so keep as it is.
 
 - Delegate techniques:
        
- One service owns the data, the other makes service calls.
 - Consider owner by primary domain or by operational characteristic.
 
 - Service consolidation technique:
        
- Combine possible owners in a bigger service.
 
 
 - Split table:
        
 
 - Solutions:
      
 
 - Simple ownership:
    
 - Distribute transactions, eventual consistency patterns:
  
- Background synchronization pattern:
    
- Separate external process or service that periodically check data sources and keeps them in sync.
 - Pro: no inter-service communication.
 - Cons:
      
- Slow eventual consistency.
 - High coupling between sync process and all other processes.
 
 - Complex implementation.
 
 - Orchestrated Request-Based Pattern:
    
- One process/service in charge of making synchronous requests to other services.
 - Prefer a dedicated orchestrator.
 - Pro:
      
- Services still decoupled, if orchestrator is standalone.
 - Favours consistency over availability.
 - Atomic business requests.
 
 - Cons:
      
- Slower responsiveness.
 - Complex error handling.
 - Usually requires compensating transactions.
 
 
 - Event-based pattern:
    
- Pro:
      
- Decoupled services.
 - Fast data consistency.
 - Fast responsiveness.
 
 - Cons: complex error handling.
 
 - Pro:
      
 
 - Background synchronization pattern:
    
 
Chapter 10 - Distributed Data Access
- Data access patterns:
  
- Inter-service calls.
 - Column schema replication:
    
- Keep local copy of the other service data.
 
 - Duplicated caching pattern:
    
- Same as (2) but in-memory and using some product (Hazelcast, Ignite).
 - Pro: data remains consistent and ownership is preserved.
      
- How much I disagree on this?
 
 
 - Data domain pattern:
    
- Share DB, same as joint ownership.
 
 
 
Chapter 11 - Managing Distributed Workflow
- Orchestration communication style:
  
- Aka mediator.
 - In microservices, one orchestrator per workflow.
 - Responsibilities:
    
- Workflow state.
 - Optional behaviour.
 - Error handling.
 - Notification.
 
 
 - Choreography communication style:
  
- Workflow state management patterns:
    
- Front controller pattern:
      
- First called service owns the state.
 - Other services may query and update the state.
 
 - Stateless choreography:
      
- Query individual services to know the state of the workflow.
 
 - Stamp coupling:
      
- Add workflow state in the message between services.
 - Each service updates its part.
 
 
 - Front controller pattern:
      
 
 - Workflow state management patterns:
    
 
- As workflow complexity goes up, the need for an orchestrator raises.
 
Chapter 12 - Transactional Sagas
Types:
- Epic saga:
    
- “Traditional”.
 - Avoid.
 
 - Phone Tag:
    
- Like Epic but without a coordinator.
 
 - Fairy Tale:
    
- As Epic but without distributed transactions.
 
 - Fantasy Fiction:
    
- To improve the Epic saga performance, but it fails.
 
 - Horror Story:
    
- Building atomicity on top of async + no mediator.
 
 
- Epic saga:
    
 R/A stands for Responsiveness/Availability.
- S/E stands for Scale/Elasticity.
 
| Saga | Communication | Consistency | Coordination | Coupling | Complexity | R/A | S/E | 
|---|---|---|---|---|---|---|---|
| Epic | Sync | Atomic | Orchestrated | Very high | Low | Low | Very low | 
| Phone Tag | Sync | Atomic | Choreographed | High | High | Low | Low | 
| Fairy Tale | Sync | Eventual | Orchestrated | High | Very low | Medium | High | 
| Time Travel | Sync | Eventual | Choreographed | Medium | Low | Medium | High | 
| Fantasy Fiction | Async | Atomic | Orchestrated | High | High | Low | Low | 
| Horror Story | Async | Atomic | Choreographed | Medium | Very high | Low | Medium | 
| Parallel | Async | Eventual | Orchestrated | Low | Low | High | High | 
| Anthology | Async | Eventual | Choreographed | Very low | High | High | Very high | 
- Consider state machines (instead of atomic distributed transactions) to know the current state of a transactional saga.
 
Chapter 13 - Contracts
- Anti-pattern: include in contract more information than needed.
 - Strict contracts:
  
- Pros:
    
- Guaranteed contract fidelity.
 - Versioned.
 - Easier to verify at build time.
 - Better documentation.
 
 - Cons:
    
- Tight coupling.
 - Versioned.
 
 
 - Pros:
    
 - Loose contracts:
  
- Pros:
    
- Highly decoupled.
 - Easier to evolve.
 
 - Cons:
    
- Contract management.
 - Requires fitness functions.
 
 
 - Pros:
    
 - Tightly coupling violates one of the aspirational goals fo microservices, hence prefer loose contracts and consider using consumer-driven contracts.
 
Chapter 14 - Managing Analytical Data
- Data Warehouse:
  
- Transform data at ingestion.
 - Brittle integration.
 - Usually failed to deliver.
 - Technical partition.
 
 - Data Lake:
  
- Transform data at usage time.
 - Difficult to discover proper assets.
 - PII and sensitive info issues.
 - Technical partitioning.
 
 - Data Mesh:
  
- Like microservices but for analytical data.
 - Principles:
    
- Domain ownership of data:
      
- Distributed and shared in a peer-to-peer fashion.
 
 - Data as a product:
      
- Data product quantum:
        
- Adjacent and coupled to microservices.
 - Always async communication.
 
 
 - Data product quantum:
        
 - Self-service platform:
      
- Oriented to sharing and consuming data.
 
 - Computational federated governance:
      
- Policies automated and embedded as sidecars.
 
 
 - Domain ownership of data:
      
 
 
Chapter 15 - Build Your Own Trade-off Analysis
- Often, a solution has many beneficial aspects, but lacks critical capabilities that prevent success.
 - Generic solutions are rarely useful in real-world architectures without applying additional situation-specific context.
 - Use concrete use cases.
 - Reduce trade-off analysis to a few key points:
  
- Translate them to non-technical parlance for non-tech people.