init
This commit is contained in:
@@ -0,0 +1,454 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user