ADR Authoring — Architecture Patterns, Workflows, and Decision Guides
| Field | Value |
|---|---|
| Type | Skill Resource |
| Source | ~/.copilot/skills/architecture/references/adr-authoring.md |
| Description | Not specified |
Source Content
ADR Authoring — Architecture Patterns, Workflows, and Decision Guides
Reference material for writing ADR-shaped system design decisions: the pattern catalog, the step-by-step workflows, and the technology decision matrices. Validate any ADR produced from this material with scripts/lint_adr.sh.
Part 1 — Architecture Pattern Catalog
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.json2. 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.tsKey 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.ymlCommunication 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 eventconst 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:
// Eventstype OrderEvent = | { type: 'OrderCreated'; customerId: string; items: Item[] } | { type: 'ItemAdded'; itemId: string; quantity: number } | { type: 'OrderShipped'; trackingNumber: string };
// Aggregate rebuilt from eventsclass 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 together8. 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 |
Part 2 — System Design Workflows
Step-by-step workflows for common system design tasks.
Workflows Index
- System Design Interview Approach
- Capacity Planning Workflow
- API Design Workflow
- Database Schema Design
- Scalability Assessment
- Migration Planning
1. System Design Interview Approach
Use when designing a system from scratch or explaining architecture decisions.
Step 1: Clarify Requirements (3-5 minutes)
Functional requirements:
- What are the core features?
- Who are the users?
- What actions can users take?
Non-functional requirements:
- Expected scale (users, requests/sec, data size)
- Latency requirements
- Availability requirements (99.9%? 99.99%?)
- Consistency requirements (strong? eventual?)
Example questions to ask:
- How many users? Daily active users?- Read/write ratio?- Data retention period?- Geographic distribution?- Peak vs average load?Step 2: Estimate Scale (2-3 minutes)
Calculate key metrics:
Users: 10M monthly active usersDAU: 1M daily active usersRequests: 100 req/user/day = 100M req/day = 1,200 req/sec (avg) = 3,600 req/sec (peak, 3x)
Storage: 1KB/request × 100M = 100GB/day = 36TB/year
Bandwidth: 100GB/day = 1.2 MB/sec (avg)Step 3: Design High-Level Architecture (5-10 minutes)
Start with basic components:
┌──────────┐ ┌──────────┐ ┌──────────┐│ Client │────▶│ API │────▶│ Database │└──────────┘ └──────────┘ └──────────┘Add components as needed:
- Load balancer for traffic distribution
- Cache for read-heavy workloads
- CDN for static content
- Message queue for async processing
- Search index for complex queries
Step 4: Deep Dive into Components (10-15 minutes)
For each major component, discuss:
- Why this technology choice?
- How does it handle failures?
- How does it scale?
- What are the trade-offs?
Step 5: Address Bottlenecks (5 minutes)
Common bottlenecks:
- Database read/write capacity
- Network bandwidth
- Single points of failure
- Hot spots in data distribution
Solutions:
- Caching (Redis, Memcached)
- Database sharding
- Read replicas
- CDN for static content
- Async processing for non-critical paths
2. Capacity Planning Workflow
Use when estimating infrastructure requirements for a new system or feature.
Step 1: Gather Requirements
| Metric | Current | 6 months | 1 year |
|---|---|---|---|
| Monthly active users | |||
| Peak concurrent users | |||
| Requests per second | |||
| Data storage (GB) | |||
| Bandwidth (Mbps) |
Step 2: Calculate Compute Requirements
Web/API servers:
Peak RPS: 3,600Requests per server: 500 (conservative)Servers needed: 3,600 / 500 = 8 servers
With redundancy (N+2): 10 serversCPU estimation:
Per request: 50ms CPU timePeak RPS: 3,600CPU cores: 3,600 × 0.05 = 180 cores
With headroom (70% target utilization): 180 / 0.7 = 257 cores = 32 servers × 8 coresStep 3: Calculate Storage Requirements
Database storage:
Records per day: 100,000Record size: 2KBDaily growth: 200MB
With indexes (2x): 400MB/dayRetention (1 year): 146GB
With replication (3x): 438GBFile storage:
Files per day: 10,000Average file size: 500KBDaily growth: 5GB
Retention (1 year): 1.8TBStep 4: Calculate Network Requirements
Bandwidth:
Response size: 10KB averagePeak RPS: 3,600Outbound: 3,600 × 10KB = 36MB/s = 288 Mbps
With headroom (50%): 432 Mbps ≈ 500 Mbps connectionStep 5: Document and Review
Create capacity plan document:
- Current requirements
- Growth projections
- Infrastructure recommendations
- Cost estimates
- Review triggers (when to re-evaluate)
3. API Design Workflow
Use when designing new APIs or refactoring existing ones.
Step 1: Identify Resources
List the nouns in your domain:
E-commerce example:- Users- Products- Orders- Payments- ReviewsStep 2: Define Operations
Map CRUD to HTTP methods:
| Operation | HTTP Method | URL Pattern |
|---|---|---|
| List | GET | /resources |
| Get one | GET | /resources/{id} |
| Create | POST | /resources |
| Update | PUT/PATCH | /resources/{id} |
| Delete | DELETE | /resources/{id} |
Step 3: Design Request/Response Formats
Request example:
POST /api/v1/ordersContent-Type: application/json
{ "customer_id": "cust-123", "items": [ {"product_id": "prod-456", "quantity": 2} ], "shipping_address": { "street": "123 Main St", "city": "San Francisco", "state": "CA", "zip": "94102" }}Response example:
HTTP/1.1 201 CreatedContent-Type: application/json
{ "id": "ord-789", "status": "pending", "customer_id": "cust-123", "items": [...], "total": 99.99, "created_at": "2024-01-15T10:30:00Z", "_links": { "self": "/api/v1/orders/ord-789", "customer": "/api/v1/customers/cust-123" }}Step 4: Handle Errors Consistently
Error response format:
HTTP/1.1 400 Bad RequestContent-Type: application/json
{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid request parameters", "details": [ { "field": "quantity", "message": "must be greater than 0" } ] }, "request_id": "req-abc123"}Standard error codes:
| HTTP Status | Use Case |
|---|---|
| 400 | Validation errors |
| 401 | Authentication required |
| 403 | Permission denied |
| 404 | Resource not found |
| 409 | Conflict (duplicate, etc.) |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
Step 5: Document the API
Include:
- Authentication method
- Base URL and versioning
- Endpoints with examples
- Error codes and meanings
- Rate limits
- Pagination format
4. Database Schema Design Workflow
Use when designing a new database or major schema changes.
Step 1: Identify Entities
List the things you need to store:
E-commerce:- User (id, email, name, created_at)- Product (id, name, price, stock)- Order (id, user_id, status, total)- OrderItem (id, order_id, product_id, quantity, price)Step 2: Define Relationships
Relationship types:
User ──1:N──▶ Order (one user, many orders)Order ──1:N──▶ OrderItem (one order, many items)Product ──1:N──▶ OrderItem (one product, many order items)Step 3: Choose Primary Keys
Options:
| Type | Pros | Cons |
|---|---|---|
| Auto-increment | Simple, ordered | Not distributed-friendly |
| UUID | Globally unique | Larger, random |
| ULID | Globally unique, sortable | Larger |
Step 4: Add Indexes
Index selection rules:
-- Index columns used in WHERE clausesCREATE INDEX idx_orders_user_id ON orders(user_id);
-- Index columns used in JOINsCREATE INDEX idx_order_items_order_id ON order_items(order_id);
-- Index columns used in ORDER BY with WHERECREATE INDEX idx_orders_user_status ON orders(user_id, status);
-- Consider composite indexes for common queries-- Query: SELECT * FROM orders WHERE user_id = ? AND status = 'active'CREATE INDEX idx_orders_user_status ON orders(user_id, status);Step 5: Plan for Scale
Partitioning strategies:
-- Partition by date (time-series data)CREATE TABLE events ( id BIGINT, created_at TIMESTAMP, data JSONB) PARTITION BY RANGE (created_at);
-- Partition by hash (distribute evenly)CREATE TABLE users ( id BIGINT, email VARCHAR(255)) PARTITION BY HASH (id);Sharding considerations:
- Shard key selection (user_id, tenant_id, etc.)
- Cross-shard query limitations
- Rebalancing strategy
5. Scalability Assessment Workflow
Use when evaluating if current architecture can handle growth.
Step 1: Profile Current System
Metrics to collect:
Current load:- Average requests/sec: ___- Peak requests/sec: ___- Average latency: ___ ms- P99 latency: ___ ms- Error rate: ___%
Resource utilization:- CPU: ___%- Memory: ___%- Disk I/O: ___%- Network: ___%Step 2: Identify Bottlenecks
Check each layer:
| Layer | Bottleneck Signs |
|---|---|
| Web servers | High CPU, connection limits |
| Application | Slow requests, thread pool exhaustion |
| Database | Slow queries, lock contention |
| Cache | High miss rate, memory pressure |
| Network | Bandwidth saturation, latency |
Step 3: Load Test
Test scenarios:
1. Baseline: Current production load2. 2x load: Expected growth in 6 months3. 5x load: Stress test4. Spike: Sudden 10x for 5 minutesTools:
- k6, Locust, JMeter for HTTP
- pgbench for PostgreSQL
- redis-benchmark for Redis
Step 4: Identify Scaling Strategy
Vertical scaling (scale up):
- Add more CPU, memory, disk
- Simpler but has limits
- Use when: Single server can handle more
Horizontal scaling (scale out):
- Add more servers
- Requires stateless design
- Use when: Need linear scaling
Step 5: Create Scaling Plan
Document:
Trigger: When average CPU > 70% for 15 minutes
Action:1. Add 2 more web servers2. Update load balancer3. Verify health checks pass
Rollback:1. Remove added servers2. Update load balancer3. Investigate issue6. Migration Planning Workflow
Use when migrating to new infrastructure, database, or architecture.
Step 1: Assess Current State
Document:
- Current architecture diagram
- Data volumes
- Dependencies
- Integration points
- Performance baselines
Step 2: Define Target State
Document:
- New architecture diagram
- Technology changes
- Expected improvements
- Success criteria
Step 3: Plan Migration Strategy
Strategies:
| Strategy | Risk | Downtime | Complexity |
|---|---|---|---|
| Big bang | High | Yes | Low |
| Blue-green | Medium | Minimal | Medium |
| Canary | Low | None | High |
| Strangler fig | Low | None | High |
Strangler fig pattern (recommended for large systems):
1. Add facade in front of old system2. Route small percentage of traffic to new system3. Gradually increase traffic to new system4. Retire old system when 100% migratedStep 4: Create Rollback Plan
For each step, define:
Step: Migrate user service to new database
Rollback trigger:- Error rate > 1%- Latency > 500ms P99- Data inconsistency detected
Rollback steps:1. Route traffic back to old database2. Sync any new data back3. Investigate root cause
Rollback time estimate: 15 minutesStep 5: Execute with Checkpoints
Migration checklist:
□ Backup current system□ Verify backup restoration works□ Deploy new infrastructure□ Run smoke tests on new system□ Migrate small percentage (1%)□ Monitor for 24 hours□ Increase to 10%□ Monitor for 24 hours□ Increase to 50%□ Monitor for 24 hours□ Complete migration (100%)□ Decommission old system□ Document lessons learnedQuick Reference
| Task | Start Here |
|---|---|
| New system design | System Design Interview Approach |
| Infrastructure sizing | Capacity Planning |
| New API | API Design |
| Database design | Database Schema Design |
| Handle growth | Scalability Assessment |
| System migration | Migration Planning |
Part 3 — Technology Decision Guide
Decision frameworks and comparison matrices for common technology choices.
Decision Frameworks Index
- Database Selection
- Caching Strategy
- Message Queue Selection
- Authentication Strategy
- Frontend Framework Selection
- Cloud Provider Selection
- API Style Selection
1. Database Selection
SQL vs NoSQL Decision Matrix
| Factor | Choose SQL | Choose NoSQL |
|---|---|---|
| Data relationships | Complex, many-to-many | Simple, denormalized OK |
| Schema | Well-defined, stable | Evolving, flexible |
| Transactions | ACID required | Eventual consistency OK |
| Query patterns | Complex joins, aggregations | Key-value, document lookups |
| Scale | Vertical (some horizontal) | Horizontal first |
| Team expertise | Strong SQL skills | Document/KV experience |
Database Type Selection
Relational (SQL):
| Database | Best For | Avoid When |
|---|---|---|
| PostgreSQL | General purpose, JSON support, extensions | Simple key-value only |
| MySQL | Web applications, read-heavy | Complex queries, JSON-heavy |
| SQLite | Embedded, development, small apps | Concurrent writes, scale |
Document (NoSQL):
| Database | Best For | Avoid When |
|---|---|---|
| MongoDB | Flexible schema, rapid iteration | Complex transactions |
| CouchDB | Offline-first, sync required | High throughput |
Key-Value:
| Database | Best For | Avoid When |
|---|---|---|
| Redis | Caching, sessions, real-time | Persistence critical |
| DynamoDB | Serverless, auto-scaling | Complex queries |
Wide-Column:
| Database | Best For | Avoid When |
|---|---|---|
| Cassandra | Write-heavy, time-series | Complex queries, small scale |
| ScyllaDB | Cassandra alternative, performance | Small datasets |
Time-Series:
| Database | Best For | Avoid When |
|---|---|---|
| TimescaleDB | Time-series with SQL | Non-time-series data |
| InfluxDB | Metrics, monitoring | Relational queries |
Search:
| Database | Best For | Avoid When |
|---|---|---|
| Elasticsearch | Full-text search, logs | Primary data store |
| Meilisearch | Simple search, fast setup | Complex analytics |
Quick Decision Flow
Start │ ├─ Need ACID transactions? ──Yes──► PostgreSQL/MySQL │ ├─ Flexible schema needed? ──Yes──► MongoDB │ ├─ Write-heavy (>50K/sec)? ──Yes──► Cassandra/ScyllaDB │ ├─ Key-value access only? ──Yes──► Redis/DynamoDB │ ├─ Time-series data? ──Yes──► TimescaleDB/InfluxDB │ ├─ Full-text search? ──Yes──► Elasticsearch │ └─ Default ──────────────────────► PostgreSQL2. Caching Strategy
Cache Type Selection
| Type | Use Case | Invalidation | Complexity |
|---|---|---|---|
| Read-through | Frequent reads, tolerance for stale | On write/TTL | Low |
| Write-through | Data consistency critical | Automatic | Medium |
| Write-behind | High write throughput | Async | High |
| Cache-aside | Fine-grained control | Application | Medium |
Cache Technology Selection
| Technology | Best For | Limitations |
|---|---|---|
| Redis | General purpose, data structures | Memory cost |
| Memcached | Simple key-value, high throughput | No persistence |
| CDN (CloudFront, Fastly) | Static assets, edge caching | Dynamic content |
| Application cache | Per-instance, small data | Not distributed |
Cache Patterns
Cache-Aside (Lazy Loading):
Read:1. Check cache2. If miss, read from DB3. Store in cache4. Return data
Write:1. Write to DB2. Invalidate cacheWrite-Through:
Write:1. Write to cache2. Cache writes to DB3. Return success
Read:1. Read from cache (always hit)TTL Guidelines:
| Data Type | Suggested TTL |
|---|---|
| User sessions | 24-48 hours |
| API responses | 1-5 minutes |
| Static content | 24 hours - 1 week |
| Database queries | 5-60 minutes |
| Feature flags | 1-5 minutes |
3. Message Queue Selection
Queue Technology Comparison
| Feature | RabbitMQ | Kafka | SQS | Redis Streams |
|---|---|---|---|---|
| Throughput | Medium (10K/s) | Very High (100K+/s) | Medium | High |
| Ordering | Per-queue | Per-partition | FIFO optional | Per-stream |
| Durability | Configurable | Strong | Strong | Configurable |
| Replay | No | Yes | No | Yes |
| Complexity | Medium | High | Low | Low |
| Cost | Self-hosted | Self-hosted | Pay-per-use | Self-hosted |
Decision Matrix
| Requirement | Recommendation |
|---|---|
| Simple task queue | SQS or Redis |
| Event streaming | Kafka |
| Complex routing | RabbitMQ |
| Log aggregation | Kafka |
| Serverless integration | SQS |
| Real-time analytics | Kafka |
| Request/reply pattern | RabbitMQ |
When to Use Each
RabbitMQ:
- Complex routing logic (topic, fanout, headers)
- Request/reply patterns
- Priority queues
- Message acknowledgment critical
Kafka:
- Event sourcing
- High throughput requirements (>50K messages/sec)
- Message replay needed
- Stream processing
- Log aggregation
SQS:
- AWS-native applications
- Simple queue semantics
- Serverless architectures
- Don’t want to manage infrastructure
Redis Streams:
- Already using Redis
- Moderate throughput
- Simple streaming needs
- Real-time features
4. Authentication Strategy
Method Selection
| Method | Best For | Avoid When |
|---|---|---|
| Session-based | Traditional web apps, server-rendered | Mobile apps, microservices |
| JWT | SPAs, mobile apps, microservices | Need immediate revocation |
| OAuth 2.0 | Third-party access, social login | Internal-only apps |
| API Keys | Server-to-server, simple auth | User authentication |
| mTLS | Service mesh, high security | Public APIs |
JWT vs Sessions
| Factor | JWT | Sessions |
|---|---|---|
| Scalability | Stateless, easy to scale | Requires session store |
| Revocation | Difficult (need blocklist) | Immediate |
| Payload | Can contain claims | Server-side only |
| Security | Token in client | Server-controlled |
| Mobile friendly | Yes | Requires cookies |
OAuth 2.0 Flow Selection
| Flow | Use Case |
|---|---|
| Authorization Code | Web apps with backend |
| Authorization Code + PKCE | SPAs, mobile apps |
| Client Credentials | Machine-to-machine |
| Device Code | Smart TVs, CLI tools |
Avoid: Implicit flow (deprecated), Resource Owner Password (legacy only)
Token Lifetimes
| Token Type | Suggested Lifetime |
|---|---|
| Access token | 15-60 minutes |
| Refresh token | 7-30 days |
| API key | No expiry (rotate quarterly) |
| Session | 24 hours - 7 days |
5. Frontend Framework Selection
Framework Comparison
| Factor | React | Vue | Angular | Svelte |
|---|---|---|---|---|
| Learning curve | Medium | Low | High | Low |
| Ecosystem | Largest | Large | Complete | Growing |
| Performance | Good | Good | Good | Excellent |
| Bundle size | Medium | Small | Large | Smallest |
| TypeScript | Good | Good | Native | Good |
| Job market | Largest | Growing | Enterprise | Niche |
Decision Matrix
| Requirement | Recommendation |
|---|---|
| Large team, enterprise | Angular |
| Startup, rapid iteration | React or Vue |
| Performance critical | Svelte or Solid |
| Existing React team | React |
| Progressive enhancement | Vue or Svelte |
| Component library needed | React (most options) |
Meta-Framework Selection
| Framework | Best For |
|---|---|
| Next.js (React) | Full-stack React, SSR/SSG |
| Nuxt (Vue) | Full-stack Vue, SSR/SSG |
| SvelteKit | Full-stack Svelte |
| Remix | Data-heavy React apps |
| Astro | Content sites, multi-framework |
When to Use SSR vs SPA vs SSG
| Rendering | Use When |
|---|---|
| SSR | SEO critical, dynamic content, auth-gated |
| SPA | Internal tools, highly interactive, no SEO |
| SSG | Content sites, blogs, documentation |
| ISR | Mix of static and dynamic |
6. Cloud Provider Selection
Provider Comparison
| Factor | AWS | GCP | Azure |
|---|---|---|---|
| Market share | Largest | Growing | Enterprise strong |
| Service breadth | Most comprehensive | Strong ML/data | Best Microsoft integration |
| Pricing | Complex, volume discounts | Simpler, sustained use | EA discounts |
| Kubernetes | EKS | GKE (best managed) | AKS |
| Serverless | Lambda (mature) | Cloud Functions | Azure Functions |
| Database | RDS, DynamoDB | Cloud SQL, Spanner | SQL, Cosmos |
Decision Factors
| If You Need | Consider |
|---|---|
| Microsoft ecosystem | Azure |
| Best Kubernetes experience | GCP |
| Widest service selection | AWS |
| Machine learning focus | GCP or AWS |
| Government compliance | AWS GovCloud or Azure Gov |
| Startup credits | All offer programs |
Multi-Cloud Considerations
Go multi-cloud when:
- Regulatory requirements mandate it
- Specific service (e.g., GCP BigQuery) is best-in-class
- Negotiating leverage with vendors
Stay single-cloud when:
- Team is small
- Want to minimize complexity
- Deep integration needed
Service Mapping
| Need | AWS | GCP | Azure |
|---|---|---|---|
| Compute | EC2 | Compute Engine | Virtual Machines |
| Containers | ECS, EKS | GKE, Cloud Run | AKS, Container Apps |
| Serverless | Lambda | Cloud Functions | Azure Functions |
| Object Storage | S3 | Cloud Storage | Blob Storage |
| SQL Database | RDS | Cloud SQL | Azure SQL |
| NoSQL | DynamoDB | Firestore | Cosmos DB |
| CDN | CloudFront | Cloud CDN | Azure CDN |
| DNS | Route 53 | Cloud DNS | Azure DNS |
7. API Style Selection
REST vs GraphQL vs gRPC
| Factor | REST | GraphQL | gRPC |
|---|---|---|---|
| Use case | General purpose | Flexible queries | Microservices |
| Learning curve | Low | Medium | High |
| Over-fetching | Common | Solved | N/A |
| Caching | HTTP native | Complex | Custom |
| Browser support | Native | Native | Limited |
| Tooling | Mature | Growing | Strong |
| Performance | Good | Good | Excellent |
Decision Matrix
| Requirement | Recommendation |
|---|---|
| Public API | REST |
| Mobile apps with varied needs | GraphQL |
| Microservices communication | gRPC |
| Real-time updates | GraphQL subscriptions or WebSocket |
| File uploads | REST |
| Internal services only | gRPC |
| Third-party developers | REST + OpenAPI |
When to Choose Each
Choose REST when:
- Building public APIs
- Need HTTP caching
- Simple CRUD operations
- Team experienced with REST
Choose GraphQL when:
- Multiple clients with different data needs
- Rapid frontend iteration
- Complex, nested data relationships
- Want to reduce API calls
Choose gRPC when:
- Service-to-service communication
- Performance critical
- Streaming required
- Strong typing important
API Versioning Strategies
| Strategy | Pros | Cons |
|---|---|---|
URL path (/v1/) | Clear, easy to implement | URL pollution |
Query param (?version=1) | Flexible | Easy to miss |
Header (Accept-Version: 1) | Clean URLs | Less discoverable |
| No versioning (evolve) | Simple | Breaking changes risky |
Recommendation: URL path versioning for public APIs, header versioning for internal.
Quick Reference
| Decision | Default Choice | Alternative When |
|---|---|---|
| Database | PostgreSQL | Scale/flexibility → MongoDB, DynamoDB |
| Cache | Redis | Simple needs → Memcached |
| Queue | SQS (AWS) / RabbitMQ | Event streaming → Kafka |
| Auth | JWT + Refresh | Traditional web → Sessions |
| Frontend | React + Next.js | Simplicity → Vue, Performance → Svelte |
| Cloud | AWS | Microsoft shop → Azure, ML-first → GCP |
| API | REST | Mobile flexibility → GraphQL, Internal → gRPC |