Slide 1 of 24
Software Engineering

Software Architecture
Patterns & Principles

How experienced engineers think about structure, scale, and change

SOLID Gang of Four Microservices Cloud-Native Event-Driven

April 2026 · Prepared by Babu AI for Thota

Slide 2
Introduction

The Problem: Code That Fights Change

Every non-trivial system eventually faces the same enemy: complexity of change. Code gets modified. Requirements shift. Teams grow. Systems that weren't designed for change become archaeology — where engineers are afraid to touch anything.

Architecture patterns exist because experienced engineers have seen this before. These aren't abstract theories — they're battle-tested solutions proven across decades of production systems.

"The architecture of a system is the set of design decisions that is hardest to change."
— Martin Fowler
Slide 3
Introduction

The Pattern Landscape

Patterns exist at every scale of the system. The trick is knowing which pattern solves which problem.

Class & Component Level

SOLID, Gang of Four (Creational, Structural, Behavioral) — how to organize objects and responsibilities

Application Architecture

Layered, Hexagon, Onion, Clean, Microkernel — how to structure an application's internals

Distributed Systems

Microservices, Event-Driven, CQRS, Saga, Service Mesh — managing multi-service complexity

Deployment & Operations

Containers, Kubernetes, Blue-Green, Canary — how the system runs in production

Resilience Patterns

Circuit Breaker, Retry, Bulkhead, Rate Limiting — handling failures gracefully

Domain Modeling

Domain-Driven Design, Bounded Contexts, Aggregates — aligning code with business capabilities

Slide 4
Part I · Design Principles

The SOLID Principles

Five guidelines for writing maintainable object-oriented code — a system where each principle enables the others.

S

Single Responsibility

A module has one reason to change. The most impactful — and most violated. Violations sound like: "Manager", "Handler", "Service" in a class name.

O

Open/Closed

Open for extension, closed for modification. Extend behavior by adding new code, not by editing working code.

L

Liskov Substitution

Subtypes must be substitutable for base types. A Square can't be a Rectangle — unless callers never set width and height independently.

I

Interface Segregation

Split interfaces by client role. Don't force a class to implement methods its clients don't use.

D

Dependency Inversion

Depend on abstractions, not concretions. Pass dependencies in via constructors. Makes testing trivial.

SRP is the key

Follow SRP well and the others follow naturally. SRP violations are the #1 source of code pain.

Slide 5
Part I · Design Principles

The Daily Principles: SRP, DRY, KISS, YAGNI

Principle
DRY — Don't Repeat Yourself

Every piece of knowledge has a single authoritative representation. The test: if I change this, will I also need to change that? If yes, it's the same knowledge.

Principle
KISS — Keep It Simple, Stupid

Match complexity to the actual problem. Simple should be the default. Add complexity only when necessary — and remove it as soon as it isn't.

Principle
YAGNI — You Aren't Gonna Need It

Don't build it until you need it. Speculative complexity has an immediate cost; hypothetical future flexibility doesn't.

Principle
Law of Demeter

Only talk to immediate collaborators. The tell: train wrecks like user.getAccount().getBalance().getCurrency(). Each link is a hidden dependency.

The WET Counter

WET = Write Everything Twice
Wait for the third occurrence before abstracting. First two may have different reasons to change.

Carmack's Rule

"It is hard for less experienced developers to appreciate how rarely architecting for future requirements turns out net-positive."

Slide 6
Part II · Gang of Four Patterns

23 Patterns. Three Categories.

Published in 1994 by Gamma, Helm, Johnson, and Vlissides. Understanding them matters less for using them directly and more for recognizing when you're facing a problem they solve.

Creational (5)

Object creation complexity.

Singleton, Factory Method, Abstract Factory, Builder, Prototype

Structural (7)

Composing classes & objects into larger structures.

Adapter, Bridge, Composite, Decorator, Facade, Proxy, Flyweight

Behavioral (11)

Responsibility between objects & algorithms.

Observer, Strategy, Command, Template Method, Iterator, State, Mediator...

Patterns are vocabulary, not rules. A pattern that solves a problem elegantly in one context can be over-engineering in another.
Slide 7
Part II · Gang of Four Patterns

The Most Useful GoF Patterns

Creational
Builder

Step-by-step construction of complex objects. Immutability (product exposed only after build()). Python's @dataclass(frozen=True) with .to_builder() is a modern incarnation.

Structural
Decorator

Add behavior dynamically without changing the class. Java streams: BufferedInputStream(FileInputStream(...)). Caution: chain order matters, debugging is hard.

Behavioral
Observer

One-to-many dependency: state changes notify all dependents. Angular change detection, RxJS streams, event systems. Pitfall: memory leaks from un unregistered observers.

Behavioral
Strategy

Swap algorithms at runtime. Payment processors (card vs. PayPal vs. Bitcoin). Each strategy is independent — unlike State, where transitions are coordinated.

Structural
Facade

Unified, simplified interface to a complex subsystem. jQuery for DOM manipulation. Good facades are cohesive; bad ones are dumping grounds.

Structural
Composite

Tree structures where clients treat individual objects and compositions uniformly. File systems (files + directories). UI component hierarchies.

Behavioral
Command

Encapsulates requests as objects. Enables queuing, undo/redo, and logging. Each command = receiver + method + arguments. GUI buttons, transaction rollback.

Overused
Singleton

Justified uses are rare. It's global state in disguise. Before using it: can dependency injection with a single registration work instead? Usually yes.

Slide 8
Part III · Enterprise & Distributed Systems

When Systems Grow Beyond One Process

Once systems span multiple services, two new categories of problems emerge that single-process patterns don't address:

Consistency Without Transactions

ACID transactions don't work across service boundaries. When Service A and Service B each own their own database, you can't atomically update both. You need new patterns: Saga, Outbox, Event Sourcing.

Discovery & Communication

Services need to find each other, route requests, balance load, and handle failures across network boundaries. Service Discovery, API Gateway, Circuit Breaker become essential infrastructure.

Slide 9
Part III · Enterprise & Distributed Systems

Structural Patterns for Large Systems

Layered Architecture

UI → Business Logic → Data Access. The default starting point. Works when clear layer separation is needed. Fails when cross-cutting changes touch multiple layers or teams split by layer (anti-pattern).

Pipeline / Pipes & Filters

Unix philosophy: cat | grep | sort | uniq. Each filter is independent, testable, composable. ETL pipelines, compilers, stream processing. Poor fit for branching logic or heavy cross-stage state.

Microkernel / Plugin

Minimal core + plugins for everything else. Operating systems, VS Code (every feature is a plugin). Best when extensibility is core to the product and different clients need different features.

Slide 10
Part III · Enterprise & Distributed Systems

Domain-Centric: Hexagon, Onion, Clean

Three patterns, one philosophy: domain logic at the center, completely isolated from infrastructure. They differ mainly in metaphor and terminology.

Hexagon (Ports & Adapters)

Driving ports (incoming: API, UI) and driven ports (outgoing: DB, external APIs) at edges. Adapters translate. Domain has zero framework dependencies. Coined by Alistair Cockburn.

Onion Architecture

Concentric circles radiating from domain core. Domain entities & services at center → Application services → Infrastructure at outer edge. Dependencies point inward only.

Clean Architecture

Uncle Bob's 4-layer ring: Entities → Use Cases → Interface Adapters → Frameworks & Drivers. The Dependency Rule is absolute. Most explicitly structured of the three.

The core insight: If your domain logic imports from your database library, your framework, or your HTTP client — it's not really domain logic. It's infrastructure code with a domain-shaped mask on.
Slide 11
Part III · Enterprise & Distributed Systems

CQRS & Event Sourcing

Pattern
CQRS

Command Query Responsibility Segregation. Split read and write into separate models. Write side: normalized, correct. Read side: denormalized for query efficiency. Enables independent scaling. Pairs with Event Sourcing.

Pattern
Event Sourcing

Store state changes as immutable events, not current state. Current state = replay all events. Complete audit trail for free. "What was account balance on Jan 15?" → replay events. Challenge: schema evolution.

Combined Pattern
Events + CQRS

Commands produce events. Events are stored. Read models are built by projecting events asynchronously. Write model is authoritative; read models are eventually consistent.

Saga Pattern
Multi-Service Transactions

Sequence of local transactions, each publishing an event. If step 3 fails, compensating transactions undo steps 2 and 1. Two styles: Choreography (services self-coordinate) vs. Orchestration (central director).

Pattern
Outbox Pattern

Reliable event publishing without distributed transactions. Write event to outbox table in same DB transaction as business data. Separate process polls and publishes. Guarantees at-least-once delivery.

Slide 12
Part IV · Microservices

Microservices: Not Just Small Services

Microservices is an architectural style built from many smaller patterns. The core idea: small, independently deployable services organized around business capabilities, each owning their own data.

The Foundational Principles

  • Bounded Contexts — services align with DDD Bounded Contexts, not technical layers
  • No shared databases — each service owns its data, enforcing autonomy
  • "Smart endpoints, dumb pipes" — business logic in services, not infrastructure
  • Independent deployment — services can be deployed, scaled, and failure-isolated independently

The Key Test

Can you fully redeploy Service A without touching Service B? Can Service A fail without taking down Service B? If yes, you have a real microservice. If they share a database or A calls B synchronously on every request, you have a distributed monolith.

Slide 13
Part IV · Microservices

Communication Patterns

API Gateway

Single entry point for all client requests. Handles: authentication, SSL termination, request logging, rate limiting, request aggregation. Without it, clients need to know every service endpoint. Kong, Envoy, AWS API Gateway, Nginx.

Service Discovery

Services find each other dynamically as they scale. Client-side: client queries a registry (Consul, Eureka). Server-side: load balancer queries registry. Kubernetes DNS is the standard in K8s environments.

Backend for Frontends (BFF)

Each client type (web, mobile, third-party) gets its own dedicated API backend. Mobile and web have different data needs — don't force a one-size-fits-nothing compromise.

Service Mesh

Dedicated infrastructure layer for service-to-service communication. Sidecar proxies (Envoy) handle mTLS, traffic management, and observability. Istio and Linkerd are leading tools. Adds operational complexity — justified at scale.

Slide 14
Part IV · Microservices

Resilience Patterns: The Difference Between Success and Failure

Distributed systems fail in partial, asynchronous, time-dependent ways that monolithic systems don't. These patterns are not optional — they're survival.

Pattern
Circuit Breaker

Trip when downstream is failing. Closed (normal) → Open (fail fast, no calls) → Half-Open (probe recovery) → Closed. Prevents cascade failures. Hystrix, Resilience4j, Polly.

Pattern
Retry with Backoff

Re-attempt transient failures. Exponential backoff (1s → 2s → 4s) + jitter (randomization) prevents thundering herd. Requires idempotent operations.

Pattern
Bulkhead

Named after ship compartments. Separate thread/connection pools per downstream service. If Service A's pool is exhausted, Service B's remains available.

Pattern
Rate Limiting

Protect services from being overwhelmed. Token bucket and leaky bucket algorithms. HTTP 429 "Too Many Requests" is the standard response. Usually at API gateway.

Pattern
Fallbacks

When circuit is open, return a degraded but functional response. Cached data. Static error page. Meaningful error message. Never let a cascade failure reach the user.

Anti-Pattern
Distributed Monolith

Services that are tightly coupled via synchronous calls and shared databases. All the operational complexity of microservices with none of the benefits.

Slide 15
Part V · Cloud-Native & Reactive

Cloud-Native: Building for the Cloud

Cloud-native isn't just "running on cloud infrastructure" — it's an approach to building systems that take full advantage of cloud computing's model: elasticity, resilience, and operational automation.

📦

Containers

Immutable packaging. Rebuild, don't modify. Docker, multi-stage builds, minimal base images.

☸️

Kubernetes

Container orchestration. Self-healing, scaling, service discovery. Pods, Deployments, Services, Ingress.

🔄

CI/CD

Automated pipelines. Test, build, deploy. Trunk-based development. Feature flags enable shipping without deploying.

Slide 16
Part V · Cloud-Native & Reactive

The Twelve-Factor App

The philosophical foundation for cloud-ready applications. Originally from Heroku engineers; still the clearest articulation of what makes an app portable and maintainable in cloud environments.

Config from Environment

Store config in environment, not in code. Same codebase → dev, staging, production with different env vars. This is what makes deployment identical across environments.

Stateless Processes

Share nothing between requests. State goes to a backing service (Redis, database). This is what enables horizontal scaling — any instance can handle any request.

Backing Services as Resources

Databases, queues, caches are attached resources — swappable by config change. A MySQL database is indistinguishable from a managed Postgres from the app's perspective.

Build, Release, Run

Strict separation of stages. Code → build artifact → config → runnable instance. Once a release is created, it cannot be modified. Rollback = switch to previous release.

Disposability

Fast startup, graceful shutdown. Instances can be created or destroyed at any moment. No reliance on instance identity. This is what makes auto-scaling work.

Dev/Prod Parity

Keep development and production as similar as possible. Same runtime, same backing services. "Works on my machine" is eliminated by design, not by discipline.

Slide 17
Part V · Cloud-Native & Reactive

Kubernetes & Container Patterns

Core Kubernetes Primitives

PrimitiveWhat it does
PodAtomic deployable unit — containers sharing network/storage
DeploymentRolling updates, self-healing, rollback
ServiceStable network endpoint with load balancing
IngressHTTP/HTTPS routing with host and path rules
StatefulSetStable identities for stateful applications
CronJobScheduled workloads
Pattern
Sidecar

Helper container sharing the pod's lifecycle. Log collectors, service proxies, observability agents. Language-agnostic feature implementation without modifying the main app.

Pattern
Ambassador

Out-of-process proxy for outbound communication. Envoy as ambassador for all outgoing service calls. Standardizes instrumentation across languages.

Pattern
Adapter

Normalize external interfaces to what the application expects. Legacy metrics → Prometheus format. Wraps heterogeneous systems into a consistent interface.

Slide 18
Part V · Cloud-Native & Reactive

Deployment & Release Patterns

Blue-Green Deployment

Two identical environments. Live = Blue. New = Green. Switch traffic via load balancer at cutover. Instant rollback capability. Cost: double the infrastructure.

Pros: Zero downtime, instant rollback
Cons: Double infra, DB migration complexity

Canary Releases

Deploy new version to 5% of traffic. Monitor error rates. Gradually increase. Rollback by reducing traffic. Real production traffic is the test.

Pros: Minimal blast radius, real-world testing
Cons: Requires good observability, slower iteration

Feature Flags

Decouple deployment from release. Code ships behind a flag; enabling it goes live instantly. Enable = new feature. Disable = instant rollback. No redeployment needed.

Pros: Instant rollback, trunk-based dev, A/B testing
Cons: Flag cleanup tech debt, complexity management

Slide 19
Part V · Cloud-Native & Reactive

The Reactive Manifesto

Published 2014, defines four system-level properties that compose across all scales of a distributed system.

Responsive

Consistent response times. Problems detected and handled quickly. Upper bounds on latency — the system makes promises it can keep.

🛡️

Resilient

Stays responsive in face of failure. Achieved via replication, containment, isolation, delegation. Failures are contained within components.

📈

Elastic

Stays responsive under varying load. No central bottlenecks. Sharded components. Supports predictive and reactive scaling.

Message-Driven

Async message-passing establishes boundaries between components. Loose coupling, isolation, location transparency. Enables all the other properties.

Back Pressure

When downstream can't keep up, signal upstream to slow down. Prevents cascade failures. The mechanism that makes elasticity practical under real load.

Slide 20
Part V · Cloud-Native & Reactive

Chaos Engineering & Observability

Chaos Engineering

Pioneered by Netflix. Inject controlled failures into production to find weaknesses before they cause outages.

Define Steady State

What does "normal" look like? Establish measurable hypotheses about system behavior.

Inject Real-World Failures

Server terminations (Chaos Monkey), latency spikes, network partitions, resource exhaustion.

Automate Experiments

Run continuously in CI/CD. Measure blast radius. Iterate. Tools: Gremlin, LitmusChaos, AWS FIS.

Three Pillars of Observability

Pillar 1
Logs

Structured event records. ELK stack (Elasticsearch, Logstash, Kibana) or Loki. The raw material of debugging.

Pillar 2
Metrics

Numerical measurements over time. Prometheus (pull-based). Alert on anomalies, not thresholds.

Pillar 3
Distributed Tracing

Track a request end-to-end across service boundaries. A single request can fan out to dozens of services. Zipkin, Jaeger, OpenTelemetry.

Slide 21
Part VI · Domain-Driven Design

DDD: The Decomposition Language

Domain-Driven Design doesn't produce an architecture — it's a set of tactical patterns for modeling complex domains. It serves as the seam language for microservices and the domain isolation layer in Clean/Hexagon architectures.

Bounded Context

The most important DDD concept. An explicit boundary where a particular domain model is authoritative. Outside the boundary, the same words may mean different things. "Customer" in billing ≠ "Customer" in shipping — each context owns its model. Bounded Contexts are the natural seam for microservice decomposition.

Aggregates

Clusters of related objects treated as a single unit for data changes. An Aggregate Root is the entry point — external code interacts only with the root, never directly with internals. Immutability of value objects within the aggregate.

Domain Events

Records of significant business occurrences: OrderPlaced, PaymentReceived, ShipmentDispatched. Emitted by aggregates, consumed asynchronously by other Bounded Contexts. The foundation of event-driven microservices.

Ubiquitous Language

Shared terminology for a Bounded Context, used consistently in code, discussions, and documentation. The language is explicit and owned by the team. Eliminates translation between domain experts and engineers.

Slide 22
Summary

Three Themes Connect Everything

🔀

Managing Dependencies

Every pattern answers: what should depend on what, and how should changes flow? SOLID at class level. Hexagon/Clean at architecture level. Microservices at deployment level.

💥

Embracing Failure

Circuit breakers, bulkheads, retries, sagas, outbox, chaos engineering. Assume things will break. Design for it. Make the blast radius as small as possible.

🔄

Incremental Change

Strangler fig, blue-green, canary, feature flags. Big bang rewrites are how teams get into trouble. These patterns contain the risk of change.

Slide 23
Summary

Practical Advice for Engineers

  • Start simple. A well-structured monolith will take you further than you expect. Reach for microservices when you've hit the specific problems they solve — not before.
  • Follow SRP obsessively. The most impactful and most violated principle. Violations sound like classes named after job titles.
  • Patterns are vocabulary. Use them to communicate design intent. Don't use them as逞罚.
  • Test the domain in isolation. If you can't test your business logic without a database or HTTP client, your domain isn't properly isolated.
  • Design for failure from day one. Add circuit breakers and timeouts before you need them, not after your first cascade failure.
  • Observe everything. Logs, metrics, traces. You can't debug what you can't see. Distributed tracing is not optional in microservices.
  • Respect bounded contexts. Don't share databases across service boundaries. Don't share models across bounded contexts.
  • Invest in deployment automation. If deployment is scary, you're doing something wrong. Blue-green, canary, feature flags — make deployment low-risk.
Slide 24

The best architecture is the one your team can actually maintain.

Start simple. Add complexity only when you have to. Remove it as soon as you can.

SOLID Gang of Four Hexagon / Clean Microservices Cloud-Native DDD

Prepared by Babu AI for Thota · April 2026