LearningTree

Software Architecture
Patterns

A structured reference covering foundational architecture patterns: Layers, Pipes & Filters, Microservices, and Dependency Injection.

01
Chapter One

Introduction to Patterns

What Patterns Reuse

Patterns allow us to reuse "thinking" β€” applying proven solutions to problems across different domains and levels of abstraction. They are discovered, not invented, and appear in software architecture, design, physical architecture, and many other fields.

πŸ“š

Libraries

Allow us to reuse code β€” pre-built implementations that can be called from your own code.

πŸ—οΈ

Frameworks

Allow us to reuse structure β€” predefined application skeletons and control flows you fill in.

πŸ’‘

Patterns

Allow us to reuse thinking β€” applying solutions to problems across different domains and abstraction levels.

🎯

What Patterns Do

  • Describe reusable (abstract or concrete) solutions for common problems
  • Provide a recognizable structure and terminology for those solutions
🌍

Patterns Are Discovered, Not Invented

The universe is full of them. Patterns have been discovered in many fields:

  • Software architecture
  • Software design
  • Analysis
  • Physical (buildings) architecture
  • Gardening, teaching, …
🧩

Designing With Patterns

Patterns offer approaches for:

  • Decomposition of a system
  • Distribution of responsibilities within a system

Patterns complement each other:

  • Refinement and combination
Pattern Classification
πŸ“

Architectural β€” Buschmann [Bus+1996]

  • Adaptable Systems
  • Interactive Systems
  • Mud-to-Structure
  • Distributed Systems
πŸ”§

Design Patterns β€” GoF [Gam+1994]

  • Creational
  • Structural
  • Behavioral

Others: enterprise integration, enterprise architecture, microservices, concurrency, …

Must-Know Patterns for CPSA-F
ISAQB CPSA-F Required (R1, R2) β€” Most Fundamental
Layers Pattern Pipes and Filters Microservices Dependency Injection
Note: There are many more patterns (Singleton, Observer, CQRS, Event Sourcing, etc.) β€” you need to be aware of as many as possible, but the four above are considered the most fundamental for the exam.
Patterns in Practice β€” Common Problems β†’ Common Solutions
πŸ’»
Operating System
πŸ”„
IoT Vacuum Cleaner
πŸ›’
Large Scale Online Store
🏦
Banking Application
↓
Common Design & Architecture Problems
↓
Common Solutions β€” Patterns
πŸ“‹ Chapter 1 β€” Summary
  • Libraries reuse code; Frameworks reuse structure; Patterns reuse thinking
  • Patterns describe reusable (abstract or concrete) solutions for common problems
  • Patterns are discovered, not invented β€” the universe is full of them
  • Patterns offer approaches for decomposition and distribution of responsibilities
  • Patterns complement each other through refinement and combination
  • CPSA-F must-know patterns: Layers, Pipes & Filters, Microservices, Dependency Injection
02
Chapter Two

Layers Pattern

Layers Pattern β€” Overview

The Layers Pattern organizes components into stacked layers of services, where each layer encapsulates details, provides abstraction, and communicates with adjacent layers. Dependencies flow downward only β€” upper layers depend on lower layers, never the reverse.

πŸ“¦ Each Layer

  • Encapsulates details
  • May provide abstraction
  • Provides services to the layer above

πŸ”— Dependencies

  • Can depend on layers below it
  • Cannot depend on layers above it

πŸ“€ Usage

  • Usage directed from upper to lower layer

β‡… Communication

  • Communication can be directed in both ways
Layer Stack
Highest abstraction
Client
uses β†’
Layer N
β‡… communicate
Layer N-1
β‡… communicate
Layer 1
Lowest abstraction
OSI Model β€” Abstraction Layers Example
# Layer Responsibility
7ApplicationHigh-level APIs
6Presentation"Data translator", encryption, compression
5SessionManaging communication sessions
4TransportReliable transmission of data
3NetworkAddressing, routing and traffic control
2Data LinkReliable transmission of data frames
1PhysicalRX/TX physical medium

Example: ISO Open Systems Interconnection (OSI) model

Multi-Layered / N-Layered Web Architecture
Presentation Layer
User interaction Β· HTTP Requests Β· Input validation Β· HTTP responses
β‡…
Business Logic Layer
Business rules Β· Data validation Β· Access control Β· Computations
β‡…
Persistence Layer
Database / File System Β· Object-storage mapping Β· Connections
Three-Tiered Architecture
Presentation Tier
Webpage
Mobile App
β†’
Application Tier
Web App Server
UI Β· Logic Β· Data
β†’
Data Tier
Files
Database
Note: Each tier can itself contain layered architecture internally (UI β†’ Logic β†’ Data sublayers within the Application Tier, API β†’ Query Engine β†’ Logic β†’ Storage within the Database).
Example β€” Layers Within Tiers
Online Bookstore β€” Each Tier Contains Its Own Layers
πŸ‘€ User (Browser / Mobile)
↓ HTTP
Presentation Tier
UI Layer
HTML Β· CSS Β· React
↓
Controller Layer
REST endpoints Β· Input validation
β†’
Application Tier
Service Layer
Business rules Β· Orchestration
↓
Domain Layer
Entities Β· Value Objects
↓
Repository Layer
ORM Β· SQL mapping
β†’
Data Tier
API Layer
Query engine Β· Protocols
↓
Storage Layer
PostgreSQL Β· Files Β· Cache
Tiers = physical separation (deployment) Β· Layers = logical separation (within each tier)
Closed vs Open Layer Architecture
Closed / Strict Layer Architecture
Presentation Layer Closed
↓ must pass through ↓
Business Logic Layer Closed
↓ must pass through ↓
Persistence Layer Closed

Each layer can only interact with the layer directly below. No skipping.

Open Layer Architecture
Presentation Layer Closed
↓ or skip β†˜
Business Logic Layer Open
↓
Persistence Layer Closed

Higher layers may call any layer below. Open layers can be bypassed.

Layer Architecture Types β€” Trade-offs
πŸ”’

Closed/Strict Layer

Advantages

  • Modularity
  • Simplifies debugging and testing

Disadvantages

  • Potential inefficiency / performance overhead
πŸ”“

Open Layer

Advantages

  • Can bypass layers: higher efficiency / performance

Disadvantages

  • Harder to understand/maintain
  • Risk of bugs
Layers Pattern β€” Pros & Cons
βœ“ Pros
βœ— Cons
  • Layers are independent β€” distribution of tasks during development
  • Layers are independent in production β€” installation and maintenance
  • Implementations are interchangeable
  • Unidirectional dependencies (no circular dependencies)
  • The Layers pattern is easy to understand
  • Layers are overhead when they only pass information to following layers
  • Loose layering allows skipping layers (open) but increases dependency
  • Some changes (e.g., adding a data field) may require changes to all layers
πŸ“‹ Chapter 2 β€” Summary
  • Each layer encapsulates details and provides services to the layer above
  • Dependencies are unidirectional: upper layers depend on lower, never circular
  • Closed/Strict: each layer may only interact with the layer directly below
  • Open: higher layers may call any layer below, bypassing intermediate layers
  • Example: OSI 7-layer model, Web architecture (Presentation β†’ Business β†’ Persistence)
  • Pro: independent layers, easy to understand. Con: overhead, changes may ripple
03
Chapter Three

Pipes and Filters Pattern

Pattern Components

The Pipes and Filters pattern decomposes a processing task into a sequence of independent processing steps (Filters) connected by channels (Pipes). Each Filter is unaware of others, transforming or manipulating data independently, enabling simple composition of complex pipelines.

πŸ”—

Pipes

  • Transport data/messages between filters
  • Can buffer data
  • Connect one filter's output to another's input
Source
buffer
Filter
βš™οΈ

Filters

  • Processing unit, unaware of other filters
  • Transform, aggregate, or manipulate data
  • Connects to multiple input/output pipes
in β†’
βš™οΈ Filter
β†’ out
Pipes and Filters β€” Flow Diagram
Pipeline Architecture β€” Data flows left to right through independent filters
πŸ“₯ Data
Source
producer
pipe Β· buffer
βš™οΈ Filter 1 β€” Transform
unaware of others
pipe Β· buffer
βš™οΈ Filter 2 β€” Aggregate
unaware of others
pipe Β· buffer
βš™οΈ Filter 3 β€” Enrich
unaware of others
pipe Β· buffer
πŸ“€ Data
Sink
consumer
πŸ”— Pipe
Transports data Β· Can buffer Β· Connects output β†’ input
βš™οΈ Filter
Independent Β· Unaware of others Β· Transform / Aggregate / Enrich
πŸ“€ Sink
Final consumer Β· Stores or forwards the processed result
Unix/Linux Pipe Example
cat input.txt | grep 'search_term' | sort | uniq -c
cat = source | = pipe (OS buffer) grep = filter 1 sort = filter 2 uniq -c = sink

Programs/Processes connected by OS-allocated buffers as pipes

Real-World Examples
🎬
Video Encoding Pipeline
each filter runs on its own thread Β· FIFO queue as pipe
πŸŽ₯
Raw Video
FIFO
βš™οΈ Decompress
h.264 β†’ raw frames
FIFO
βš™οΈ Resize
1080p β†’ 720p
FIFO
βš™οΈ Color Correct
brightness/contrast
FIFO
βš™οΈ Watermark
overlay logo
FIFO
βš™οΈ Compress
encode h.265
FIFO
πŸ“
Output File
πŸ“Š
ETL Big Data Pipeline
feeds BI Β· dashboards Β· ML models
πŸ—„οΈ DB
☁️ Lake
πŸ“‘ API
stream
βš™οΈ Extract
read sources
normalise format
pipe
βš™οΈ Transform
clean Β· enrich
aggregate Β· join
pipe
βš™οΈ Load
write to sink
validate schema
pipe
πŸ›οΈ Data Warehouse
πŸ“ˆ BI
πŸ“Š Dash
πŸ€– ML
πŸ”„
API Data Ingestion Pipeline
each filter is independent β€” easy to add, remove, or reorder

Raw JSON from an API flows through 4 sequential filters before being stored. Each filter handles one concern only.

πŸ“¨
Raw
data in
βš™οΈ Parse
filter
JSON β†’ objects
decode bytes
detect encoding
βš™οΈ Validate
filter
schema check
required fields
type coercion
βš™οΈ Transform
filter
map fields
normalise dates
currency convert
βš™οΈ Enrich
filter
geo-lookup
add metadata
join ref data
πŸ—„οΈ
Store
data out
← pipes (buffers between each filter) β†’
πŸ’‘ Reusability: any filter can be swapped, reordered, or removed independently β€” e.g. insert a Deduplicate filter between Validate and Transform without touching the others.
Pipes and Filters β€” Pros & Cons
βœ“ Pros
βœ— Cons
  • Simple (linear) dependencies
  • Flexible β€” filters can be composed in many ways
  • Easily scalable
  • Filters can be developed independently
  • Difficult error tracking and handling
  • Buffers might overflow
  • Sharing global data might be difficult
πŸ“‹ Chapter 3 β€” Summary
  • Pipes transport data/messages between filters and can buffer data
  • Filters are processing units, unaware of other filters
  • Filters transform, aggregate, or manipulate data
  • Filters connect to multiple input/output pipes
  • Real examples: Unix pipes, GStreamer, ETL pipelines, video encoding
  • Pro: flexible, scalable. Con: error tracking is hard, buffers may overflow
04
Chapter Four

Microservices Architecture

Monolithic Architecture β€” The Problem

Microservices architecture divides large systems into small, independently deployable units. Each microservice can be developed, deployed, scaled, and replaced independently, communicating over networks. It is ideal for large organizations with complex, big codebases.

⚠️

As Codebase Grows, It Becomes Difficult To:

  • Troubleshoot
  • Add new features
  • Build
  • Test
  • Load in IDE
πŸ‘₯

Organizational Scalability Problems

  • More engineers β†’ more code merge conflicts
  • Meetings become larger, longer, less productive
  • Solution: consider migrating to Microservices
Microservices Architecture

Divide large systems into small, independently operable units.

Microservices can be individually
πŸ› οΈ Developed
πŸš€ Deployed
βš™οΈ Operated / Maintained
πŸ”„ Changed / Replaced
πŸ“ˆ Scaled
🌐 Services communicate over networks
Online Shopping Service
Client
↓ API Gateway
Customer Account Service
Customer DB
Product Catalog Service
Product DB
Shopping Cart Service
Cart KVS
Order Dispatch Service
Order DB

Each service: independently deployed Β· own database Β· communicates over network

Benefits of Smaller Codebase per Service
βœ…

Easier

  • Understand
  • Develop
⚑

Faster

  • Build
  • Test
  • Load in IDE
🎯

Simpler

  • Troubleshoot
  • Add new features
Microservices Benefits

β‘  Polyglot Technology β€” each service picks its own language and data store independently.

🌐 API Gateway
↓↓↓↓
Order Service
Java Β· Order DB
Recommend
Python Β· Redis
User Profile
C# Β· SQL Server
Notification
Go Β· Kafka
Services only share an API contract β€” internal tech stack is fully independent

β‘‘ Independent Scalability β€” scale only the service under load, not the entire system.

βœ— Monolith
All features bundled
β†’ must scale everything
πŸ’Έ wasteful
VS
βœ“ Microservices
User ServiceΓ—1
πŸ”₯ Product ServiceΓ—5
πŸ”₯ Order ServiceΓ—4
NotificationΓ—1
βœ… cost-efficient Β· fault-isolated

β‘’ Higher Organisational Velocity β€” teams own, deploy, and operate their service independently (Inverse Conway Maneuver).

Team Alpha
Build β†’ Test β†’ Deploy
πŸ“¦ Order Service
Team Beta
Build β†’ Test β†’ Deploy
πŸ›οΈ Product Service
Team Gamma
Build β†’ Test β†’ Deploy
πŸ€– Recommendation
πŸš€ All teams deploy independently β€” no coordination meetings required
Key Design Principles
🎯

Single Responsibility Principle (SRP)

Each microservice owns one business capability. For example, in an online dating service: Image Service, User Profile Service, Matching Service, Billing Service β€” each routed through an API Gateway.

API Gateway
↙ ↓ ↓ β†˜
Image
Profile
Matching
Billing
one service Β· one responsibility
πŸ—„οΈ

Separate Database Per Microservice

  • Each service owns its data store independently
  • Some data duplication (breaking DRY) is expected
  • Some performance overhead of getting data from another microservice (Cost of Information Hiding)
Order Service
↓
πŸ—„οΈ Order DB
User Service
↓
πŸ—„οΈ User DB
no shared data store Β· isolated ownership

Inverse Conway Law Maneuver: Organize teams around services (Subscription, Notification, Payments, Recommendations teams) so that team structure reflects the desired microservices architecture.

β€” Microservices Organizational Strategy
Microservices β€” Pros & Cons
βœ“ Pros
βœ— Cons
  • Improve changeability and flexibility
  • Decouple technology decisions (polyglot)
  • Multiple versions of a service can coexist
  • Good scalability β€” scale individual services
  • Fast development, short time-to-market
  • Higher organizational velocity β€” independent teams
  • All CONs of distributed computing: network latency, outages, bandwidth limitations
  • More sophisticated error handling required
  • More complex deployment/operations
  • Dependency resolution at runtime may cause hard-to-find errors

Microservices are perfect for large organizations with complex, big codebases β€” but we don't get all the benefits for free. We need to follow design principles: Single Responsibility and database per microservice.

πŸ“‹ Chapter 4 β€” Summary
  • Divide large systems into small, independently operable units
  • Each service can use a different technology stack (C#, Python, Java, Go)
  • Services communicate over networks; separate database per service
  • Benefits: smaller codebase, team autonomy, independent scaling
  • Requires Single Responsibility Principle and database-per-service
  • Con: distributed computing complexity, latency, error handling
05
Chapter Five

Dependency Injection Pattern

The Problem β€” Without Dependency Injection

Dependency Injection (DI) is the combination of Dependency Inversion Principle (DIP) and Inversion of Control (IoC). A dedicated Assembler component creates and injects concrete implementations into clients that depend on abstractions, decoupling high-level modules from low-level details.

πŸ”΄

Tight Coupling Problem

Without DI, PasswordManager directly depends on a concrete BcryptEncryptor:

Encryptor encryptor = new BcryptEncryptor();
PasswordManager pm = new PasswordManager(encryptor);

Nightmare for big classes β€” creating large sets of lower-level classes manually. Hard to swap implementations.

🟒

With DI β€” Abstract Interface

PasswordManager depends on the Encryptor interface, not a concrete class:

private final Encryptor encryptor;

@Autowired
public PasswordManager(Encryptor enc) {
  this.encryptor = enc;
}

The framework injects the correct concrete implementation at runtime.

Dependency Injection β€” Architecture Diagram
The Assembler creates & injects the concrete implementation at runtime
Client
PasswordManager
high-level module
── uses ──▢
Β«interfaceΒ»
Encryptor
abstraction
↑
injects
β–³
implements
Assembler
DiAssembler
creates at runtime
── creates ──▢
BcryptEncryptor
SHA2Encryptor
concrete implementations
DIP, IoC and DI β€” Relationships
πŸ”„

DIP β€” Dependency Inversion Principle

  • High-level modules should not depend on low-level modules
  • Both should depend on abstractions
πŸ”

IoC β€” Inversion of Control

  • Component registers itself with the framework, later called by the framework
  • Third-party libraries define control flow
  • Other applications: callbacks, scheduler, event loops, observer pattern
πŸ’‰

DI β€” Dependency Injection

  • = Dependency Inversion Principle
  • + Special application of IoC
  • Creates and injects instances for abstractions a client depends on
DI = DIP + IoC
Dependency Injection = Dependency Inversion Principle + Inversion of Control
Java + Spring Example
Configuration-based injection: The DI framework reads a property (e.g., app.encryptor.type=bcrypt or sha2) and uses reflection to inject the appropriate implementation at runtime. The application code never needs to know which concrete class is used.
application.properties
# Switch implementations without changing any Java code
app.encryptor.type=bcrypt
Encryptor.java ← abstraction
public interface Encryptor {
    String encrypt(String plainText);
}
BcryptEncryptor.java
@Component("bcrypt")
public class BcryptEncryptor
    implements Encryptor {

    @Override
    public String encrypt(String p) {
        return BCrypt.hashpw(
            p, BCrypt.gensalt());
    }
}
SHA2Encryptor.java
@Component("sha2")
public class SHA2Encryptor
    implements Encryptor {

    @Override
    public String encrypt(String p) {
        return DigestUtils
            .sha256Hex(p);
    }
}
PasswordManager.java ← client (never references a concrete class)
@Service
public class PasswordManager {

    private final Encryptor encryptor;  // depends on abstraction, not impl

    @Autowired
    public PasswordManager(
        @Qualifier("${app.encryptor.type}") Encryptor encryptor) {
        this.encryptor = encryptor;  // Spring injects at runtime
    }

    public String hashPassword(String password) {
        return encryptor.encrypt(password);  // calls Encryptor interface
    }
}
Change app.encryptor.type=sha2 in properties β†’ SHA2Encryptor is injected. Zero code change.
Dependency Injection β€” Pros & Cons
βœ“ Pros
βœ— Cons
  • Supports the open-closed principle, loose coupling, and dependency inversion
  • Manage dependencies "by configuration"
  • Vastly improves extensibility
  • Vastly improves flexibility and adaptability
  • Vastly improves testability (mock components)
  • Better readability of code
  • Hard learning curve for developers
  • Shifts complexity to DI frameworks
  • Static analysis is difficult/impossible
  • Things fail at run time (not compile time)
πŸ“‹ Chapter 5 β€” Summary
  • DIP: high-level modules should not depend on low-level modules β€” both depend on abstractions
  • IoC: third-party libraries define control flow rather than application-specific components
  • DI = DIP + IoC: assembler creates and injects implementations at runtime
  • Two injection styles: setter injection and constructor injection
  • Pro: loose coupling, extensibility, testability (mock components)
  • Con: hard learning curve, runtime failures, shifts complexity to DI framework
Summary β€” All Architecture Patterns at a Glance
01 Β· Introduction to Patterns

What & Why

  • Proven, reusable solutions to recurring design problems
  • Provide shared vocabulary for architects & developers
  • Patterns vs. Practices β€” know the difference
02 Β· Layers Pattern

Horizontal Separation

  • Organize code into horizontal layers (presentation, business, data)
  • Each layer depends only on the layer directly below
  • Promotes SoC, testability & replaceability
03 Β· Pipes & Filters

Data Transformation Pipeline

  • Filters = independent processing steps; Pipes = connectors
  • Each filter reads, transforms, and writes data
  • Easily add, remove or reorder processing steps
04 Β· Microservices

Independently Deployable Services

  • Small, autonomous services organized around business capabilities
  • Independent deployment, scaling & technology choices
  • Trade-off: operational complexity & distributed data management
05 Β· Dependency Injection

DIP + IoC in Action

  • DIP β€” both high & low-level depend on abstractions
  • IoC β€” framework controls flow; DI injects implementations
  • Setter & constructor injection; great testability