Software Architecture
Clean Architecture
Layer Structure (outside → inside)
┌─────────────────────────────────────┐
│ Frameworks & Drivers │ ← FastAPI, Express, React, DB drivers
│ (Infrastructure Layer) │
├─────────────────────────────────────┤
│ Interface Adapters │ ← Controllers, Presenters, Gateways
│ (Adapter Layer) │
├─────────────────────────────────────┤
│ Application Business Rules │ ← Use Cases, Application Services
│ (Use Case Layer) │
├─────────────────────────────────────┤
│ Enterprise Business Rules │ ← Entities, Domain Models
│ (Domain Layer) │
└─────────────────────────────────────┘
Dependency Rule: Dependencies only point inward. The domain knows nothing about the database.
Practical Application
- Domain entities have no framework imports
- Use cases define interfaces (ports); infrastructure implements them (adapters)
- Controllers are thin — they translate HTTP to use case calls, nothing more
- The database is a detail, not the architecture
SOLID Principles
S — Single Responsibility A class/module has one reason to change. If you need "and" to describe what it does, split it.
O — Open/Closed Open for extension, closed for modification. Add behavior via new classes, not by modifying existing ones. Strategy pattern is the classic implementation.
L — Liskov Substitution
Subtypes must be substitutable for their base types without breaking the program. If you find yourself checking isinstance() in business logic, Liskov is violated.
I — Interface Segregation Many specific interfaces > one general interface. Clients shouldn't depend on methods they don't use.
D — Dependency Inversion High-level modules don't depend on low-level modules — both depend on abstractions. Inject dependencies; don't construct them inside classes.
Essential Design Patterns
Creational
Factory Method — when you need to create objects without specifying the exact class Builder — when constructing complex objects step by step
Structural
Repository — abstracts data access behind an interface; use case calls user_repo.find_by_id(), doesn't know if it's Postgres or Redis
Adapter — wraps incompatible interfaces; isolates third-party code
Behavioral
Strategy — encapsulates interchangeable algorithms; inject the algorithm, don't hardcode it Observer/Event — decouple producers from consumers; domain events are the DDD version Command — encapsulate requests as objects; enables undo, queuing, logging
Domain-Driven Design (Core Concepts)
Entity — has identity (ID), state can change, two entities with same ID are the same object Value Object — no identity, defined by its attributes, immutable (Money, Address, Email) Aggregate — cluster of entities/VOs with a root; transactions happen within one aggregate Domain Event — something that happened in the domain that other parts care about Repository — collection-like interface to access aggregates Service — domain logic that doesn't fit in an entity
Architecture Patterns
CQRS (Command Query Responsibility Segregation)
Separate read models from write models. Commands change state; queries return data. Use when read and write load patterns differ significantly.
Event Sourcing
Store events, not state. Current state = replaying all events. Use when audit trail, temporal queries, or undo are requirements. Warning: significant complexity cost.
Hexagonal Architecture (Ports & Adapters)
Application core defines ports (interfaces); adapters implement them. Database, HTTP, and message queue are all adapters. Core is fully testable without infrastructure.
Anti-Patterns to Avoid
Big Ball of Mud — no clear structure, everything depends on everything Anemic Domain Model — entities are just data bags, all logic in services God Object — one class knows too much and does too much Leaky Abstractions — database details seeping into business logic Premature Optimization of Architecture — adding CQRS/Event Sourcing before scale requires it
Architecture Decision Template
When making an architecture decision, document:
Context: [What situation are we in?]
Decision: [What we chose]
Rationale: [Why — including rejected alternatives]
Consequences: [What becomes easier, what becomes harder]
Quick Heuristics
- If a change requires modifying multiple unrelated files → coupling problem
- If testing requires running the full application → dependency inversion violated
- If the code can't be understood without knowing the framework → architecture leaked
- If you're not sure where new code goes → the layer structure isn't clear enough
Source: adapted from ComposioHQ/awesome-claude-skills/software-architecture + standard references