ในสถาปัตยกรรม Microservices แต่ละ Service ทำงานเป็นอิสระ แต่ต้องสื่อสารกันเพื่อให้ระบบทำงานครบถ้วน การเลือก Communication Pattern ที่ถูกต้องเป็นหัวใจสำคัญที่กำหนดทั้ง Performance, Reliability, Scalability และ Maintainability ของระบบทั้งหมด
บทความนี้จะอธิบาย Communication Patterns ทั้งแบบ Synchronous และ Asynchronous, API Gateway, Service Mesh, Resilience Patterns และวิธีเลือก Pattern ที่เหมาะสม
Synchronous Communication
Synchronous คือ Service A เรียก Service B แล้วรอคำตอบ เหมาะกับกรณีที่ต้องการ Response ทันที
1. REST (HTTP/JSON)
รูปแบบพื้นฐานที่นิยมที่สุด ใช้ HTTP Methods (GET, POST, PUT, DELETE) กับ JSON Payload
// Order Service เรียก User Service
// GET /api/users/123
const response = await fetch('http://user-service:3000/api/users/123');
const user = await response.json();
// ข้อดี: เรียบง่าย, ทุกภาษา Support, Debug ง่าย
// ข้อเสีย: Latency สูง (HTTP overhead), Tight Coupling, ไม่ Efficient สำหรับ Binary Data
2. gRPC
High-performance RPC Framework จาก Google ใช้ Protocol Buffers (Protobuf) เป็น Interface Definition Language
// user.proto — Schema Definition
syntax = "proto3";
service UserService {
rpc GetUser (UserRequest) returns (UserResponse);
rpc ListUsers (ListRequest) returns (stream UserResponse); // Server Streaming
}
message UserRequest {
string id = 1;
}
message UserResponse {
string id = 1;
string name = 2;
string email = 3;
}
// Go Client
conn, _ := grpc.Dial("user-service:50051", grpc.WithInsecure())
client := pb.NewUserServiceClient(conn)
user, err := client.GetUser(ctx, &pb.UserRequest{Id: "123"})
| Feature | REST | gRPC |
|---|---|---|
| Protocol | HTTP/1.1 or 2 | HTTP/2 |
| Format | JSON (text) | Protobuf (binary) |
| Performance | ปานกลาง | สูงมาก (10x faster) |
| Streaming | ไม่มี native | Bi-directional |
| Code Generation | ต้อง manual | Auto จาก .proto |
| Browser Support | ดี | ต้องใช้ gRPC-Web |
3. GraphQL
# Query — ขอเฉพาะ Field ที่ต้องการ
query {
user(id: "123") {
name
email
orders {
id
total
}
}
}
# ข้อดี: ไม่ Over-fetch, Single Endpoint, Strong Typing
# ข้อเสีย: ซับซ้อนกว่า REST, N+1 Problem, Caching ยากกว่า
Asynchronous Communication
Asynchronous คือ Service A ส่ง Message แล้วไม่ต้องรอ Response เหมาะกับ Background Processing, Event-Driven Workflows และ Decoupling
1. Message Queue
Producer ส่ง Message เข้า Queue, Consumer ดึงไปประมวลผล เช่น RabbitMQ, Amazon SQS, Redis Queue
// RabbitMQ — Producer (Order Service)
const amqp = require('amqplib');
const conn = await amqp.connect('amqp://rabbitmq:5672');
const channel = await conn.createChannel();
await channel.assertQueue('order-processing');
channel.sendToQueue('order-processing', Buffer.from(JSON.stringify({
orderId: 'ORD-001',
userId: '123',
items: [{ productId: 'P1', qty: 2 }],
action: 'process'
})));
// Consumer (Inventory Service)
channel.consume('order-processing', (msg) => {
const order = JSON.parse(msg.content.toString());
// ตัดสต็อกสินค้า
updateInventory(order.items);
channel.ack(msg); // Acknowledge
});
2. Event Streaming (Apache Kafka)
Kafka เหมาะกับ High-throughput, Event Sourcing และ Real-time Data Pipeline
// Kafka Producer
const { Kafka } = require('kafkajs');
const kafka = new Kafka({ brokers: ['kafka:9092'] });
const producer = kafka.producer();
await producer.send({
topic: 'order-events',
messages: [{
key: 'ORD-001',
value: JSON.stringify({
type: 'ORDER_CREATED',
data: { orderId: 'ORD-001', total: 1500 },
timestamp: Date.now()
})
}]
});
// Kafka Consumer (Multiple Consumer Groups)
const consumer = kafka.consumer({ groupId: 'payment-service' });
await consumer.subscribe({ topic: 'order-events' });
await consumer.run({
eachMessage: async ({ message }) => {
const event = JSON.parse(message.value.toString());
if (event.type === 'ORDER_CREATED') {
await processPayment(event.data);
}
}
});
| Feature | Message Queue (RabbitMQ) | Event Streaming (Kafka) |
|---|---|---|
| Model | Push (Broker ส่งให้ Consumer) | Pull (Consumer ดึงเอง) |
| Retention | ลบหลัง Consume | เก็บตาม Retention Period |
| Replay | ไม่ได้ | ได้ (Rewind Offset) |
| Throughput | ปานกลาง | สูงมาก (ล้าน msg/s) |
| Use Case | Task Queue, RPC | Event Sourcing, Analytics |
Publish-Subscribe Pattern
// Publisher ส่ง Event โดยไม่รู้ว่าใคร Subscribe
// Service หลายตัว Subscribe Topic เดียวกัน
// Order Service (Publisher)
channel.publish('order-exchange', 'order.created', Buffer.from(JSON.stringify(order)));
// Email Service (Subscriber 1)
channel.bindQueue('email-queue', 'order-exchange', 'order.created');
// Inventory Service (Subscriber 2)
channel.bindQueue('inventory-queue', 'order-exchange', 'order.created');
// Analytics Service (Subscriber 3)
channel.bindQueue('analytics-queue', 'order-exchange', 'order.*');
API Gateway Pattern
API Gateway เป็น Single Entry Point สำหรับ Client ทำหน้าที่ Route Request ไปยัง Service ที่ถูกต้อง พร้อมจัดการ Cross-cutting Concerns
หน้าที่ของ API Gateway
- Routing — กระจาย Request ไปยัง Service ที่ถูกต้อง
- Authentication/Authorization — ตรวจสอบ JWT Token
- Rate Limiting — จำกัดจำนวน Request
- Load Balancing — กระจาย Traffic
- Caching — Cache Response ที่ไม่เปลี่ยนบ่อย
- Request/Response Transformation — แปลง Format
- Logging & Monitoring — บันทึก Request ทั้งหมด
# Kong API Gateway Configuration (YAML)
services:
- name: user-service
url: http://user-service:3000
routes:
- name: user-routes
paths:
- /api/users
methods:
- GET
- POST
plugins:
- name: rate-limiting
config:
minute: 100
- name: jwt
- name: cors
- name: order-service
url: http://order-service:3001
routes:
- name: order-routes
paths:
- /api/orders
API Gateway Tools
| Tool | ประเภท | จุดเด่น |
|---|---|---|
| Kong | Open Source | Plugin ecosystem, Lua scripting |
| AWS API Gateway | Managed | Lambda integration, Auto-scaling |
| Envoy Proxy | Open Source | High performance, gRPC support |
| NGINX | Open Source | Mature, Simple config |
| Traefik | Open Source | Auto-discovery, Docker native |
BFF — Backend for Frontend
สร้าง API Gateway แยกสำหรับแต่ละ Client Type:
- Web BFF — Optimize สำหรับ Browser (GraphQL, Full data)
- Mobile BFF — Optimize สำหรับ Mobile (Compact payload, Less data)
- Admin BFF — Optimize สำหรับ Admin Dashboard
// Mobile BFF — ส่งเฉพาะ Field ที่ Mobile ต้องการ
app.get('/mobile/orders/:id', async (req, res) => {
const order = await orderService.getOrder(req.params.id);
const user = await userService.getUser(order.userId);
// Compact response สำหรับ Mobile
res.json({
id: order.id,
total: order.total,
status: order.status,
userName: user.name
});
});
Service Mesh
Service Mesh จัดการ Service-to-Service Communication โดยไม่ต้องแก้ Code ทำงานผ่าน Sidecar Proxy ที่ Inject เข้าไปข้าง Service
Istio
# Istio VirtualService — Traffic Management
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
spec:
hosts:
- user-service
http:
- match:
- headers:
x-version:
exact: "v2"
route:
- destination:
host: user-service
subset: v2
- route:
- destination:
host: user-service
subset: v1
weight: 90
- destination:
host: user-service
subset: v2
weight: 10 # Canary 10%
---
# Circuit Breaker
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: user-service
spec:
host: user-service
trafficPolicy:
connectionPool:
http:
h2UpgradePolicy: DEFAULT
http1MaxPendingRequests: 100
http2MaxRequests: 1000
outlierDetection:
consecutive5xxErrors: 5
interval: 10s
baseEjectionTime: 30s
Service Mesh Features
- mTLS — Automatic Encryption ระหว่าง Services
- Traffic Management — Canary, A/B Testing, Blue-Green
- Observability — Distributed Tracing, Metrics, Logging
- Resilience — Circuit Breaker, Retry, Timeout
| Feature | Istio | Linkerd |
|---|---|---|
| Complexity | สูง | ต่ำ |
| Resource Usage | สูง | ต่ำ |
| Features | ครบ | เน้น Essential |
| Learning Curve | สูง | ปานกลาง |
| เหมาะกับ | Enterprise ขนาดใหญ่ | ทีมขนาดกลาง |
Resilience Patterns
Circuit Breaker Pattern
ป้องกัน Cascading Failure เมื่อ Service ปลายทางล่ม หยุดส่ง Request ชั่วคราวแทนที่จะรอ Timeout
// Resilience4j (Java/Spring Boot)
@CircuitBreaker(name = "userService", fallbackMethod = "fallbackUser")
@Retry(name = "userService")
@TimeLimiter(name = "userService")
public CompletableFuture<User> getUser(String id) {
return CompletableFuture.supplyAsync(
() -> userClient.getUser(id)
);
}
public CompletableFuture<User> fallbackUser(String id, Exception ex) {
return CompletableFuture.completedFuture(
new User(id, "Unknown", "N/A") // Cached/Default response
);
}
// application.yml
resilience4j:
circuitbreaker:
instances:
userService:
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 30s
permittedNumberOfCallsInHalfOpenState: 3
Circuit Breaker มี 3 สถานะ:
- Closed — ทำงานปกติ นับ Failure Rate
- Open — หยุดส่ง Request ทั้งหมด Return Fallback
- Half-Open — ปล่อย Request บางส่วนเพื่อทดสอบ
Retry & Timeout Patterns
// Exponential Backoff with Jitter
async function retryWithBackoff(fn, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxRetries) throw error;
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
const jitter = delay * 0.5 * Math.random();
await sleep(delay + jitter);
}
}
}
// Timeout
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeout);
Bulkhead Pattern
แยก Resource Pool ของแต่ละ Service เพื่อป้องกันไม่ให้ Service ที่ช้าดึง Resource ของ Service อื่น:
// แยก Connection Pool
const userServicePool = new ConnectionPool({ maxConnections: 20 });
const orderServicePool = new ConnectionPool({ maxConnections: 30 });
// ถ้า User Service ช้า → ใช้ได้แค่ 20 Connections
// Order Service ไม่ได้รับผลกระทบ
Saga Pattern — Distributed Transactions
ในระบบ Microservices ไม่สามารถใช้ Database Transaction ข้าม Service ได้ Saga Pattern แก้ปัญหานี้โดยแบ่ง Transaction เป็นหลาย Local Transactions พร้อม Compensating Actions
Choreography Saga
แต่ละ Service Publish Event หลังทำงานเสร็จ Service ถัดไป React ต่อ:
// ลำดับ:
// 1. Order Service → OrderCreated event
// 2. Payment Service (ฟัง OrderCreated) → PaymentProcessed event
// 3. Inventory Service (ฟัง PaymentProcessed) → StockReserved event
// 4. Shipping Service (ฟัง StockReserved) → ShipmentCreated event
// ถ้า Payment Failed:
// Payment Service → PaymentFailed event
// Order Service (ฟัง PaymentFailed) → Cancel Order (Compensate)
Orchestration Saga
มี Orchestrator (Saga Coordinator) ควบคุม Flow ทั้งหมด:
// Saga Orchestrator
class OrderSaga {
async execute(orderData) {
try {
// Step 1: Create Order
const order = await orderService.create(orderData);
// Step 2: Process Payment
const payment = await paymentService.charge(order.total);
// Step 3: Reserve Inventory
await inventoryService.reserve(order.items);
// Step 4: Create Shipment
await shippingService.create(order.id);
return { success: true, orderId: order.id };
} catch (error) {
// Compensating Actions (Rollback)
await this.compensate(error.failedStep);
}
}
async compensate(failedStep) {
switch(failedStep) {
case 'shipping':
await inventoryService.release(order.items);
case 'inventory':
await paymentService.refund(payment.id);
case 'payment':
await orderService.cancel(order.id);
}
}
}
| Aspect | Choreography | Orchestration |
|---|---|---|
| Coupling | Loose | Central Coordinator |
| Complexity | ซับซ้อนเมื่อ Service เยอะ | เข้าใจง่ายกว่า |
| Single Point of Failure | ไม่มี | Orchestrator |
| Debugging | ยาก (Distributed) | ง่ายกว่า (Central) |
| เหมาะกับ | 2-4 Services | 5+ Services |
CQRS (Command Query Responsibility Segregation)
// แยก Write Model (Command) กับ Read Model (Query)
// Command Side — Write to Primary DB
app.post('/orders', async (req, res) => {
const order = await orderRepository.save(req.body);
await eventBus.publish('OrderCreated', order);
res.json(order);
});
// Event Handler — Update Read Model
eventBus.subscribe('OrderCreated', async (order) => {
// Write to Read-optimized DB (Elasticsearch, Redis, etc.)
await readStore.index('orders', {
...order,
userName: await userService.getName(order.userId),
productNames: await productService.getNames(order.items)
});
});
// Query Side — Read from Optimized Store
app.get('/orders/search', async (req, res) => {
const results = await readStore.search('orders', req.query);
res.json(results); // Fast! ไม่ต้อง Join
});
API Versioning Strategies
// 1. URL Path Versioning
GET /api/v1/users
GET /api/v2/users
// 2. Header Versioning
GET /api/users
Accept: application/vnd.myapp.v2+json
// 3. Query Parameter
GET /api/users?version=2
// 4. Content Negotiation
Accept: application/json; version=2
Service Discovery
Client-Side Discovery
// Service ค้นหา Instance จาก Registry เอง
const instances = await consul.health.service('user-service');
const target = instances[Math.floor(Math.random() * instances.length)];
const response = await fetch(`http://${target.address}:${target.port}/api/users`);
Server-Side Discovery (Kubernetes DNS)
# Kubernetes Service — Auto DNS Resolution
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
ports:
- port: 3000
# เรียกผ่าน DNS
# http://user-service:3000/api/users
# http://user-service.namespace.svc.cluster.local:3000/api/users
Service Discovery Tools
| Tool | Type | Feature |
|---|---|---|
| Consul (HashiCorp) | Dedicated | Health Check, KV Store, Multi-DC |
| Eureka (Netflix) | Dedicated | Java ecosystem, Spring Cloud |
| Kubernetes DNS | Built-in | Auto discovery ใน K8s |
| etcd | KV Store | Consistent, Distributed |
Health Checks & Readiness
// Express.js Health Check Endpoints
app.get('/health/live', (req, res) => {
// Liveness: Service ยัง Run อยู่ไหม?
res.status(200).json({ status: 'UP' });
});
app.get('/health/ready', async (req, res) => {
// Readiness: พร้อมรับ Traffic ไหม?
try {
await db.query('SELECT 1');
await redis.ping();
res.status(200).json({ status: 'READY', db: 'UP', cache: 'UP' });
} catch (err) {
res.status(503).json({ status: 'NOT_READY', error: err.message });
}
});
# Kubernetes Probes
spec:
containers:
- name: user-service
livenessProbe:
httpGet:
path: /health/live
port: 3000
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
Inter-Service Authentication
// 1. mTLS (Service Mesh จัดการให้)
// Istio inject Sidecar Proxy → Automatic mTLS
// 2. JWT Token Propagation
// API Gateway ตรวจ User Token
// แล้วส่ง Service Token ไปยัง Internal Services
app.use(async (req, res, next) => {
const userToken = req.headers.authorization;
// Verify User Token
const user = await verifyJWT(userToken);
// Create Internal Service Token
req.serviceToken = await createServiceJWT({
service: 'order-service',
user: user.id,
permissions: user.roles
});
next();
});
// 3. API Key (Simple)
// Internal services use pre-shared API keys
fetch('http://user-service/api/users', {
headers: { 'X-Service-Key': process.env.INTERNAL_API_KEY }
});
Idempotency in Microservices
เมื่อ Retry Request ต้องมั่นใจว่าทำซ้ำแล้วไม่เกิดผลข้างเคียง (เช่น ตัดเงิน 2 ครั้ง):
// Idempotency Key
app.post('/payments', async (req, res) => {
const idempotencyKey = req.headers['idempotency-key'];
// ตรวจว่าเคยประมวลผล Key นี้แล้วหรือยัง
const existing = await redis.get(`payment:${idempotencyKey}`);
if (existing) {
return res.json(JSON.parse(existing)); // Return cached result
}
// ประมวลผลครั้งแรก
const payment = await processPayment(req.body);
await redis.set(`payment:${idempotencyKey}`, JSON.stringify(payment), 'EX', 86400);
res.json(payment);
});
// Client
fetch('/payments', {
method: 'POST',
headers: {
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json'
},
body: JSON.stringify({ amount: 1500, currency: 'THB' })
});
เลือก Communication Pattern อย่างไร?
| สถานการณ์ | Pattern ที่เหมาะ | เหตุผล |
|---|---|---|
| Query ข้อมูลทันที | REST / gRPC (Sync) | ต้องการ Response ทันที |
| High-performance Internal | gRPC | Protobuf binary, HTTP/2 |
| Background Processing | Message Queue | ไม่ต้องรอ, Retry ได้ |
| Event Notification | Pub/Sub | Decouple, Multiple Consumers |
| High-throughput Events | Kafka | Replay, Event Sourcing |
| Distributed Transaction | Saga | Compensating Actions |
| Flexible Client Queries | GraphQL | ลด Over/Under-fetch |
| Multi-client API | BFF + API Gateway | Optimize per client |
สรุป
การเลือก Communication Pattern ที่ถูกต้องใน Microservices เป็นการตัดสินใจสำคัญที่ส่งผลกระทบต่อทุกด้านของระบบ ไม่มี Pattern ใดที่ดีที่สุดสำหรับทุกกรณี แต่การเข้าใจข้อดีข้อเสียของแต่ละ Pattern จะช่วยให้ตัดสินใจได้ดีขึ้น
เริ่มจาก Synchronous (REST/gRPC) สำหรับ Simple Use Cases แล้วค่อยเพิ่ม Asynchronous (Message Queue/Kafka) เมื่อต้องการ Decoupling ใช้ API Gateway เป็น Single Entry Point ใช้ Circuit Breaker ป้องกัน Cascading Failure และใช้ Saga เมื่อต้องจัดการ Distributed Transactions ระบบที่ดีคือระบบที่ใช้ Pattern ที่เหมาะสมในแต่ละจุด ไม่ใช่ Pattern เดียวทั้งระบบ
