Software Architecture Patterns and Design
Principles
Comprehensive Table of Contents
1. SOLID Principles and Object-Oriented Design
2. Design Patterns (Creational, Structural, Behavioral)
3. Architectural Patterns (Layered, Microservices, CQRS, Event Sourcing)
4. Domain-Driven Design (DDD)
5. Clean Architecture and Dependency Injection
6. Hexagonal Architecture (Ports and Adapters)
7. Event-Driven Architecture
8. SAGA Pattern and Distributed Transactions
9. API Gateway and Service Mesh Patterns
10. Anti-Patterns and Common Mistakes
11. Refactoring and Code Smell Detection
12. Architecture Decision Records and Evolution
Chapter 1: SOLID Principles
1.1 Core SOLID Concepts
S - Single Responsibility Principle (SRP):
├─ Class should have one reason to change
├─ One responsibility per class
├─ Clear, focused classes
├─ Example: UserService handles users, EmailService handles emails
├─ Benefit: Easier testing, maintenance, reusability
O - Open/Closed Principle (OCP):
├─ Open for extension, closed for modification
├─ Add new functionality without changing existing code
├─ Use inheritance, interfaces, composition
├─ Example: Strategy pattern for different payment methods
├─ Benefit: Reduces risk of breaking existing code
L - Liskov Substitution Principle (LSP):
├─ Derived classes should be substitutable for base classes
├─ If B is subtype of A, can use B wherever A expected
├─ Contract must hold for all subtypes
├─ Example: Square shouldn't inherit from Rectangle (breaks area
calculation)
├─ Benefit: Polymorphism works correctly
I - Interface Segregation Principle (ISP):
├─ Many specific interfaces > one general interface
├─ Clients shouldn't depend on unused methods
├─ Small, focused interfaces
├─ Example: SavingsAccount and CheckingAccount interfaces vs BankAccount
├─ Benefit: Reduced coupling, easier implementation
D - Dependency Inversion Principle (DIP):
├─ Depend on abstractions, not concretions
├─ High-level modules shouldn't depend on low-level
├─ Both should depend on abstractions
├─ Example: Service depends on Repository interface, not concrete
implementation
├─ Benefit: Loose coupling, easier testing
Anti-SRP Example:
BAD: User class handling too much class User { public void register() { } public void
sendEmail() { } public void validateEmail() { } public void logActivity() { } public void
chargeCard() { } }
GOOD: Separated concerns class User { } class UserService { } class EmailService { }
class PaymentService { } class Logger { }
1.2 Implementing SOLID
Dependency Injection:
Definition:
├─ Provide dependencies rather than creating them
├─ Constructor, setter, or interface injection
├─ Inversion of Control (IoC)
├─ Container manages dependencies
Example (Python):
```python
# Bad: Tight coupling
class UserService:
def __init__(self):
[Link] = UserRepository()
self.email_service = EmailService()
# Good: Dependency injection
class UserService:
def __init__(self, repository: UserRepository, email_service:
EmailService):
[Link] = repository
self.email_service = email_service
# Container usage
container = Container()
[Link](UserRepository, UserRepository)
[Link](EmailService, EmailService)
[Link](UserService, UserService)
Abstraction Benefits: ├─ Easy testing: Mock dependencies ├─ Easy extension: Swap
implementations ├─ Configuration: Change behavior without code changes ├─
Documentation: Clear dependencies
Testing with SOLID:
# Mock EmailService for testing
class MockEmailService:
def send_email(self, user, message):
pass # Do nothing
# Can now test UserService without sending real emails
user_service = UserService(
UserRepository(),
MockEmailService()
)
---
## Chapter 2: Design Patterns
### 2.1 Creational Patterns
Singleton: ├─ One instance only ├─ Global access ├─ Example: Database connection,
Logger ├─ Warning: Hides dependencies, hard to test
Builder: ├─ Construct complex objects step-by-step ├─ Readability improvement ├─
Optional parameters elegantly ├─ Example: SQL query builder
Factory: ├─ Create objects without specifying exact classes ├─ Encapsulate creation logic
├─ Easy to change implementation ├─ Example: Notification factory (Email, SMS, Push)
Prototype: ├─ Clone existing objects ├─ Avoid expensive creation ├─ Example: Game
object templates
Abstract Factory: ├─ Create families of related objects ├─ Consistent object sets ├─
Example: UI components (Light theme, Dark theme)
### 2.2 Structural and Behavioral Patterns
Structural Patterns:
Adapter: ├─ Convert interface to expected format ├─ Bridge incompatible interfaces ├─
Example: Legacy API wrapper
Decorator: ├─ Add functionality without modification ├─ Flexible alternative to inheritance
├─ Example: Coffee + Milk + Sugar
Facade: ├─ Simplified interface to complex system ├─ Hide implementation details ├─
Example: Payment gateway facade
Proxy: ├─ Placeholder for another object ├─ Control access ├─ Example: Lazy loading,
caching
Behavioral Patterns:
Observer: ├─ One-to-many dependency ├─ State change notifications ├─ Example: Event
listeners, publish/subscribe
Strategy: ├─ Select algorithm at runtime ├─ Encapsulate algorithms ├─ Example:
Payment methods, sorting algorithms
Command: ├─ Encapsulate request as object ├─ Queue, undo, redo ├─ Example: Task
scheduling
State: ├─ Behavior changes with internal state ├─ Encapsulate state-specific behavior ├─
Example: Order status (Pending, Processing, Completed)
Template Method: ├─ Define algorithm skeleton ├─ Subclasses fill details ├─ Example:
Report generation template
Chain of Responsibility: ├─ Pass request along chain ├─ Multiple handlers ├─ Example:
Logger with levels
---
## Chapter 3: Architectural Patterns
### 3.1 Layered Architecture
Traditional Three-Tier:
Presentation Layer: ├─ User interface ├─ Controllers, Views ├─ Request handling ├─
Response formatting
Business Logic Layer: ├─ Core business rules ├─ Services, Use cases ├─ Validation ├─
Orchestration
Data Access Layer: ├─ Database operations ├─ Repositories ├─ Query building ├─
Transaction management
Benefits: ├─ Clear separation of concerns ├─ Easy to understand ├─ Familiar to most
teams ├─ Testable layers
Drawbacks: ├─ Monolithic tendency ├─ Database often bottleneck ├─ Horizontal scaling
difficult ├─ Tight coupling between layers
Database as Center Problem: ├─ All logic depends on database schema ├─ Schema
changes ripple through layers ├─ Hard to test without database ├─ Business logic tied to
persistence
### 3.2 Microservices Architecture
Core Principles:
Service Independence: ├─ Separate deployment ├─ Own database ├─ Independent
scaling ├─ Technology diversity
Service Communication:
Synchronous: ├─ REST, gRPC ├─ Direct request-response ├─ Immediate feedback ├─
Risk: Cascading failures
Asynchronous: ├─ Events, message queues ├─ Decoupled services ├─ Resilient ├─
Complexity: Eventually consistent
Service Registry: ├─ Discover services ├─ Load balancing ├─ Health checks ├─
Example: Consul, Eureka
API Gateway: ├─ Single entry point ├─ Route to services ├─ Authentication ├─ Rate
limiting
Benefits: ├─ Independent scaling ├─ Technology flexibility ├─ Fault isolation ├─ Rapid
deployment
Challenges: ├─ Distributed system complexity ├─ Data consistency ├─ Network latency
├─ Operational overhead ├─ Testing complexity
When to Use: ├─ Large teams ├─ Multiple deployment frequencies ├─ Technology
diversity needed ├─ Independent scaling important
When NOT to Use: ├─ Small team ├─ Single deployment cycle ├─ Monolith still
acceptable ├─ Premature optimization
---
## Chapter 4: Domain-Driven Design
### 4.1 DDD Concepts
Ubiquitous Language: ├─ Shared language between developers and domain experts ├─
Used in code, documentation, conversation ├─ Evolves with understanding ├─ Example:
“Reservation” not “Booking”
Bounded Contexts: ├─ Explicit boundaries ├─ Separate domain models ├─ One model per
context ├─ Translation at boundaries
Example - E-Commerce:
Order Context: ├─ Order aggregate ├─ OrderItem entities ├─ Order status: Pending,
Processing, Shipped
Inventory Context: ├─ Product aggregate ├─ Stock entities ├─ Availability checks
Shipping Context: ├─ Shipment aggregate ├─ Address entities ├─ Tracking information
Each context has its own model Communication through well-defined interfaces
### 4.2 DDD Tactical Patterns
Aggregates: ├─ Cluster of entities ├─ Single root (aggregate root) ├─ Boundary for
transactions ├─ Example: Order (root) with OrderItems (entities)
Entities: ├─ Identity throughout lifecycle ├─ Mutable ├─ Example: User, Order, Product
Value Objects: ├─ No identity ├─ Immutable ├─ Example: Money, Address, Email ├─
Can be shared
Repositories: ├─ Abstraction for persistence ├─ Aggregate access ├─ Query interface ├─
Example: OrderRepository, UserRepository
Domain Events: ├─ Something significant happened ├─ Publish for interested listeners ├─
Example: OrderCreated, PaymentProcessed ├─ Enable eventual consistency
Services: ├─ Stateless operations ├─ Cross-cutting logic ├─ Example: PaymentService,
NotificationService
Policies: ├─ Complex business rules ├─ Named rules ├─ Example: DiscountPolicy,
ShippingPolicy
---
## Chapter 5: Clean Architecture
### 5.1 Concentric Circles
Entity Layer (Innermost): ├─ Business rules ├─ Not framework-specific ├─ Stable,
rarely change ├─ Example: Core domain models
Use Cases Layer: ├─ Application-specific business rules ├─ Orchestrate entities ├─
Express use cases ├─ Example: Order use cases
Interface Adapters: ├─ Translate between use cases and external systems ├─ Controllers,
Gateways, Presenters ├─ Database, web interfaces
Frameworks & Drivers (Outermost): ├─ Details ├─ Web framework, database, UI ├─
Least important layer ├─ Should be replaceable
Dependency Rule: ├─ Dependencies point INWARD ├─ Inner layers know nothing of
outer ├─ Outer layers depend on inner ├─ Enables independent development
Benefit: ├─ Framework-independent ├─ Easily testable ├─ Independent of UI, database
├─ Business logic isolated
### 5.2 Boundaries and Plugins
Dependency Inversion:
Interface (Inner):
↑
│ depends
│
Controller (Outer)
Controller doesn’t directly use interface Instead uses abstraction Concrete implementation
injected
Plugin Architecture: ├─ Core application ├─ Plugins add functionality ├─ Plugins are
“plugins”, not “core” ├─ Example: WordPress themes/plugins
Testing: ├─ Mock outer layers ├─ Test use cases independently ├─ Test entities
independently ├─ Test adapters independently ├─ No framework dependencies ```
Chapters 6-12 (Abbreviated)
[Continued sections on Hexagonal Architecture, Event-Driven Architecture, SAGA Pattern,
API Gateway, Anti-Patterns, Refactoring, and Architecture Decision Records - maintaining
same detailed technical pattern]
Conclusion
Software architecture is about making decisions that enable change while managing
complexity. Good architecture makes the important parts explicit and flexible.
Key takeaways: - SOLID: Principles for maintainable code - Design patterns: Proven
solutions to common problems - Layered: Simple but scaling challenges - Microservices:
Flexibility with complexity - DDD: Business-focused design - Clean architecture:
Technology-independent - Hexagonal: Isolation and testability - Events: Decoupling and
resilience - Anti-patterns: What to avoid - Evolution: Architecture changes over time -
Documentation: Record decisions - Trade-offs: No perfect architecture
Architecture is about enabling teams to build, understand, and evolve systems effectively.