My Book Notes: Software Architecture: The Hard Parts (Part 1)

ismail bayram
9 min readNov 13, 2023

--

Book Review

Experienced authors of the book tells us how architecture decisions made with proper trade-off analysis over conversion of entangled monolithic project to microservice architecture. Throughout the book you will learn there is no silver bullet for architecture. When the actors of the project in book encounters a problem, they overhelm the problems with good communication, documentation and proper analysis. I really appreciate the authors since they’ve written this book, because I’ve learn too many things about how I can turn things to my side while everything was going bad.

Chapter 1. What Happens When There are no Best-Practises?

Don’t try to find the best design in software architecture; instead, strive for the least worst combination of trade-offs.

Fitness functions validates the structure of architecture not domain criteria; unit tests are the opposite.

Software architecture is the stuff you can’t Google answers for.

Chapter 2. Discerning Coupling in Software Architecture

To discerning coupling problems architects must analyze trade-offs;

  1. Find what parts are entangled 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 (Plural: Quanta)

An Architecture Quantum is an independent application which is deployable, cohesive. For example a well-structured microservice is an architecture quantum.

Static Coupling

Static coupling is dependency between classess, functions, databases etc.

Static coupling analyzes operational dependencies.

Dynamic Coupling

Dynamic Coupling is calling other services at the runtime.

Dynamic coupling analyzes communicational dependencies.

High Functional Cohesion

It is equal to Bounded-Context in Domain Driven Design. All parts of an architecture quantum are related to each other.

💡 Quanta number of a project is how many coupling points exist in the architecture.

High Static Coupling

It implies that the elements inside the architecture quantum are tightly wired together. For example: common contracts between APIs, shared databases.

Dynamic Quantum Coupling

Synchronous communication between 2 services might be an example for the definition.

Chapter 3. Architectural Modularity

Maintainability

Maintainability is about the ease of adding, removing or updating part of software application.

Maintainability metric which was invented by Alexander von Zitzewitz.

Testability

It is defined as the ease of testing (usually implemented through automated tests).

Deployability

It is not just about the ease of deployability, it is also about the frequency and risk of deployment. The frequency of deployment should be high.

Scalability

Scalability is affording the ability of high number of users’ traffic.

Elasticity

Elasticity is affording the ability of changing frequently number of users’ traffic

Scalability vs Elasticity

Availability / Fault Tolerance

Availability is the ability of one part of an application to be broken, but other parts of it work.

Chapter 4. Architectural Decomposition

Component-based decomposition

It is an extraction approach that applies various refactoring patterns for extracting components which is the logical building blocks of an application.

💡 When migrating a monolithic applications to microservices, moving to service based architecture should be considered since the database would be the same and the architects could focused on domains separation.

Tactical Forking

This approach involves making replicas of an application and chipping away at the unwanted parts to form services. Tactical forking can be achieved with this steps:

  1. Clone the repository
  2. Assign the repos to the responsible teams
  3. Each team should delete the unused parts that don’t break the system.

To detect what approach can be used, code base structure of the application should be inspected. If the components of the codebase are tightly coupled, tactical forking should be chosen.

Benefits of Tactical Forking

  • Teams can start working right away on their services.
  • Deleting code easier than extracting components from a chaotic codebase

Shortcomings of Tactical Forking

  • The resulting services will still contain large amount of legacy code.
  • Inconsistencies may occur between shared code and shared component files.

Chapter 5. Component-Based Decomposition Patterns

5.1 Identify and Size Components Pattern

The first step of migration from monolithic to distributed.

Component Size Percent: The amount of source code of a component ratio according to the project size.

Component Statements: The count of lines of source code in a component.

Files: Total number of source code files in a component.

For example in a 10-component project, component size percentage per component should not exceed 25%.

5.2 Gather Common Domain Components Pattern

Let’s say that we have an SMTPConnection class which is inherited from 5 different classes in 5 different components. All 5 classes are used for e-mail notification, so they should be gathered in a separated service, a shared library or a component.

💡 The coupling metric should be calculated for the newcomer block of code.

5.3 Flatten Components Pattern

Component: A collection of classes grouped within a leaf node namespace.

Root Namespace: A namespace node that has been extended by another namespace node. For example, given the namespaces ss.survey and ss.survey.templates, ss.survey would be considered a root namespace because it is extended by .templates. Root namespaces are also sometimes referred to as subdomain.

Orphaned Classes: An orphaned class is inside of root namespace.

💡 Flattening a component is removing orphaned classes. Thus source codes reside only in leaf nodes.

5.4 Determine Component Dependencies Pattern

The purpose of this pattern is to analyze the incoming and outgoing dependencies (coupling) between components. To break apart a monolithic application into a distributed one, logical coupling between components (not the functional ones, such as common utility functions) should be low.

5.5 Create Component Domains Pattern

This pattern is to logically group components together in order to make the component groups separate services.

5.6 Create Domain Services Pattern

Once components have been properly sized, flattened and grouped into domains, those domains can then be moved to separately deployed domain services aka service based architecture.

Chapter 6. Pulling Apart Operation Data

Data Distintegrators

Drivers that justify breaking apart data.

Change control

How many services are impacted by a database table change?

Connection management

Can my database handle the connections needed from multiple distributed services? Since the app services use the same database in service based architecture, the connection count quota should be assigned for each service in order to solve the connections limit exceed.

Scalability

Can the database scale to meet the demands of the services accessing it?

Fault tolerance

How many services are impacted by a database crash or maintenance downtime?

Architectural quantum

Is a single shared database forcing me into an undesirable single architecture quantum?

Database type optimization

Can I optimize my data by using multiple database types?

Data Integrators

Drivers that justify keeping data together.

Data relationships

Are there foreign keys, triggers, or views that form close relationships between the tables?

Database transactions

Is a single transactional unit of work necessary to ensure data integrity and consistency?

Decomposing Monolithic Data

Data Domain: it is a collection of coupled database artifacts such as tables, views, foreign keys and triggers.

Step 1: Analyze Database and Create Data Domains

Identify specific domain groupings within the database.

Step 2: Assign Tables to Data Domains

When tables belonging to different data domains are tightly coupled and related to one another, data domains must necessarily be combined in a broader bounded context where multiple services own a specific data domain.

Step 3: Separate Database Connections to Data Domains

Schema connections should be separated for each independent service, which has been defined the previous step.

Step 4: Move Schemas to Separate Database Servers

There are two ways to achieve that;

Backup and Restore

This requires downtime.

Replicate

This avoids downtime but it requires more work to setup the replication and manage increased connection.

Step 5: Switch Over to Independent Database Servers

Connections between service and database should be switched to the new database servers.

Selecting a Database Type

Selecting a database requires knowing characteristic features of them;

  • Ease-of-learning curve
  • Ease of data modeling
  • Scalability/throughput: Can the database scale horizontally, vertically, or both?
  • Availability/partition tolerance
  • Consistency

Relational Databases

Relational databases favor consistency over availability and partition tolerance.

PostgreSQL, MySQL

Key-Value Databases

NoSQL

Redis, Memcache, Amazon DynamoDB

Document Databases

NoSQL

MongoDB, Amazon DocumentDB

Column Family Databases

NoSQL

Apache Cassandra, Scylla

Graph Databases

NoSQL

Neo4j

NewSQL Databases

Mixed of NoSQL and traditional RDBMS features.

CockroachDB, ClustrixDB

Chapter 7. Service Granularity

Granularity refers to how fine-grained or coarse-grained your microservices are, that is, how much functionality and data they encapsulate.

Modularity refers to how well-defined and cohesive your microservices are, that is, how clearly they separate their concerns and responsibilities.

Granularity Disintegrators

Service Scope and Function

Is the service doing too many unrelated things?

Check cohesion of the service and ofcourse comply the Sinle Responsibility Principle.

Code Volatility

Are changes isolated to only one part of the service?

Check code changing frequency for each part of a service.

Scalability and Throughput

Do parts of the service need to scale differently?

Fault Tolerance

Are the errors taht cause critical functions to fail within the service?

Security

Dome some parts of the service need higher security levels than others?

Extensibility

Is the service always expanding to add new contexts?

Granularity Integrators

Database Transactions

Is an ACID transaction required between separate services?

Workflow and Choreography

Do services need to talk to one another?

Shared Code

Do services need to share code with another one?

Except logging, authentication, monitoring etc.

Data Relationships

Can services use the separated data?

Chapter 8. Reuse Patterns

Code Replication

Copying the code blocks to each service.

Tradeoffs

Shared Library

Versioned shared libraries like dll, jar or package.

Tradeoffs

Shared Service

A shared service which can be deployable itself when a change must be done without affecting other services. But a simple change in a shared service might bring down most of the services that use it. To fix this problem, api versioning can be used. But another problem raises after versioning, which is changing request endpoint versions in the services that use the shared service.

Tradeoffs

It can be used when multiple languages are used in different services.

Sidecars and Service Mesh

Inspired from Hexagonal architecture principle (ports and adapters). It is architectural equivalent to the Decorator Design Pattern. Consolidating operational coupling not domain coupling. Sidecar is a separate application running with its connected service.

It can be used when multiple languages are used in different services.

Orthogonal Coupling

Two distinct purposes that must still intersect to form a complete solution. For example monitoring is necessary but independent from domain behaviour.

Tradeoffs

--

--