455 lines
12 KiB
Markdown
455 lines
12 KiB
Markdown
# Backend Architecture Patterns
|
|
|
|
Microservices, event-driven architecture, and scalability patterns (2025).
|
|
|
|
## Monolith vs Microservices
|
|
|
|
### Monolithic Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────┐
|
|
│ Single Application │
|
|
│ │
|
|
│ ┌─────────┐ ┌──────────┐ │
|
|
│ │ Users │ │ Products │ │
|
|
│ └─────────┘ └──────────┘ │
|
|
│ ┌─────────┐ ┌──────────┐ │
|
|
│ │ Orders │ │ Payments │ │
|
|
│ └─────────┘ └──────────┘ │
|
|
│ │
|
|
│ Single Database │
|
|
└─────────────────────────────────┘
|
|
```
|
|
|
|
**Pros:**
|
|
- Simple to develop and deploy
|
|
- Easy local testing
|
|
- Single codebase
|
|
- Strong consistency (ACID transactions)
|
|
|
|
**Cons:**
|
|
- Tight coupling
|
|
- Scaling limitations
|
|
- Deployment risk (all-or-nothing)
|
|
- Tech stack lock-in
|
|
|
|
**When to Use:** Startups, MVPs, small teams, unclear domain boundaries
|
|
|
|
### Microservices Architecture
|
|
|
|
```
|
|
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
│ User │ │ Product │ │ Order │ │ Payment │
|
|
│ Service │ │ Service │ │ Service │ │ Service │
|
|
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
|
|
│ │ │ │
|
|
┌──▼──┐ ┌──▼──┐ ┌──▼──┐ ┌──▼──┐
|
|
│ DB │ │ DB │ │ DB │ │ DB │
|
|
└─────┘ └─────┘ └─────┘ └─────┘
|
|
```
|
|
|
|
**Pros:**
|
|
- Independent deployment
|
|
- Technology flexibility
|
|
- Fault isolation
|
|
- Easier scaling (scale services independently)
|
|
|
|
**Cons:**
|
|
- Complex deployment
|
|
- Distributed system challenges (network latency, partial failures)
|
|
- Data consistency (eventual consistency)
|
|
- Operational overhead
|
|
|
|
**When to Use:** Large teams, clear domain boundaries, need independent scaling, tech diversity
|
|
|
|
## Microservices Patterns
|
|
|
|
### Database per Service Pattern
|
|
|
|
**Concept:** Each service owns its database
|
|
|
|
```
|
|
User Service → User DB (PostgreSQL)
|
|
Product Service → Product DB (MongoDB)
|
|
Order Service → Order DB (PostgreSQL)
|
|
```
|
|
|
|
**Benefits:**
|
|
- Service independence
|
|
- Technology choice per service
|
|
- Fault isolation
|
|
|
|
**Challenges:**
|
|
- No joins across services
|
|
- Distributed transactions
|
|
- Data duplication
|
|
|
|
### API Gateway Pattern
|
|
|
|
```
|
|
Client
|
|
│
|
|
▼
|
|
┌─────────────────┐
|
|
│ API Gateway │ - Authentication
|
|
│ (Kong/NGINX) │ - Rate limiting
|
|
└────────┬────────┘ - Request routing
|
|
│
|
|
┌────┴────┬────────┬────────┐
|
|
▼ ▼ ▼ ▼
|
|
User Product Order Payment
|
|
Service Service Service Service
|
|
```
|
|
|
|
**Responsibilities:**
|
|
- Request routing
|
|
- Authentication/authorization
|
|
- Rate limiting
|
|
- Request/response transformation
|
|
- Caching
|
|
|
|
**Implementation (Kong):**
|
|
```yaml
|
|
services:
|
|
- name: user-service
|
|
url: http://user-service:3000
|
|
routes:
|
|
- name: user-route
|
|
paths:
|
|
- /api/users
|
|
|
|
- name: product-service
|
|
url: http://product-service:3001
|
|
routes:
|
|
- name: product-route
|
|
paths:
|
|
- /api/products
|
|
|
|
plugins:
|
|
- name: rate-limiting
|
|
config:
|
|
minute: 100
|
|
- name: jwt
|
|
```
|
|
|
|
### Service Discovery
|
|
|
|
**Concept:** Services find each other dynamically
|
|
|
|
```typescript
|
|
// Consul service discovery
|
|
import Consul from 'consul';
|
|
|
|
const consul = new Consul();
|
|
|
|
// Register service
|
|
await consul.agent.service.register({
|
|
name: 'user-service',
|
|
address: '192.168.1.10',
|
|
port: 3000,
|
|
check: {
|
|
http: 'http://192.168.1.10:3000/health',
|
|
interval: '10s',
|
|
},
|
|
});
|
|
|
|
// Discover service
|
|
const services = await consul.catalog.service.nodes('product-service');
|
|
const productServiceUrl = `http://${services[0].ServiceAddress}:${services[0].ServicePort}`;
|
|
```
|
|
|
|
### Circuit Breaker Pattern
|
|
|
|
**Concept:** Stop calling failing service, prevent cascade failures
|
|
|
|
```typescript
|
|
import CircuitBreaker from 'opossum';
|
|
|
|
const breaker = new CircuitBreaker(callExternalService, {
|
|
timeout: 3000, // 3s timeout
|
|
errorThresholdPercentage: 50, // Open circuit after 50% failures
|
|
resetTimeout: 30000, // Try again after 30s
|
|
});
|
|
|
|
breaker.on('open', () => {
|
|
console.log('Circuit breaker opened!');
|
|
});
|
|
|
|
breaker.fallback(() => ({
|
|
data: 'fallback-response',
|
|
source: 'cache',
|
|
}));
|
|
|
|
const result = await breaker.fire(requestParams);
|
|
```
|
|
|
|
**States:**
|
|
- **Closed:** Normal operation, requests go through
|
|
- **Open:** Too many failures, requests fail immediately
|
|
- **Half-Open:** Testing if service recovered
|
|
|
|
### Saga Pattern (Distributed Transactions)
|
|
|
|
**Choreography-Based Saga:**
|
|
```
|
|
Order Service: Create Order → Publish "OrderCreated"
|
|
↓
|
|
Payment Service: Reserve Payment → Publish "PaymentReserved"
|
|
↓
|
|
Inventory Service: Reserve Stock → Publish "StockReserved"
|
|
↓
|
|
Shipping Service: Create Shipment → Publish "ShipmentCreated"
|
|
|
|
If any step fails → Compensating transactions (rollback)
|
|
```
|
|
|
|
**Orchestration-Based Saga:**
|
|
```
|
|
Saga Orchestrator
|
|
↓ Create Order
|
|
Order Service
|
|
↓ Reserve Payment
|
|
Payment Service
|
|
↓ Reserve Stock
|
|
Inventory Service
|
|
↓ Create Shipment
|
|
Shipping Service
|
|
```
|
|
|
|
## Event-Driven Architecture
|
|
|
|
**Impact:** 85% organizations recognize business value
|
|
|
|
### Event Sourcing
|
|
|
|
**Concept:** Store events, not current state
|
|
|
|
```typescript
|
|
// Traditional: Store current state
|
|
{
|
|
userId: '123',
|
|
balance: 500
|
|
}
|
|
|
|
// Event Sourcing: Store events
|
|
[
|
|
{ type: 'AccountCreated', userId: '123', timestamp: '...' },
|
|
{ type: 'MoneyDeposited', amount: 1000, timestamp: '...' },
|
|
{ type: 'MoneyWithdrawn', amount: 500, timestamp: '...' },
|
|
]
|
|
|
|
// Reconstruct state by replaying events
|
|
const balance = events
|
|
.filter(e => e.userId === '123')
|
|
.reduce((acc, event) => {
|
|
if (event.type === 'MoneyDeposited') return acc + event.amount;
|
|
if (event.type === 'MoneyWithdrawn') return acc - event.amount;
|
|
return acc;
|
|
}, 0);
|
|
```
|
|
|
|
**Benefits:**
|
|
- Complete audit trail
|
|
- Temporal queries (state at any point in time)
|
|
- Event replay for debugging
|
|
- Flexible projections
|
|
|
|
### Message Broker Patterns
|
|
|
|
**Kafka (Event Streaming):**
|
|
```typescript
|
|
import { Kafka } from 'kafkajs';
|
|
|
|
const kafka = new Kafka({
|
|
clientId: 'order-service',
|
|
brokers: ['kafka:9092'],
|
|
});
|
|
|
|
// Producer
|
|
const producer = kafka.producer();
|
|
await producer.send({
|
|
topic: 'order-events',
|
|
messages: [
|
|
{
|
|
key: order.id,
|
|
value: JSON.stringify({
|
|
type: 'OrderCreated',
|
|
orderId: order.id,
|
|
userId: order.userId,
|
|
total: order.total,
|
|
}),
|
|
},
|
|
],
|
|
});
|
|
|
|
// Consumer
|
|
const consumer = kafka.consumer({ groupId: 'inventory-service' });
|
|
await consumer.subscribe({ topic: 'order-events' });
|
|
await consumer.run({
|
|
eachMessage: async ({ topic, partition, message }) => {
|
|
const event = JSON.parse(message.value.toString());
|
|
if (event.type === 'OrderCreated') {
|
|
await reserveInventory(event.orderId);
|
|
}
|
|
},
|
|
});
|
|
```
|
|
|
|
**RabbitMQ (Task Queues):**
|
|
```typescript
|
|
import amqp from 'amqplib';
|
|
|
|
const connection = await amqp.connect('amqp://localhost');
|
|
const channel = await connection.createChannel();
|
|
|
|
// Producer
|
|
await channel.assertQueue('email-queue', { durable: true });
|
|
channel.sendToQueue('email-queue', Buffer.from(JSON.stringify({
|
|
to: user.email,
|
|
subject: 'Welcome!',
|
|
body: 'Thank you for signing up',
|
|
})));
|
|
|
|
// Consumer
|
|
await channel.consume('email-queue', async (msg) => {
|
|
const emailData = JSON.parse(msg.content.toString());
|
|
await sendEmail(emailData);
|
|
channel.ack(msg);
|
|
});
|
|
```
|
|
|
|
## CQRS (Command Query Responsibility Segregation)
|
|
|
|
**Concept:** Separate read and write models
|
|
|
|
```
|
|
Write Side (Commands): Read Side (Queries):
|
|
CreateOrder GetOrderById
|
|
UpdateOrder GetUserOrders
|
|
↓ ↑
|
|
┌─────────┐ ┌─────────┐
|
|
│ Write │ → Events → │ Read │
|
|
│ DB │ (sync) │ DB │
|
|
│(Postgres) │(MongoDB)│
|
|
└─────────┘ └─────────┘
|
|
```
|
|
|
|
**Benefits:**
|
|
- Optimized read models
|
|
- Scalable (scale reads independently)
|
|
- Flexible (different DB for reads/writes)
|
|
|
|
**Implementation:**
|
|
```typescript
|
|
// Command (Write)
|
|
class CreateOrderCommand {
|
|
constructor(public userId: string, public items: OrderItem[]) {}
|
|
}
|
|
|
|
class CreateOrderHandler {
|
|
async execute(command: CreateOrderCommand) {
|
|
const order = await Order.create(command);
|
|
await eventBus.publish(new OrderCreatedEvent(order));
|
|
return order.id;
|
|
}
|
|
}
|
|
|
|
// Query (Read)
|
|
class GetOrderQuery {
|
|
constructor(public orderId: string) {}
|
|
}
|
|
|
|
class GetOrderHandler {
|
|
async execute(query: GetOrderQuery) {
|
|
// Read from optimized read model
|
|
return await OrderReadModel.findById(query.orderId);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Scalability Patterns
|
|
|
|
### Horizontal Scaling (Scale Out)
|
|
|
|
```
|
|
Load Balancer
|
|
↓
|
|
┌───┴───┬───────┬───────┐
|
|
│ App 1 │ App 2 │ App 3 │ ... App N
|
|
└───┬───┴───┬───┴───┬───┘
|
|
└───────┴───────┘
|
|
↓
|
|
Shared Database
|
|
(with read replicas)
|
|
```
|
|
|
|
### Database Sharding
|
|
|
|
**Range-Based Sharding:**
|
|
```
|
|
Users 1-1M → Shard 1
|
|
Users 1M-2M → Shard 2
|
|
Users 2M-3M → Shard 3
|
|
```
|
|
|
|
**Hash-Based Sharding:**
|
|
```typescript
|
|
function getShardId(userId: string): number {
|
|
const hash = crypto.createHash('md5').update(userId).digest('hex');
|
|
return parseInt(hash.substring(0, 8), 16) % SHARD_COUNT;
|
|
}
|
|
|
|
const shardId = getShardId(userId);
|
|
const db = shards[shardId];
|
|
const user = await db.users.findById(userId);
|
|
```
|
|
|
|
### Caching Layers
|
|
|
|
```
|
|
Client
|
|
→ CDN (static assets)
|
|
→ API Gateway Cache (public endpoints)
|
|
→ Application Cache (Redis - user sessions, hot data)
|
|
→ Database Query Cache
|
|
→ Database
|
|
```
|
|
|
|
## Architecture Decision Matrix
|
|
|
|
| Pattern | When to Use | Complexity | Benefits |
|
|
|---------|-------------|------------|----------|
|
|
| **Monolith** | Small team, MVP, unclear boundaries | Low | Simple, fast development |
|
|
| **Microservices** | Large team, clear domains, need scaling | High | Independent deployment, fault isolation |
|
|
| **Event-Driven** | Async workflows, audit trail needed | Moderate | Decoupling, scalability |
|
|
| **CQRS** | Different read/write patterns | High | Optimized queries, scalability |
|
|
| **Serverless** | Spiky traffic, event-driven | Low | Auto-scaling, pay-per-use |
|
|
|
|
## Anti-Patterns to Avoid
|
|
|
|
1. **Distributed Monolith** - Microservices that all depend on each other
|
|
2. **Chatty Services** - Too many inter-service calls (network overhead)
|
|
3. **Shared Database** - Microservices sharing same DB (tight coupling)
|
|
4. **Over-Engineering** - Using microservices for small apps
|
|
5. **No Circuit Breakers** - Cascade failures in distributed systems
|
|
|
|
## Architecture Checklist
|
|
|
|
- [ ] Clear service boundaries (domain-driven design)
|
|
- [ ] Database per service (no shared databases)
|
|
- [ ] API Gateway for client requests
|
|
- [ ] Service discovery configured
|
|
- [ ] Circuit breakers for resilience
|
|
- [ ] Event-driven communication (Kafka/RabbitMQ)
|
|
- [ ] CQRS for read-heavy systems
|
|
- [ ] Distributed tracing (Jaeger/OpenTelemetry)
|
|
- [ ] Health checks for all services
|
|
- [ ] Horizontal scaling capability
|
|
|
|
## Resources
|
|
|
|
- **Microservices Patterns:** https://microservices.io/patterns/
|
|
- **Martin Fowler - Microservices:** https://martinfowler.com/articles/microservices.html
|
|
- **Event-Driven Architecture:** https://aws.amazon.com/event-driven-architecture/
|
|
- **CQRS Pattern:** https://martinfowler.com/bliki/CQRS.html
|