Skip to content

ADR Authoring — Architecture Patterns, Workflows, and Decision Guides

FieldValue
TypeSkill Resource
Source~/.copilot/skills/architecture/references/adr-authoring.md
DescriptionNot 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

  1. Monolithic Architecture
  2. Modular Monolith
  3. Microservices Architecture
  4. Event-Driven Architecture
  5. CQRS (Command Query Responsibility Segregation)
  6. Event Sourcing
  7. Hexagonal Architecture (Ports & Adapters)
  8. Clean Architecture
  9. 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:

ProsCons
Simple deploymentScaling is all-or-nothing
Easy debuggingLarge codebase becomes unwieldy
No network latency between componentsSingle point of failure
Simple testingTechnology 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:

ProsCons
Clear module boundariesStill single deployment
Easier to extract services laterRequires discipline to maintain boundaries
Single database simplifies transactionsCan 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:

ProsCons
Independent deploymentNetwork complexity
Independent scalingDistributed system challenges
Technology flexibilityOperational overhead
Team autonomyData consistency challenges
Fault isolationTesting 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:

ProsCons
Loose couplingEventual consistency
ScalabilityDebugging complexity
Audit trail built-inMessage ordering challenges
Easy to add new consumersInfrastructure 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:

ProsCons
Optimized read modelsEventual consistency between models
Independent scalingComplexity
Simplified queriesSynchronization logic
Better performanceMore 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:

ProsCons
Complete audit trailStorage grows indefinitely
Time-travel debuggingQuery complexity
Natural fit for event-drivenLearning curve
Enables CQRSEventual 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:

ProsCons
Business logic isolationMore abstractions
Highly testableInitial setup overhead
External systems are swappableCan be over-engineered
Clear boundariesLearning 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:

ProsCons
Framework independenceMore code
Testable business logicCan feel over-engineered
Clear dependency directionLearning curve
Flexible delivery mechanismsInitial 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:

ProsCons
Single entry pointSingle point of failure
Cross-cutting concerns centralizedAdditional latency
Backend service abstractionComplexity
Client-specific APIsCan 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 teamMonolith
Clear boundaries, future flexibilityModular Monolith
Independent deployment/scalingMicroservices
Loose coupling, async processingEvent-Driven
Separate read/write optimizationCQRS
Complete audit trailEvent Sourcing
Testable, swappable externalsHexagonal
Framework independenceClean Architecture
Single entry point, multiple servicesAPI Gateway

Part 2 — System Design Workflows

Step-by-step workflows for common system design tasks.

Workflows Index

  1. System Design Interview Approach
  2. Capacity Planning Workflow
  3. API Design Workflow
  4. Database Schema Design
  5. Scalability Assessment
  6. 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 users
DAU: 1M daily active users
Requests: 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

MetricCurrent6 months1 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,600
Requests per server: 500 (conservative)
Servers needed: 3,600 / 500 = 8 servers
With redundancy (N+2): 10 servers

CPU estimation:

Per request: 50ms CPU time
Peak RPS: 3,600
CPU cores: 3,600 × 0.05 = 180 cores
With headroom (70% target utilization):
180 / 0.7 = 257 cores
= 32 servers × 8 cores

Step 3: Calculate Storage Requirements

Database storage:

Records per day: 100,000
Record size: 2KB
Daily growth: 200MB
With indexes (2x): 400MB/day
Retention (1 year): 146GB
With replication (3x): 438GB

File storage:

Files per day: 10,000
Average file size: 500KB
Daily growth: 5GB
Retention (1 year): 1.8TB

Step 4: Calculate Network Requirements

Bandwidth:

Response size: 10KB average
Peak RPS: 3,600
Outbound: 3,600 × 10KB = 36MB/s = 288 Mbps
With headroom (50%): 432 Mbps ≈ 500 Mbps connection

Step 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
- Reviews

Step 2: Define Operations

Map CRUD to HTTP methods:

OperationHTTP MethodURL Pattern
ListGET/resources
Get oneGET/resources/{id}
CreatePOST/resources
UpdatePUT/PATCH/resources/{id}
DeleteDELETE/resources/{id}

Step 3: Design Request/Response Formats

Request example:

POST /api/v1/orders
Content-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 Created
Content-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 Request
Content-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 StatusUse Case
400Validation errors
401Authentication required
403Permission denied
404Resource not found
409Conflict (duplicate, etc.)
429Rate limit exceeded
500Internal 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:

TypeProsCons
Auto-incrementSimple, orderedNot distributed-friendly
UUIDGlobally uniqueLarger, random
ULIDGlobally unique, sortableLarger

Step 4: Add Indexes

Index selection rules:

-- Index columns used in WHERE clauses
CREATE INDEX idx_orders_user_id ON orders(user_id);
-- Index columns used in JOINs
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
-- Index columns used in ORDER BY with WHERE
CREATE 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:

LayerBottleneck Signs
Web serversHigh CPU, connection limits
ApplicationSlow requests, thread pool exhaustion
DatabaseSlow queries, lock contention
CacheHigh miss rate, memory pressure
NetworkBandwidth saturation, latency

Step 3: Load Test

Test scenarios:

1. Baseline: Current production load
2. 2x load: Expected growth in 6 months
3. 5x load: Stress test
4. Spike: Sudden 10x for 5 minutes

Tools:

  • 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 servers
2. Update load balancer
3. Verify health checks pass
Rollback:
1. Remove added servers
2. Update load balancer
3. Investigate issue

6. 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:

StrategyRiskDowntimeComplexity
Big bangHighYesLow
Blue-greenMediumMinimalMedium
CanaryLowNoneHigh
Strangler figLowNoneHigh

Strangler fig pattern (recommended for large systems):

1. Add facade in front of old system
2. Route small percentage of traffic to new system
3. Gradually increase traffic to new system
4. Retire old system when 100% migrated

Step 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 database
2. Sync any new data back
3. Investigate root cause
Rollback time estimate: 15 minutes

Step 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 learned

Quick Reference

TaskStart Here
New system designSystem Design Interview Approach
Infrastructure sizingCapacity Planning
New APIAPI Design
Database designDatabase Schema Design
Handle growthScalability Assessment
System migrationMigration Planning

Part 3 — Technology Decision Guide

Decision frameworks and comparison matrices for common technology choices.

Decision Frameworks Index

  1. Database Selection
  2. Caching Strategy
  3. Message Queue Selection
  4. Authentication Strategy
  5. Frontend Framework Selection
  6. Cloud Provider Selection
  7. API Style Selection

1. Database Selection

SQL vs NoSQL Decision Matrix

FactorChoose SQLChoose NoSQL
Data relationshipsComplex, many-to-manySimple, denormalized OK
SchemaWell-defined, stableEvolving, flexible
TransactionsACID requiredEventual consistency OK
Query patternsComplex joins, aggregationsKey-value, document lookups
ScaleVertical (some horizontal)Horizontal first
Team expertiseStrong SQL skillsDocument/KV experience

Database Type Selection

Relational (SQL):

DatabaseBest ForAvoid When
PostgreSQLGeneral purpose, JSON support, extensionsSimple key-value only
MySQLWeb applications, read-heavyComplex queries, JSON-heavy
SQLiteEmbedded, development, small appsConcurrent writes, scale

Document (NoSQL):

DatabaseBest ForAvoid When
MongoDBFlexible schema, rapid iterationComplex transactions
CouchDBOffline-first, sync requiredHigh throughput

Key-Value:

DatabaseBest ForAvoid When
RedisCaching, sessions, real-timePersistence critical
DynamoDBServerless, auto-scalingComplex queries

Wide-Column:

DatabaseBest ForAvoid When
CassandraWrite-heavy, time-seriesComplex queries, small scale
ScyllaDBCassandra alternative, performanceSmall datasets

Time-Series:

DatabaseBest ForAvoid When
TimescaleDBTime-series with SQLNon-time-series data
InfluxDBMetrics, monitoringRelational queries

Search:

DatabaseBest ForAvoid When
ElasticsearchFull-text search, logsPrimary data store
MeilisearchSimple search, fast setupComplex 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 ──────────────────────► PostgreSQL

2. Caching Strategy

Cache Type Selection

TypeUse CaseInvalidationComplexity
Read-throughFrequent reads, tolerance for staleOn write/TTLLow
Write-throughData consistency criticalAutomaticMedium
Write-behindHigh write throughputAsyncHigh
Cache-asideFine-grained controlApplicationMedium

Cache Technology Selection

TechnologyBest ForLimitations
RedisGeneral purpose, data structuresMemory cost
MemcachedSimple key-value, high throughputNo persistence
CDN (CloudFront, Fastly)Static assets, edge cachingDynamic content
Application cachePer-instance, small dataNot distributed

Cache Patterns

Cache-Aside (Lazy Loading):

Read:
1. Check cache
2. If miss, read from DB
3. Store in cache
4. Return data
Write:
1. Write to DB
2. Invalidate cache

Write-Through:

Write:
1. Write to cache
2. Cache writes to DB
3. Return success
Read:
1. Read from cache (always hit)

TTL Guidelines:

Data TypeSuggested TTL
User sessions24-48 hours
API responses1-5 minutes
Static content24 hours - 1 week
Database queries5-60 minutes
Feature flags1-5 minutes

3. Message Queue Selection

Queue Technology Comparison

FeatureRabbitMQKafkaSQSRedis Streams
ThroughputMedium (10K/s)Very High (100K+/s)MediumHigh
OrderingPer-queuePer-partitionFIFO optionalPer-stream
DurabilityConfigurableStrongStrongConfigurable
ReplayNoYesNoYes
ComplexityMediumHighLowLow
CostSelf-hostedSelf-hostedPay-per-useSelf-hosted

Decision Matrix

RequirementRecommendation
Simple task queueSQS or Redis
Event streamingKafka
Complex routingRabbitMQ
Log aggregationKafka
Serverless integrationSQS
Real-time analyticsKafka
Request/reply patternRabbitMQ

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

MethodBest ForAvoid When
Session-basedTraditional web apps, server-renderedMobile apps, microservices
JWTSPAs, mobile apps, microservicesNeed immediate revocation
OAuth 2.0Third-party access, social loginInternal-only apps
API KeysServer-to-server, simple authUser authentication
mTLSService mesh, high securityPublic APIs

JWT vs Sessions

FactorJWTSessions
ScalabilityStateless, easy to scaleRequires session store
RevocationDifficult (need blocklist)Immediate
PayloadCan contain claimsServer-side only
SecurityToken in clientServer-controlled
Mobile friendlyYesRequires cookies

OAuth 2.0 Flow Selection

FlowUse Case
Authorization CodeWeb apps with backend
Authorization Code + PKCESPAs, mobile apps
Client CredentialsMachine-to-machine
Device CodeSmart TVs, CLI tools

Avoid: Implicit flow (deprecated), Resource Owner Password (legacy only)

Token Lifetimes

Token TypeSuggested Lifetime
Access token15-60 minutes
Refresh token7-30 days
API keyNo expiry (rotate quarterly)
Session24 hours - 7 days

5. Frontend Framework Selection

Framework Comparison

FactorReactVueAngularSvelte
Learning curveMediumLowHighLow
EcosystemLargestLargeCompleteGrowing
PerformanceGoodGoodGoodExcellent
Bundle sizeMediumSmallLargeSmallest
TypeScriptGoodGoodNativeGood
Job marketLargestGrowingEnterpriseNiche

Decision Matrix

RequirementRecommendation
Large team, enterpriseAngular
Startup, rapid iterationReact or Vue
Performance criticalSvelte or Solid
Existing React teamReact
Progressive enhancementVue or Svelte
Component library neededReact (most options)

Meta-Framework Selection

FrameworkBest For
Next.js (React)Full-stack React, SSR/SSG
Nuxt (Vue)Full-stack Vue, SSR/SSG
SvelteKitFull-stack Svelte
RemixData-heavy React apps
AstroContent sites, multi-framework

When to Use SSR vs SPA vs SSG

RenderingUse When
SSRSEO critical, dynamic content, auth-gated
SPAInternal tools, highly interactive, no SEO
SSGContent sites, blogs, documentation
ISRMix of static and dynamic

6. Cloud Provider Selection

Provider Comparison

FactorAWSGCPAzure
Market shareLargestGrowingEnterprise strong
Service breadthMost comprehensiveStrong ML/dataBest Microsoft integration
PricingComplex, volume discountsSimpler, sustained useEA discounts
KubernetesEKSGKE (best managed)AKS
ServerlessLambda (mature)Cloud FunctionsAzure Functions
DatabaseRDS, DynamoDBCloud SQL, SpannerSQL, Cosmos

Decision Factors

If You NeedConsider
Microsoft ecosystemAzure
Best Kubernetes experienceGCP
Widest service selectionAWS
Machine learning focusGCP or AWS
Government complianceAWS GovCloud or Azure Gov
Startup creditsAll 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

NeedAWSGCPAzure
ComputeEC2Compute EngineVirtual Machines
ContainersECS, EKSGKE, Cloud RunAKS, Container Apps
ServerlessLambdaCloud FunctionsAzure Functions
Object StorageS3Cloud StorageBlob Storage
SQL DatabaseRDSCloud SQLAzure SQL
NoSQLDynamoDBFirestoreCosmos DB
CDNCloudFrontCloud CDNAzure CDN
DNSRoute 53Cloud DNSAzure DNS

7. API Style Selection

REST vs GraphQL vs gRPC

FactorRESTGraphQLgRPC
Use caseGeneral purposeFlexible queriesMicroservices
Learning curveLowMediumHigh
Over-fetchingCommonSolvedN/A
CachingHTTP nativeComplexCustom
Browser supportNativeNativeLimited
ToolingMatureGrowingStrong
PerformanceGoodGoodExcellent

Decision Matrix

RequirementRecommendation
Public APIREST
Mobile apps with varied needsGraphQL
Microservices communicationgRPC
Real-time updatesGraphQL subscriptions or WebSocket
File uploadsREST
Internal services onlygRPC
Third-party developersREST + 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

StrategyProsCons
URL path (/v1/)Clear, easy to implementURL pollution
Query param (?version=1)FlexibleEasy to miss
Header (Accept-Version: 1)Clean URLsLess discoverable
No versioning (evolve)SimpleBreaking changes risky

Recommendation: URL path versioning for public APIs, header versioning for internal.


Quick Reference

DecisionDefault ChoiceAlternative When
DatabasePostgreSQLScale/flexibility → MongoDB, DynamoDB
CacheRedisSimple needs → Memcached
QueueSQS (AWS) / RabbitMQEvent streaming → Kafka
AuthJWT + RefreshTraditional web → Sessions
FrontendReact + Next.jsSimplicity → Vue, Performance → Svelte
CloudAWSMicrosoft shop → Azure, ML-first → GCP
APIRESTMobile flexibility → GraphQL, Internal → gRPC