Quick reference microservices patterns. Communication, resilience, data management, API gateway, service discovery, dan best practices.
Prinsip-prinsip fundamental yang menjadi dasar arsitektur microservices.
| Principle | Description |
|---|---|
| Single Responsibility | One service = one business capability |
| Autonomous | Independent deployment & database |
| Loose Coupling | Minimal dependencies between services |
| Resilient | Failures don't cascade |
| Observable | Comprehensive logging & monitoring |
Situasi-situasi dimana microservices cocok digunakan atau tidak cocok.
Good for:
Bad for:
Berbagai cara komunikasi antar microservices.
Komunikasi real-time menggunakan HTTP calls langsung.
// Service A calls Service B
const response = await fetch('http://service-b/api/endpoint');
const data = await response.json();Kelebihan: Simple, immediate response Kekurangan: Tight coupling, blocking, cascading failures
// Service A publishes event
await messageQueue.publish('order.created', { orderId: '123' });
// Service B subscribes
messageQueue.subscribe('order.created', async (event) => {
await processOrder(event.orderId);
});Kelebihan: Loose coupling, fault-tolerant, async Kekurangan: Complex, eventual consistency
// Define proto
service UserService {
rpc GetUser (UserRequest) returns (UserResponse);
}
// Client
const response = await client.getUser({ id: '123' });Kelebihan: Fast, type-safe, bidirectional streaming Kekurangan: More setup, HTTP/2 required
class CircuitBreaker {
state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
failures = 0;
threshold = 5;
async call(fn: Function) {
if (this.state === 'OPEN') {
throw new Error('Circuit breaker open');
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess() {
this.failures = 0;
this.state = 'CLOSED';
}
private onFailure() {
this.failures++;
if (this.failures >= this.threshold) {
this.state = 'OPEN';
setTimeout(() => this.state = 'HALF_OPEN', 60000);
}
}
}async function retry(fn: Function, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
await sleep(Math.pow(2, i) * 1000); // 1s, 2s, 4s
}
}
}async function withTimeout(fn: Function, ms = 5000) {
return Promise.race([
fn(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
)
]);
}async function getUser(id: string) {
try {
return await fetchFromService(id);
} catch (error) {
return getCachedUser(id); // Fallback
}
}Client → API Gateway → [Services]
API Gateway:
- Routing
- Authentication
- Rate limiting
- Request/response transformation
- Load balancing// Express.js example
import { createProxyMiddleware } from 'http-proxy-middleware';
app.use('/api/users', createProxyMiddleware({
target: 'http://user-service:3000',
changeOrigin: true
}));
app.use('/api/orders', createProxyMiddleware({
target: 'http://order-service:3001',
changeOrigin: true
}));const registry = {
'user-service': 'http://user-service:3000',
'order-service': 'http://order-service:3001'
};
const url = registry['user-service'];
const response = await fetch(`${url}/api/users/123`);apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user
ports:
- port: 80
targetPort: 3000User Service → PostgreSQL
Order Service → MongoDB
Payment Service → PostgreSQLManfaat: Autonomy, tech diversity, independent scaling Challenges: No joins, data consistency
Distributed transactions:
async function orderSaga(order) {
try {
// Step 1: Create order
const orderId = await orderService.create(order);
try {
// Step 2: Process payment
await paymentService.charge(order.amount);
try {
// Step 3: Reserve inventory
await inventoryService.reserve(order.items);
await orderService.confirm(orderId);
} catch {
await paymentService.refund(order.amount);
throw error;
}
} catch {
await orderService.cancel(orderId);
throw error;
}
} catch (error) {
console.error('Saga failed:', error);
}
}// Store events
const events = [
{ type: 'OrderCreated', orderId: '123', amount: 100 },
{ type: 'PaymentProcessed', orderId: '123' },
{ type: 'OrderShipped', orderId: '123' }
];
// Rebuild state from events
function getOrderState(orderId) {
return events
.filter(e => e.orderId === orderId)
.reduce((state, event) => applyEvent(state, event), {});
}// Write model
class OrderCommandService {
async createOrder(data) {
const order = await this.db.insert(data);
await this.eventBus.publish('order.created', order);
return order;
}
}
// Read model (optimized for queries)
class OrderQueryService {
async getOrder(id) {
return this.readDb.findById(id); // Denormalized data
}
async getOrdersByUser(userId) {
return this.readDb.find({ userId });
}
}// Generate trace ID
const traceId = generateUUID();
// Pass to downstream services
await fetch('http://service-b/api/endpoint', {
headers: {
'X-Trace-Id': traceId
}
});
// Log with trace ID
logger.info('Order created', {
traceId,
orderId: order.id,
service: 'order-service'
});logger.info('User logged in', {
userId: user.id,
timestamp: new Date(),
service: 'auth-service',
traceId: req.headers['x-trace-id']
});app.get('/health', async (req, res) => {
const health = {
status: 'healthy',
timestamp: new Date(),
checks: {
database: await checkDatabase(),
redis: await checkRedis(),
messageQueue: await checkQueue()
}
};
const isHealthy = Object.values(health.checks).every(c => c === 'ok');
res.status(isHealthy ? 200 : 503).json(health);
});Blue (old) → 100% traffic
Green (new) → 0% traffic
Deploy Green → Test
Switch traffic:
Blue → 0%
Green → 100%v1.0 → 90% traffic
v2.0 → 10% traffic (canary)
If metrics good:
v1.0 → 50%
v2.0 → 50%
Then:
v1.0 → 0%
v2.0 → 100%3 instances:
1. Update instance 1 → wait
2. Update instance 2 → wait
3. Update instance 3 → done// API Gateway validates JWT
app.use(async (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
try {
const decoded = jwt.verify(token, SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Unauthorized' });
}
});// Use mTLS or service tokens
const response = await fetch('http://service-b/api/endpoint', {
headers: {
'Authorization': `Bearer ${serviceToken}`,
'X-Service-Name': 'service-a'
}
});/api/v1/users)Distributed Monolith
Nanoservices
Shared Database
No API Gateway
Ignoring Failures
1. Identify module to extract
2. Build microservice alongside monolith
3. Route some traffic to new service
4. Gradually increase traffic
5. Remove code from monolith
6. Repeat for next moduleMonolith (100%) → Monolith (90%) + Service A (10%)
→ Monolith (50%) + Service A (50%)
→ Monolith (0%) + Service A (100%)| Factor | Monolith | Microservices |
|---|---|---|
| Team size | < 5 | > 10 |
| Complexity | Simple | Complex |
| Deployment frequency | Weeks/months | Daily/hourly |
| Scaling needs | Uniform | Variable |
| Tech diversity | Single stack | Multiple stacks |
| Time to market | Fast | Slower initially |
| Operational overhead | Low | High |
Happy architecting! 🏗️