Addresses SkillzWave feedback and Anthropic best practices: SKILL.md (343 lines): - Third-person description with trigger phrases - Added Table of Contents for navigation - Concrete tool descriptions with usage examples - Decision workflows: Database, Architecture Pattern, Monolith vs Microservices - Removed marketing fluff, added actionable content References (rewritten with real content): - architecture_patterns.md: 9 patterns with trade-offs, code examples (Monolith, Modular Monolith, Microservices, Event-Driven, CQRS, Event Sourcing, Hexagonal, Clean Architecture, API Gateway) - system_design_workflows.md: 6 step-by-step workflows (System Design Interview, Capacity Planning, API Design, Database Schema, Scalability Assessment, Migration Planning) - tech_decision_guide.md: 7 decision frameworks with matrices (Database, Cache, Message Queue, Auth, Frontend, Cloud, API) Scripts (fully functional, standard library only): - architecture_diagram_generator.py: Mermaid + PlantUML + ASCII output Scans project structure, detects components, relationships - dependency_analyzer.py: npm/pip/go/cargo support Circular dependency detection, coupling score calculation - project_architect.py: Pattern detection (7 patterns) Layer violation detection, code quality metrics All scripts tested and working. Closes #48 Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
14 KiB
Architecture Patterns Reference
Detailed guide to software architecture patterns with trade-offs and implementation guidance.
Patterns Index
- Monolithic Architecture
- Modular Monolith
- Microservices Architecture
- Event-Driven Architecture
- CQRS (Command Query Responsibility Segregation)
- Event Sourcing
- Hexagonal Architecture (Ports & Adapters)
- Clean Architecture
- API Gateway Pattern
1. Monolithic Architecture
Problem it solves: Need to build and deploy a complete application as a single unit with minimal operational complexity.
When to use:
- Small team (1-5 developers)
- MVP or early-stage product
- Simple domain with clear boundaries
- Deployment simplicity is priority
When NOT to use:
- Multiple teams need independent deployment
- Parts of system have vastly different scaling needs
- Technology diversity is required
Trade-offs:
| Pros | Cons |
|---|---|
| Simple deployment | Scaling is all-or-nothing |
| Easy debugging | Large codebase becomes unwieldy |
| No network latency between components | Single point of failure |
| Simple testing | Technology lock-in |
Structure example:
monolith/
├── src/
│ ├── controllers/ # HTTP handlers
│ ├── services/ # Business logic
│ ├── repositories/ # Data access
│ ├── models/ # Domain entities
│ └── utils/ # Shared utilities
├── tests/
└── package.json
2. Modular Monolith
Problem it solves: Need monolith simplicity but with clear boundaries that enable future extraction to services.
When to use:
- Medium team (5-15 developers)
- Domain boundaries are becoming clearer
- Want option to extract services later
- Need better code organization than traditional monolith
When NOT to use:
- Already need independent deployment
- Teams can't coordinate releases
Trade-offs:
| Pros | Cons |
|---|---|
| Clear module boundaries | Still single deployment |
| Easier to extract services later | Requires discipline to maintain boundaries |
| Single database simplifies transactions | Can drift back to coupled monolith |
| Team ownership of modules |
Structure example:
modular-monolith/
├── modules/
│ ├── users/
│ │ ├── api/ # Public interface
│ │ ├── internal/ # Implementation
│ │ └── index.ts # Module exports
│ ├── orders/
│ │ ├── api/
│ │ ├── internal/
│ │ └── index.ts
│ └── payments/
├── shared/ # Cross-cutting concerns
└── main.ts
Key rule: Modules communicate only through their public API, never by importing internal files.
3. Microservices Architecture
Problem it solves: Need independent deployment, scaling, and technology choices for different parts of the system.
When to use:
- Large team (15+ developers) organized around business capabilities
- Different parts need different scaling
- Independent deployment is critical
- Technology diversity is beneficial
When NOT to use:
- Small team that can't handle operational complexity
- Domain boundaries are unclear
- Distributed transactions are common requirement
- Network latency is unacceptable
Trade-offs:
| Pros | Cons |
|---|---|
| Independent deployment | Network complexity |
| Independent scaling | Distributed system challenges |
| Technology flexibility | Operational overhead |
| Team autonomy | Data consistency challenges |
| Fault isolation | Testing complexity |
Structure example:
microservices/
├── services/
│ ├── user-service/
│ │ ├── src/
│ │ ├── Dockerfile
│ │ └── package.json
│ ├── order-service/
│ └── payment-service/
├── api-gateway/
├── infrastructure/
│ ├── kubernetes/
│ └── terraform/
└── docker-compose.yml
Communication patterns:
- Synchronous: REST, gRPC
- Asynchronous: Message queues (RabbitMQ, Kafka)
4. Event-Driven Architecture
Problem it solves: Need loose coupling between components that react to business events asynchronously.
When to use:
- Components need loose coupling
- Audit trail of all changes is valuable
- Real-time reactions to events
- Multiple consumers for same events
When NOT to use:
- Simple CRUD operations
- Synchronous responses required
- Team unfamiliar with async patterns
- Debugging simplicity is priority
Trade-offs:
| Pros | Cons |
|---|---|
| Loose coupling | Eventual consistency |
| Scalability | Debugging complexity |
| Audit trail built-in | Message ordering challenges |
| Easy to add new consumers | Infrastructure complexity |
Event structure example:
interface DomainEvent {
eventId: string;
eventType: string;
aggregateId: string;
timestamp: Date;
payload: Record<string, unknown>;
metadata: {
correlationId: string;
causationId: string;
};
}
// Example event
const orderCreated: DomainEvent = {
eventId: "evt-123",
eventType: "OrderCreated",
aggregateId: "order-456",
timestamp: new Date(),
payload: {
customerId: "cust-789",
items: [...],
total: 99.99
},
metadata: {
correlationId: "req-001",
causationId: "cmd-create-order"
}
};
5. CQRS
Problem it solves: Read and write workloads have different requirements and need to be optimized separately.
When to use:
- Read/write ratio is heavily skewed (10:1 or more)
- Read and write models differ significantly
- Complex queries that don't map to write model
- Different scaling needs for reads vs writes
When NOT to use:
- Simple CRUD with balanced reads/writes
- Read and write models are nearly identical
- Team unfamiliar with pattern
- Added complexity isn't justified
Trade-offs:
| Pros | Cons |
|---|---|
| Optimized read models | Eventual consistency between models |
| Independent scaling | Complexity |
| Simplified queries | Synchronization logic |
| Better performance | More code to maintain |
Structure example:
// Write side (Commands)
interface CreateOrderCommand {
customerId: string;
items: OrderItem[];
}
class OrderCommandHandler {
async handle(cmd: CreateOrderCommand): Promise<void> {
const order = Order.create(cmd);
await this.repository.save(order);
await this.eventBus.publish(order.events);
}
}
// Read side (Queries)
interface OrderSummaryQuery {
customerId: string;
dateRange: DateRange;
}
class OrderQueryHandler {
async handle(query: OrderSummaryQuery): Promise<OrderSummary[]> {
// Query optimized read model (denormalized)
return this.readDb.query(`
SELECT * FROM order_summaries
WHERE customer_id = ? AND created_at BETWEEN ? AND ?
`, [query.customerId, query.dateRange.start, query.dateRange.end]);
}
}
6. Event Sourcing
Problem it solves: Need complete audit trail and ability to reconstruct state at any point in time.
When to use:
- Audit trail is regulatory requirement
- Need to answer "how did we get here?"
- Complex domain with undo/redo requirements
- Debugging production issues requires history
When NOT to use:
- Simple CRUD applications
- No audit requirements
- Team unfamiliar with pattern
- Reporting on current state is primary need
Trade-offs:
| Pros | Cons |
|---|---|
| Complete audit trail | Storage grows indefinitely |
| Time-travel debugging | Query complexity |
| Natural fit for event-driven | Learning curve |
| Enables CQRS | Eventual consistency |
Implementation example:
// Events
type OrderEvent =
| { type: 'OrderCreated'; customerId: string; items: Item[] }
| { type: 'ItemAdded'; itemId: string; quantity: number }
| { type: 'OrderShipped'; trackingNumber: string };
// Aggregate rebuilt from events
class Order {
private state: OrderState;
static fromEvents(events: OrderEvent[]): Order {
const order = new Order();
events.forEach(event => order.apply(event));
return order;
}
private apply(event: OrderEvent): void {
switch (event.type) {
case 'OrderCreated':
this.state = { status: 'created', items: event.items };
break;
case 'ItemAdded':
this.state.items.push({ id: event.itemId, qty: event.quantity });
break;
case 'OrderShipped':
this.state.status = 'shipped';
this.state.trackingNumber = event.trackingNumber;
break;
}
}
}
7. Hexagonal Architecture
Problem it solves: Need to isolate business logic from external concerns (databases, APIs, UI) for testability and flexibility.
When to use:
- Business logic is complex and valuable
- Multiple interfaces to same domain (API, CLI, events)
- Testability is priority
- External systems may change
When NOT to use:
- Simple CRUD with no business logic
- Single interface to domain
- Overhead isn't justified
Trade-offs:
| Pros | Cons |
|---|---|
| Business logic isolation | More abstractions |
| Highly testable | Initial setup overhead |
| External systems are swappable | Can be over-engineered |
| Clear boundaries | Learning curve |
Structure example:
hexagonal/
├── domain/ # Business logic (no external deps)
│ ├── entities/
│ ├── services/
│ └── ports/ # Interfaces (what domain needs)
│ ├── OrderRepository.ts
│ └── PaymentGateway.ts
├── adapters/ # Implementations
│ ├── persistence/ # Database adapters
│ │ └── PostgresOrderRepository.ts
│ ├── payment/ # External service adapters
│ │ └── StripePaymentGateway.ts
│ └── api/ # HTTP adapters
│ └── OrderController.ts
└── config/ # Wiring it all together
8. Clean Architecture
Problem it solves: Need clear dependency rules where business logic doesn't depend on frameworks or external systems.
When to use:
- Long-lived applications that will outlive frameworks
- Business logic is the core value
- Team discipline to maintain boundaries
- Multiple delivery mechanisms (web, mobile, CLI)
When NOT to use:
- Short-lived projects
- Framework-centric applications
- Simple CRUD operations
Trade-offs:
| Pros | Cons |
|---|---|
| Framework independence | More code |
| Testable business logic | Can feel over-engineered |
| Clear dependency direction | Learning curve |
| Flexible delivery mechanisms | Initial setup cost |
Dependency rule: Dependencies point inward. Inner circles know nothing about outer circles.
┌─────────────────────────────────────────┐
│ Frameworks & Drivers │
│ ┌─────────────────────────────────┐ │
│ │ Interface Adapters │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ Application Layer │ │ │
│ │ │ ┌─────────────────┐ │ │ │
│ │ │ │ Entities │ │ │ │
│ │ │ │ (Domain Logic) │ │ │ │
│ │ │ └─────────────────┘ │ │ │
│ │ └─────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
9. API Gateway Pattern
Problem it solves: Need single entry point for clients that routes to multiple backend services.
When to use:
- Multiple backend services
- Cross-cutting concerns (auth, rate limiting, logging)
- Different clients need different APIs
- Service aggregation needed
When NOT to use:
- Single backend service
- Simplicity is priority
- Team can't maintain gateway
Trade-offs:
| Pros | Cons |
|---|---|
| Single entry point | Single point of failure |
| Cross-cutting concerns centralized | Additional latency |
| Backend service abstraction | Complexity |
| Client-specific APIs | Can become bottleneck |
Responsibilities:
┌─────────────────────────────────────┐
│ API Gateway │
├─────────────────────────────────────┤
│ • Authentication/Authorization │
│ • Rate limiting │
│ • Request/Response transformation │
│ • Load balancing │
│ • Circuit breaking │
│ • Caching │
│ • Logging/Monitoring │
└─────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────┐ ┌─────┐ ┌─────┐
│Svc A│ │Svc B│ │Svc C│
└─────┘ └─────┘ └─────┘
Pattern Selection Quick Reference
| If you need... | Consider... |
|---|---|
| Simplicity, small team | Monolith |
| Clear boundaries, future flexibility | Modular Monolith |
| Independent deployment/scaling | Microservices |
| Loose coupling, async processing | Event-Driven |
| Separate read/write optimization | CQRS |
| Complete audit trail | Event Sourcing |
| Testable, swappable externals | Hexagonal |
| Framework independence | Clean Architecture |
| Single entry point, multiple services | API Gateway |