Overview
This section provides a code-first approach to learning software architecture through heavily annotated examples in Java, Kotlin, C#, and TypeScript.
What You Will Learn
The examples in this section cover patterns, principles, architectural styles, trade-offs, and real-world architectural decisions across three progressive levels:
- Beginner: Foundational architectural concepts with simple, self-contained examples
- Intermediate: Composite patterns and common enterprise architecture challenges
- Advanced: Complex systems, distributed architecture, and nuanced trade-off analysis
How to Use This Section
Each example is self-contained and annotated to explain not just what the code does, but why each architectural decision was made. Start at the level that matches your current understanding and progress through the examples in order. Each OOP example shares the same number as its FP counterpart in the in-fp-by-example sibling tutorial, enabling cross-paradigm comparison.
Paradigm-Fit Legend
Not every architectural pattern fits every paradigm equally. Many examples carry a Paradigm Note banner explaining whether the pattern is FP-native, OOP-native, or paradigm-neutral. The classification follows authoritative sources:
- NEUTRAL — paradigm-agnostic concept (microservices, distributed tracing, hexagonal architecture). Both tracks teach legitimately.
- OOP-NATIVE — pattern emerged from OOP (GoF, DDD, PEAA). Norvig (1996, Design Patterns in Dynamic Languages) classified 16 of 23 GoF patterns as absorbed by FP language features; the OOP track shows the canonical class-based form.
- OOP-NATIVE-BUT-TRANSFERABLE — OOP roots but the concept transfers cleanly to FP. The paradigm note explains the FP encoding.
- FP-NATIVE — pattern emerged from or expresses most naturally in FP (Railway-Oriented Programming, Free Monads, Reader/State monads, Event Sourcing fold, FRP, Kleisli composition). OOP encodings approximate these via libraries (Rx for FRP, monad-like result types for ROP); see Examples 86–90 for stub pointers to the FP track.
- Examples 91–93 are OOP-native extras (Active Record, GRASP, Singleton-with-FP-counterexample) — the FP track carries only stubs for these.
Authority basis: Norvig 1996; Seemann (Design patterns across paradigms, 2012; SOLID: the next step is Functional, 2014); Wlaschin (Domain Modeling Made Functional); Hickey (Simple Made Easy); Evans (Domain-Driven Design); Fowler (PEAA).
Where Go Fits (Partial)
Go has interface satisfaction without inheritance — methods on structs, structural typing, no class hierarchies. Rob Pike (Google SPLASH 2012 keynote: "Go at Google: Language Design in the Service of Software Engineering") explicitly rejected type hierarchies: "Type hierarchies result in brittle code... Go therefore encourages composition over inheritance, using simple, often one-method interfaces." That makes Go a partial fit for this OOP track:
- Translates one-to-one to Go: SOLID (especially ISP — Go interfaces are tiny by convention), composition over inheritance, dependency injection via constructor functions, Adapter pattern (concrete type satisfies a
interface), Decorator pattern (embedding), Strategy / Command pattern (function values or interface values). - Does NOT translate to Go: LSP (no subtype hierarchies to substitute against), Template Method (no abstract methods + inheritance), GoF "OOP-NATIVE" patterns that hinge on inheritance hierarchies (Visitor with double dispatch, Bridge, Abstract Factory family hierarchies).
- Honest path: read this track for patterns that translate, then see in-procedural-by-example for patterns where composition + structural typing + explicit data flow are the design force — that track teaches what only Go and Rust teach (no inheritance assumed; ownership / structural typing as primary mechanism).
Structure of Each Example
Every example follows a consistent five-part format:
- Brief Explanation — what the pattern or principle addresses and why it matters (2-3 sentences)
- Mermaid Diagram — visual representation of component relationships, layers, or data flow (when appropriate)
- Heavily Annotated Code — Java (canonical), Kotlin, C#, and TypeScript implementations with
// =>comments documenting architectural decisions and trade-offs; language tabs appear where the idiom differs meaningfully - Key Takeaway — the core insight to retain from the example (1-2 sentences)
- Why It Matters — production relevance and real-world impact (50-100 words)
Examples by Level
Beginner (Examples 1–28)
- Example 1: No Separation vs. Clear Separation
- Example 2: Single Responsibility Principle
- Example 3: Three-Layer Architecture
- Example 4: Presentation Layer Isolation
- Example 5: Model-View-Controller Basics
- Example 6: Model Encapsulates Validation
- Example 7: Manual Dependency Injection
- Example 8: Constructor Injection vs. Method Injection
- Example 9: Interface Segregation Principle
- Example 10: Open for Extension, Closed for Modification
- Example 11: Subtypes Must Be Substitutable
- Example 12: DRY — Don't Repeat Yourself
- Example 13: KISS — Keep It Simple, Stupid
- Example 14: YAGNI — You Aren't Gonna Need It
- Example 15: High Coupling — The Problem
- Example 16: Low Coupling Through Encapsulation
- Example 17: Cohesion — Grouping Related Behavior
- Example 18: Encapsulation with Private State
- Example 19: Preferring Composition
- Example 20: Mixin vs. Composition
- Example 21: Repository Pattern Basics
- Example 22: Repository with Query Methods
- Example 23: Service Layer Coordinates Use Cases
- Example 24: Service Layer with Error Handling
- Example 25: Data Transfer Objects
- Example 26: DTO Validation
- Example 27: Small Layered Application
- Example 28: Recognizing Architecture Smells
Intermediate (Examples 29–57)
- Example 29: Hexagonal Architecture — Ports and Adapters
- Example 30: Clean Architecture — Layer Separation with Dependency Rule
- Example 31: Onion Architecture — Domain at the Center
- Example 32: Observer Pattern — Event Notification Without Coupling
- Example 33: Domain Events — Signaling State Changes Within a Bounded Context
- Example 34: Event-Driven Architecture — Async Message Passing Between Services
- Example 35: Strategy Pattern — Swappable Algorithms
- Example 36: Factory Pattern — Centralized Object Creation
- Example 37: Builder Pattern — Constructing Complex Objects Step by Step
- Example 38: Adapter Pattern — Bridging Incompatible Interfaces
- Example 39: Decorator Pattern — Adding Behavior Without Subclassing
- Example 40: Facade Pattern — Simplified Interface to a Subsystem
- Example 41: Command Pattern — Encapsulate Actions as Objects
- Example 42: Mediator Pattern — Centralized Component Coordination
- Example 43: State Pattern — Objects That Change Behavior Based on State
- Example 44: Template Method — Define Algorithm Skeleton, Defer Steps to Subclasses
- Example 45: Value Objects — Immutable Domain Concepts Without Identity
- Example 46: Aggregate Roots — Consistency Boundaries in DDD
- Example 47: Bounded Contexts — Separating Domain Models by Responsibility
- Example 48: Anti-Corruption Layer — Protecting the Domain from External Models
- Example 49: CQRS Pattern — Separate Read and Write Models
- Example 50: Middleware Pattern — Processing Pipeline for Cross-Cutting Concerns
- Example 51: Plugin Architecture — Extending Systems Without Modifying Core
- Example 52: Repository Pattern — Abstracting Data Access
- Example 53: Unit of Work Pattern — Grouping Operations into Atomic Transactions
- Example 54: Specification Pattern — Composable Business Rules
- Example 55: CQRS with Event Sourcing — State as a Sequence of Events
- Example 56: Saga Pattern — Managing Distributed Transactions
- Example 57: Circuit Breaker Pattern — Preventing Cascade Failures
Advanced (Examples 58–85)
- Example 58: Microservices Decomposition by Business Capability
- Example 59: Strangler Fig Pattern
- Example 60: Saga Orchestration
- Example 61: Saga Choreography
- Example 62: API Versioning Strategies
- Example 63: Backend for Frontend (BFF) Pattern
- Example 64: Circuit Breaker with Fallback
- Example 65: Bulkhead Pattern
- Example 66: Retry with Exponential Backoff and Jitter
- Example 67: Distributed Tracing Architecture
- Example 68: Sidecar Pattern
- Example 69: Ambassador Pattern
- Example 70: Event Sourcing Implementation
- Example 71: Modular Monolith
- Example 72: Vertical Slice Architecture
- Example 73: Shared Kernel
- Example 74: Specification Pattern
- Example 75: Chain of Responsibility
- Example 76: Visitor Pattern in Architecture
- Example 77: Database per Service Pattern
- Example 78: Feature Toggle Architecture
- Example 79: Service Mesh Architecture
- Example 80: Interpreter Pattern for Configuration DSL
- Example 81: CQRS (Command Query Responsibility Segregation)
- Example 82: Outbox Pattern for Reliable Event Publishing
- Example 83: Anti-Corruption Layer
- Example 84: Ports and Adapters (Hexagonal Architecture)
- Example 85: Reactive Architecture with Backpressure
FP-Native Stubs (Examples 86–90)
Numbering parity with the FP track; full treatment lives there.
- Example 86: Railway-Oriented Programming (FP-Native)
- Example 87: Free Monads / Tagless Final (FP-Native)
- Example 88: Reader Monad for Dependency Injection (FP-Native)
- Example 89: Kleisli Composition for Effectful Pipelines (FP-Native)
- Example 90: State Monad for Pure Stateful Computation (FP-Native)
OOP-Native Extras (Examples 91–93)
Patterns that exist primarily in OOP because they assume mutable state, identity, and class hierarchies as first-class building blocks.
Last updated March 19, 2026