@DanLebrero.

software, simply

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:
    1. Find what parts are entangles together.
    2. Analyze how they are coupled to one another.
    3. Assess trade-offs by determining the impact of change on interdependent systems.
  • Primary reasons for breaking applications apart is:
    1. Time to market (== agility == deployability + testability + maintainability)
    2. Scalability.
    3. Availability.
    4. 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:
    1. Service Scope and Function:
      • Subjective, so don’t use alone.
    2. Code volatility:
      • If a component changes more frequently than others, consider to split it, so that deployment and testing scope is smaller.
  • 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”?

  • 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:
    1. Find what parts are entangles together.
    2. Analyze how they are coupled to one another.
    3. 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:
    1. Time to market (== agility == deployability + testability + maintainability)
    2. Scalability.
    3. Availability.
    4. Fault tolerance.

Chapter 4 - Architectural Decomposition

  • Is a codebase decomposable?
  • 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:
    1. Identify and size components:
      • Not too big, not too small == ~ 1-2 standard deviation from average.
      • Size == # statements.
      • Too big –> split.
    2. Gather common domain components:
      • To eliminate duplication.
      • Shared domain logic, not infrastructure.
      • Mostly a manual process.
    3. Flatten components:
      • Component == leaf package/namespace.
      • All code should be in a component.
      • Move shared code to its own component.
    4. 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).
    5. Create component domains:
      • Domains =~ service.
      • Package structure:
        • ss .customer.billing .payments .MonthlyBilling
        • app.domain .subdomain.component.class
      • Move components to appropriate domain.
    6. Create domain services:

Chapter 6 - Pulling Apart Operational Data

  • Evaluate data disintegrators and data integrators.
  • Disintegrators:
    1. Change control:
      • How many services are impacted by a DB change?
      • All must be deployed at the same time.
    2. Connection management:
      • Are we reaching the max number of connections allowed by the DB?
      • Are we running out of DB connections?
    3. Scalability:
      • Data is partitioned.
    4. Fault tolerance:
      • Avoid DB as a single point of failure.
    5. Increase Architecture Quantum.
    6. Need for different kinds of DBs.
  • Integrators:
    1. Data relationships:
      • Referential integrity.
    2. DB transactions.
  • Steps to split a DB:
    1. Analyze DB and create data domains:
      • Data domain: group of tables/triggers/stored procs related to one domain.
    2. Assign tabled to data domains:
      • Move tables to data domain (== DB schema).
      • Allow for cross-schema joins.
    3. Separate DB connections to data domains:
      • Disallow cross-schema joins.
      • Replace joins with service calls.
    4. Move schemas to separate DB servers.
    5. Switch over to independent DB servers:
      • Clean up old DB, do not connect to it.
  • Selecting DB type:
    1. Relational DB:
      • When not to use:
        • Graph structures with arbitrary depth.
        • Horizontal scalability required.
        • Availability (over consistency).
        • Reactive stream APIs.
    2. Key-Value DB:
      • Key -> blob.
      • Single index.
    3. Document DB:
      • Key -> JSON/XML.
      • Multiple indices.
    4. Column Family DBs.
    5. Graph DB:
      • Beginners tend to add properties to relations, while seasoned add nodes and relations.
    6. New SQL DB:
      • Scalability of NoSQL with Relational DB features like ACID.
      • CockroachDB
    7. Cloud Native DB:
      • Mix bag of DBs (including Datomic).
    8. Time-Series DB.

Support means “Programming language support, product maturity, SQ: support, community”.

Chapter 7 - Service Granularity

  • Granularity == size == # of statements + # public interfaces.
  • Granularity disintegrators:
    1. Service Scope and Function:
      • Consider cohesion and size.
      • Single responsibility principle.
      • Subjective, so don’t use alone.
    2. Code volatility:
      • If a component changes more frequently than others, consider to split it, so that deployment and testing scope is smaller.
    3. Scalability and Throughput:
      • Some component needs to scale more than others.
    4. Fault tolerance:
      • One component more likely to crash and affect other component.
    5. Security:
      • Different security requirements per component.
    6. Extensibility:
      • If a new functionality will be more appropriate as a new service.
  • Granularity integrators:
    1. DB transactions.
    2. Workflow and choreography:
      • Too many inter-service callas:
        • Less fault tolerant.
        • Less performant.
    3. Shared code (business code, not infrastructure).
    4. Data relationships.

Part II - Putting Things Back Together

Chapter 8 - Reuse Patterns

  1. Code replication:
    • Copy and paste between services.
    • Use for simple static code that is unlikely to change.
  2. 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.
  3. 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.
  4. 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:
    1. Simple ownership:
      • Only one writer.
      • Writer owns the data.
    2. Common ownership:
      • Most services write to the table.
      • Solution: create a new service that owns the data.
    3. Joint ownership:
      • Solutions:
        1. Split table:
          • May required data replication/synchronization between services on delete/create of primary entity.
        2. Data domain techniques:
          • Both services own the table, so keep as it is.
        3. Delegate techniques:
          • One service owns the data, the other makes service calls.
          • Consider owner by primary domain or by operational characteristic.
        4. Service consolidation technique:
          • Combine possible owners in a bigger service.
  • Distribute transactions, eventual consistency patterns:
    1. 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.
    2. 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.
    3. Event-based pattern:
      • Pro:
        • Decoupled services.
        • Fast data consistency.
        • Fast responsiveness.
      • Cons: complex error handling.

Chapter 10 - Distributed Data Access

  • Data access patterns:
    1. Inter-service calls.
    2. Column schema replication:
      • Keep local copy of the other service data.
    3. 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?
    4. Data domain pattern:
      • Share DB, same as joint ownership.

Chapter 11 - Managing Distributed Workflow

  1. Orchestration communication style:
    • Aka mediator.
    • In microservices, one orchestrator per workflow.
    • Responsibilities:
      • Workflow state.
      • Optional behaviour.
      • Error handling.
      • Notification.
  2. Choreography communication style:
    • Workflow state management patterns:
      1. Front controller pattern:
        • First called service owns the state.
        • Other services may query and update the state.
      2. Stateless choreography:
        • Query individual services to know the state of the workflow.
      3. Stamp coupling:
        • Add workflow state in the message between services.
        • Each service updates its part.
  • 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.
  • 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.
  • Loose contracts:
    • Pros:
      • Highly decoupled.
      • Easier to evolve.
    • Cons:
      • Contract management.
      • Requires fitness functions.
  • 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

  1. Data Warehouse:
    • Transform data at ingestion.
    • Brittle integration.
    • Usually failed to deliver.
    • Technical partition.
  2. Data Lake:
    • Transform data at usage time.
    • Difficult to discover proper assets.
    • PII and sensitive info issues.
    • Technical partitioning.
  3. Data Mesh:
    • Like microservices but for analytical data.
    • Principles:
      1. Domain ownership of data:
        • Distributed and shared in a peer-to-peer fashion.
      2. Data as a product:
        • Data product quantum:
          • Adjacent and coupled to microservices.
          • Always async communication.
      3. Self-service platform:
        • Oriented to sharing and consuming data.
      4. Computational federated governance:
        • Policies automated and embedded as sidecars.

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.

Did you enjoy it? or share!

Tagged in : Architecture book notes