Monolith Architecture คือการสร้างแอปพลิเคชันเป็นก้อนเดียว ทุกฟีเจอร์ (User Management, Order Processing, Payment, Notification) อยู่ใน Codebase เดียว Deploy เป็นหน่วยเดียว ใช้ Database ร่วมกัน ข้อดีคือเรียบง่าย พัฒนาเร็วในช่วงแรก ไม่ต้องจัดการ Network Communication ระหว่าง Services แต่เมื่อแอปโตขึ้น ปัญหาจะเริ่มปรากฏ ทีม 50 คนแก้ไขโค้ดใน Repository เดียว Merge Conflict ตลอด Deploy ครั้งหนึ่งต้อง Deploy ทุกฟีเจอร์พร้อมกัน Bug เล็กๆ ในส่วนหนึ่งอาจทำให้ทั้งระบบล่ม
Microservices Architecture คือการแบ่งแอปพลิเคชันออกเป็น Services เล็กๆ อิสระจากกัน แต่ละ Service รับผิดชอบ Business Domain เดียว (เช่น User Service, Order Service, Payment Service) มี Database ของตัวเอง Deploy แยกกันได้ สื่อสารกันผ่าน Network (HTTP, gRPC, Message Queue) แต่ละ Service อาจเขียนด้วยภาษาโปรแกรมที่ต่างกันก็ได้ ทีมเล็กๆ ดูแล Service ของตัวเองได้อย่างอิสระ
| คุณสมบัติ | Monolith | Microservices |
|---|---|---|
| Codebase | เดียว ทุกอย่างอยู่ที่เดียว | แยกตาม Service แต่ละตัว |
| Database | ใช้ร่วมกัน | แยกตาม Service (Database per Service) |
| Deployment | Deploy ทั้งก้อนพร้อมกัน | Deploy แต่ละ Service แยกกัน |
| Scaling | Scale ทั้งก้อน (Vertical) | Scale เฉพาะ Service ที่ต้องการ |
| Technology | ภาษาเดียว Framework เดียว | เลือกภาษา/เทคโนโลยีได้อิสระ |
| Team | ทีมใหญ่ทำงานบน Codebase เดียว | ทีมเล็กดูแล Service ของตัวเอง |
| Failure Impact | Bug เดียว ล่มทั้งระบบ | Service เดียวล่ม ส่วนอื่นยังทำงานได้ |
| Complexity | ต่ำในช่วงแรก สูงเมื่อโต | สูงตั้งแต่เริ่ม (Network, Ops, Data) |
Microservices ไม่ใช่คำตอบสำหรับทุกปัญหา ในความเป็นจริง Microservices เพิ่ม Complexity มหาศาล ต้องจัดการ Distributed Systems, Network Latency, Data Consistency, Service Discovery, Deployment Pipeline ของทุก Service การตัดสินใจว่าจะใช้ Microservices หรือไม่ ควรพิจารณาจากปัจจัยเหล่านี้
แต่ละ Service ควรรับผิดชอบ Business Capability เดียว ไม่ใช่แค่ Technical Function เดียว ตัวอย่างเช่น "Order Service" รับผิดชอบทุกอย่างเกี่ยวกับ Order ตั้งแต่สร้าง อัพเดต ยกเลิก จนถึงประวัติ Order ไม่ใช่แค่ CRUD Table เดียว การแบ่ง Service ที่ดีควรใช้ Domain-Driven Design (DDD) เป็นแนวทาง แบ่งตาม Bounded Context
แต่ละ Service ต้องมี Database ของตัวเอง (Database per Service) ห้าม Service อื่นเข้าถึง Database โดยตรง ต้องสื่อสารผ่าน API เท่านั้น แม้จะดูเหมือนสิ้นเปลือง (มี Database หลายตัว) แต่เป็นสิ่งจำเป็นเพื่อให้แต่ละ Service เป็นอิสระจริงๆ ถ้าแชร์ Database Service หนึ่งเปลี่ยน Schema อาจทำให้ Service อื่นพัง
แต่ละ Service ต้อง Deploy ได้โดยไม่ต้อง Deploy Service อื่น มี CI/CD Pipeline ของตัวเอง มี Versioning ของตัวเอง ทีมสามารถปล่อยฟีเจอร์ใหม่ได้ทันทีโดยไม่ต้องรอทีมอื่น
การสื่อสารแบบ Synchronous คือ Service ที่เรียกต้องรอ Response จาก Service ที่ถูกเรียก เหมาะสำหรับกรณีที่ต้องการ Response ทันที เช่น User เปิดหน้า Order Detail ต้อง Query ข้อมูลจาก Order Service และ Product Service พร้อมกัน
# REST API — ง่าย อ่านง่าย ใช้ HTTP/JSON
# Order Service เรียก Product Service
GET /api/products/12345 HTTP/1.1
Host: product-service:8080
Content-Type: application/json
# Response
{
"id": 12345,
"name": "iPhone 17 Pro",
"price": 49900,
"stock": 150
}
# gRPC — เร็วกว่า REST 2-10x ใช้ Protocol Buffers
# product.proto
syntax = "proto3";
service ProductService {
rpc GetProduct (ProductRequest) returns (ProductResponse);
rpc ListProducts (ListRequest) returns (stream ProductResponse);
}
message ProductRequest {
int32 id = 1;
}
message ProductResponse {
int32 id = 1;
string name = 2;
double price = 3;
int32 stock = 4;
}
| คุณสมบัติ | REST | gRPC |
|---|---|---|
| Protocol | HTTP/1.1 + JSON | HTTP/2 + Protocol Buffers |
| Performance | ช้ากว่า (JSON text) | เร็วกว่า 2-10x (Binary) |
| Streaming | ไม่รองรับ | รองรับ (Bi-directional) |
| เรียนรู้ | ง่าย ทุกคนรู้จัก | ต้องเรียน Proto files |
| เหมาะกับ | Public API, เว็บ | Internal Service-to-Service |
การสื่อสารแบบ Asynchronous คือ Service ส่ง Message ไปที่ Queue แล้วไม่ต้องรอ Response Service ปลายทางจะมาหยิบ Message ไปประมวลผลเมื่อพร้อม เหมาะสำหรับงานที่ไม่ต้องการ Response ทันที เช่น ส่ง Email, สร้าง Report, ประมวลผล Payment
# Producer — Order Service ส่ง Event เมื่อมี Order ใหม่
import json
from kafka import KafkaProducer
producer = KafkaProducer(
bootstrap_servers=['kafka:9092'],
value_serializer=lambda v: json.dumps(v).encode('utf-8')
)
def create_order(order_data):
# 1. บันทึก Order ลง Database
order = db.orders.insert(order_data)
# 2. ส่ง Event ไป Kafka
producer.send('order-events', {
'event': 'ORDER_CREATED',
'order_id': order.id,
'user_id': order.user_id,
'total': order.total,
'items': order.items,
'timestamp': datetime.utcnow().isoformat()
})
return order
# Consumer — Notification Service รับ Event
from kafka import KafkaConsumer
consumer = KafkaConsumer(
'order-events',
bootstrap_servers=['kafka:9092'],
group_id='notification-service',
value_deserializer=lambda v: json.loads(v.decode('utf-8'))
)
for message in consumer:
event = message.value
if event['event'] == 'ORDER_CREATED':
send_email(event['user_id'], f"Order #{event['order_id']} confirmed!")
send_push_notification(event['user_id'], "Your order is being processed")
# Consumer — Inventory Service รับ Event เดียวกัน
for message in consumer:
event = message.value
if event['event'] == 'ORDER_CREATED':
for item in event['items']:
reduce_stock(item['product_id'], item['quantity'])
API Gateway คือ Single Entry Point สำหรับ Client ทุกตัว แทนที่ Client จะต้องรู้ว่า Service แต่ละตัวอยู่ที่ไหน (User Service อยู่ที่ port 8001, Order Service อยู่ที่ port 8002) Client ติดต่อ API Gateway ตัวเดียว แล้ว Gateway จะ Route Request ไปยัง Service ที่ถูกต้อง นอกจากนี้ API Gateway ยังทำหน้าที่ Authentication, Rate Limiting, Load Balancing, Request/Response Transformation, Caching และ Logging
# NGINX as API Gateway — nginx.conf
upstream user_service {
server user-service:8001;
server user-service:8002; # Load Balancing
}
upstream order_service {
server order-service:8003;
}
upstream product_service {
server product-service:8004;
}
server {
listen 80;
server_name api.example.com;
# Rate Limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
location /api/users {
limit_req zone=api burst=20;
proxy_pass http://user_service;
proxy_set_header X-Request-ID $request_id;
}
location /api/orders {
limit_req zone=api burst=20;
proxy_pass http://order_service;
}
location /api/products {
limit_req zone=api burst=50;
proxy_pass http://product_service;
proxy_cache api_cache;
proxy_cache_valid 200 5m;
}
}
| API Gateway | ประเภท | จุดเด่น |
|---|---|---|
| Kong | Open-source / Enterprise | Plugin system, Lua extensible, Kubernetes-native |
| NGINX | Open-source | High performance, ใช้งานง่าย, Configuration-based |
| Traefik | Open-source | Auto-discovery, Docker/K8s integration, Let's Encrypt |
| AWS API Gateway | Managed | Serverless, Lambda integration, Pay-per-request |
ในระบบ Microservices Services ไม่ได้อยู่ที่ IP Address คงที่ อาจมีหลาย Instance อาจถูกย้าย Restart หรือ Scale ขึ้นลงตลอดเวลา Service Discovery คือกลไกที่ให้ Services ค้นหากันเองได้ โดยไม่ต้อง Hardcode IP Address
# Kubernetes DNS — Service Discovery อัตโนมัติ
# ทุก Service ใน Kubernetes ได้ DNS อัตโนมัติ
# Format: service-name.namespace.svc.cluster.local
# Order Service เรียก User Service
import requests
# ใน Kubernetes ใช้ DNS ได้เลย
response = requests.get("http://user-service.default.svc.cluster.local:8080/api/users/123")
# หรือย่อเป็น (ถ้าอยู่ Namespace เดียวกัน)
response = requests.get("http://user-service:8080/api/users/123")
# Consul — Service Discovery สำหรับ non-K8s
# Register Service
curl -X PUT http://consul:8500/v1/agent/service/register -d '{
"Name": "user-service",
"Port": 8080,
"Check": {
"HTTP": "http://localhost:8080/health",
"Interval": "10s"
}
}'
# Discover Service
curl http://consul:8500/v1/catalog/service/user-service
Event-Driven Architecture (EDA) คือรูปแบบสถาปัตยกรรมที่ Services สื่อสารกันผ่าน Events แทนการเรียก API โดยตรง เมื่อเกิดเหตุการณ์สำคัญ (Order Created, Payment Completed, User Registered) Service จะ Publish Event ไปที่ Event Bus (เช่น Kafka, RabbitMQ) แล้ว Services อื่นที่สนใจ Event นั้นจะ Subscribe และประมวลผล ข้อดีคือ Loose Coupling Services ไม่ต้องรู้จักกัน Producer ไม่รู้ด้วยซ้ำว่ามี Consumer กี่ตัว
# Event-Driven Flow: สั่งซื้อสินค้า
# 1. Order Service → Publish "ORDER_CREATED"
# 2. Payment Service → Subscribe → Process Payment → Publish "PAYMENT_COMPLETED"
# 3. Inventory Service → Subscribe → Reserve Stock → Publish "STOCK_RESERVED"
# 4. Shipping Service → Subscribe → Create Shipment → Publish "SHIPMENT_CREATED"
# 5. Notification Service → Subscribe → Send Email/SMS
# Event Schema — ควรมีโครงสร้างชัดเจน
{
"event_id": "evt-abc-123",
"event_type": "ORDER_CREATED",
"aggregate_id": "order-789",
"aggregate_type": "Order",
"timestamp": "2026-04-08T10:30:00Z",
"version": 1,
"data": {
"order_id": "order-789",
"user_id": "user-456",
"items": [
{"product_id": "prod-123", "quantity": 2, "price": 990}
],
"total": 1980
},
"metadata": {
"correlation_id": "req-xyz-789",
"source": "order-service",
"user_agent": "Mozilla/5.0"
}
}
| คุณสมบัติ | Kafka | RabbitMQ | NATS |
|---|---|---|---|
| แนวคิด | Distributed Log | Message Broker | Cloud-native Messaging |
| Throughput | สูงมาก (ล้าน msg/s) | ปานกลาง (หมื่น msg/s) | สูง (แสน msg/s) |
| Persistence | เก็บ Message ถาวร | ลบหลัง Consume | Optional (JetStream) |
| Ordering | รับประกันใน Partition | รับประกันใน Queue | ไม่รับประกัน |
| เหมาะกับ | Event Sourcing, Log | Task Queue, RPC | Real-time, IoT |
Saga คือรูปแบบสำหรับจัดการ Transaction ที่ข้ามหลาย Services ในระบบ Monolith ใช้ Database Transaction ธรรมดา (BEGIN, COMMIT, ROLLBACK) แต่ใน Microservices แต่ละ Service มี Database แยก ไม่สามารถใช้ Transaction เดียวข้ามหลาย Database ได้ Saga แก้ปัญหานี้โดยแบ่ง Transaction ใหญ่เป็นหลาย Local Transactions ถ้าขั้นตอนใดล้มเหลว จะเรียก Compensating Transaction (Undo) ของขั้นตอนก่อนหน้า
# Saga: สั่งซื้อสินค้า (Choreography-based)
# Success Flow:
# 1. Order Service: Create Order (PENDING)
# 2. Payment Service: Charge Payment → Success
# 3. Inventory Service: Reserve Stock → Success
# 4. Shipping Service: Create Shipment → Success
# 5. Order Service: Update Status (CONFIRMED)
# Failure Flow (Payment failed):
# 1. Order Service: Create Order (PENDING) ✓
# 2. Payment Service: Charge Payment → FAILED ✗
# 3. Order Service: Cancel Order (CANCELLED) ← Compensating
# Failure Flow (Stock insufficient):
# 1. Order Service: Create Order (PENDING) ✓
# 2. Payment Service: Charge Payment → Success ✓
# 3. Inventory Service: Reserve Stock → FAILED ✗
# 4. Payment Service: Refund Payment ← Compensating
# 5. Order Service: Cancel Order (CANCELLED) ← Compensating
# Orchestrator-based Saga (ใช้ Central Coordinator)
class OrderSaga:
def execute(self, order):
try:
payment = payment_service.charge(order.total)
stock = inventory_service.reserve(order.items)
shipment = shipping_service.create(order)
order_service.confirm(order.id)
except PaymentError:
order_service.cancel(order.id)
raise
except StockError:
payment_service.refund(payment.id)
order_service.cancel(order.id)
raise
except ShippingError:
inventory_service.release(order.items)
payment_service.refund(payment.id)
order_service.cancel(order.id)
raise
CQRS คือการแยก Read (Query) กับ Write (Command) ออกจากกัน ใช้ Model คนละตัว Database คนละตัว เหมาะสำหรับระบบที่มี Read:Write Ratio สูง เช่น 90% Read 10% Write สามารถ Optimize แต่ละด้านได้อย่างอิสระ ฝั่ง Write ใช้ Normalized Database สำหรับ Data Integrity ฝั่ง Read ใช้ Denormalized Database (เช่น Elasticsearch) สำหรับ Query ที่เร็ว
# CQRS Architecture
# Command Side (Write)
class CreateOrderCommand:
def handle(self, data):
# Validate
if not data.items:
raise ValueError("Order must have items")
# Write to primary database (PostgreSQL)
order = Order.create(
user_id=data.user_id,
items=data.items,
total=calculate_total(data.items)
)
db.session.commit()
# Publish event for Read Side to update
event_bus.publish("ORDER_CREATED", order.to_dict())
return order.id
# Query Side (Read)
class OrderQueryService:
def __init__(self):
self.es = Elasticsearch() # Read from Elasticsearch
def search_orders(self, user_id, status=None, date_from=None):
query = {"bool": {"must": [{"term": {"user_id": user_id}}]}}
if status:
query["bool"]["must"].append({"term": {"status": status}})
if date_from:
query["bool"]["must"].append({"range": {"created_at": {"gte": date_from}}})
return self.es.search(index="orders", query=query)
# Event Handler: Sync Read Database
@event_handler("ORDER_CREATED")
def sync_order_to_read_db(event):
es.index(index="orders", id=event["order_id"], body=event)
Event Sourcing คือการเก็บทุก Event ที่เกิดขึ้นกับ Entity แทนที่จะเก็บเฉพาะ State ปัจจุบัน ตัวอย่างเช่น บัญชีธนาคาร แทนที่จะเก็บแค่ "ยอดเงิน 10,000 บาท" จะเก็บทุก Transaction ที่เกิดขึ้น (+5000, -2000, +7000) แล้วคำนวณยอดจาก Events ข้อดีคือมี Audit Trail ครบถ้วน สามารถ Replay Events เพื่อสร้าง State ใหม่ได้ เหมาะกับระบบการเงินและระบบที่ต้องการ Traceability
Circuit Breaker คือ Pattern ที่ป้องกันไม่ให้ Service เรียก Service ที่ล่มซ้ำแล้วซ้ำเล่า เมื่อ Service ปลายทางไม่ตอบสนอง Circuit Breaker จะ "เปิดวงจร" (Open) หยุดส่ง Request ไปยัง Service นั้น แล้วส่ง Fallback Response แทน หลังจากรอสักครู่ จะเปลี่ยนเป็น Half-Open ลอง Request 1-2 ครั้ง ถ้าสำเร็จก็ปิดวงจร (Closed) กลับมาทำงานปกติ ถ้ายังล้มเหลวก็เปิดวงจรต่อ
# Circuit Breaker Implementation (Python)
import time
from enum import Enum
class CircuitState(Enum):
CLOSED = "closed" # ปกติ ส่ง Request ได้
OPEN = "open" # เปิดวงจร หยุดส่ง Request
HALF_OPEN = "half_open" # ลองส่ง Request ทดสอบ
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=30):
self.state = CircuitState.CLOSED
self.failure_count = 0
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.last_failure_time = None
def call(self, func, *args, **kwargs):
if self.state == CircuitState.OPEN:
if time.time() - self.last_failure_time > self.recovery_timeout:
self.state = CircuitState.HALF_OPEN
else:
raise CircuitOpenError("Service unavailable")
try:
result = func(*args, **kwargs)
self._on_success()
return result
except Exception as e:
self._on_failure()
raise
def _on_success(self):
self.failure_count = 0
self.state = CircuitState.CLOSED
def _on_failure(self):
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = CircuitState.OPEN
# ใช้งาน
payment_breaker = CircuitBreaker(failure_threshold=5, recovery_timeout=30)
try:
result = payment_breaker.call(payment_service.charge, order.total)
except CircuitOpenError:
# Fallback: Queue payment for later
payment_queue.add(order)
Bulkhead (ผนังกั้นน้ำในเรือ) คือการแบ่ง Resource Pool ออกเป็นส่วนๆ สำหรับแต่ละ Service ถ้า Service A ใช้ Connection Pool หมด ไม่กระทบ Connection Pool ของ Service B เหมือนเรือที่มีผนังกั้น ถ้าน้ำรั่วเข้าห้องหนึ่ง ห้องอื่นยังลอยได้
Service Mesh คือ Infrastructure Layer ที่จัดการ Service-to-Service Communication โดยไม่ต้องแก้โค้ดของ Application ทำงานผ่าน Sidecar Proxy (เช่น Envoy) ที่ติดตั้งข้างทุก Service Sidecar จัดการ Load Balancing, Retry, Circuit Breaking, mTLS Encryption, Observability ให้ทุก Service โดยอัตโนมัติ
# Istio — Service Mesh ยอดนิยม
# ติดตั้ง Istio ใน Kubernetes
istioctl install --set profile=default -y
# Enable Sidecar Injection สำหรับ Namespace
kubectl label namespace default istio-injection=enabled
# Virtual Service — Traffic Routing
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: order-service
spec:
hosts:
- order-service
http:
- match:
- headers:
x-canary:
exact: "true"
route:
- destination:
host: order-service
subset: v2
- route:
- destination:
host: order-service
subset: v1
weight: 90
- destination:
host: order-service
subset: v2
weight: 10 # Canary: 10% traffic
# Destination Rule — Circuit Breaker + Load Balancing
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: order-service
spec:
host: order-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
h2UpgradePolicy: UPGRADE
maxRequestsPerConnection: 10
outlierDetection:
consecutive5xxErrors: 5
interval: 30s
baseEjectionTime: 30s
แต่ละ Service เลือก Database ที่เหมาะกับ Workload ของตัวเอง Order Service อาจใช้ PostgreSQL สำหรับ ACID Transactions Product Search Service อาจใช้ Elasticsearch สำหรับ Full-text Search Session Service อาจใช้ Redis สำหรับ Low Latency Analytics Service อาจใช้ ClickHouse สำหรับ OLAP Queries
ในระบบ Microservices ไม่สามารถมี Strong Consistency ข้ามหลาย Services ได้ เพราะแต่ละ Service มี Database แยก ต้องยอมรับ Eventual Consistency คือข้อมูลอาจไม่ตรงกันชั่วครู่ แต่ในที่สุดจะ Sync กันสำเร็จ ตัวอย่างเช่น เมื่อสร้าง Order สำเร็จ Inventory อาจยังไม่ลด Stock ทันที แต่ผ่านไป 1-2 วินาที Event ถูกประมวลผล Stock ก็จะถูกลด
# Outbox Pattern — ป้องกัน Data Inconsistency
# ปัญหา: ถ้า DB Commit สำเร็จแต่ส่ง Event ไม่สำเร็จ = Data inconsistent
# แก้ไข: เขียน Event ลง Outbox Table ใน Transaction เดียวกับ Business Data
# 1. ใน Transaction เดียว
BEGIN TRANSACTION;
INSERT INTO orders (id, user_id, total, status)
VALUES ('order-789', 'user-456', 1980, 'PENDING');
INSERT INTO outbox (id, aggregate_type, aggregate_id, event_type, payload)
VALUES ('evt-123', 'Order', 'order-789', 'ORDER_CREATED',
'{"order_id":"order-789","user_id":"user-456","total":1980}');
COMMIT;
# 2. Background Worker อ่าน Outbox → Publish ไป Kafka → ลบ Outbox Record
# ถ้า Publish ล้มเหลว จะ Retry จาก Outbox
Distributed Tracing คือการติดตาม Request ตั้งแต่เข้า API Gateway จนถึง Service สุดท้าย ใน Monolith ดู Stack Trace ก็พอ แต่ใน Microservices Request เดียวอาจผ่าน 5-10 Services ถ้ามีปัญหา ต้องรู้ว่าช้าที่ Service ไหน ใช้เวลาเท่าไหร่ในแต่ละ Service เครื่องมือเช่น Jaeger และ Zipkin ช่วยแสดง Trace เป็น Timeline ที่เห็นได้ชัด
# OpenTelemetry — มาตรฐานสำหรับ Observability
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
# Setup Tracer
trace.set_tracer_provider(TracerProvider())
jaeger_exporter = JaegerExporter(agent_host_name="jaeger", agent_port=6831)
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(jaeger_exporter)
)
tracer = trace.get_tracer("order-service")
# ใช้งาน
@app.route("/api/orders", methods=["POST"])
def create_order():
with tracer.start_as_current_span("create_order") as span:
span.set_attribute("user.id", request.user_id)
# Call Payment Service
with tracer.start_as_current_span("call_payment_service"):
payment = payment_client.charge(order.total)
# Call Inventory Service
with tracer.start_as_current_span("call_inventory_service"):
inventory = inventory_client.reserve(order.items)
return jsonify(order.to_dict())
เมื่อมี 20 Services แต่ละตัวมี Log ของตัวเอง การหา Error ข้ามหลาย Services เหมือนหาเข็มในมหาสมุทร ต้องรวม Log ไว้ที่เดียว (Centralized Logging) ด้วย ELK Stack หรือ Grafana Loki และใช้ Correlation ID เชื่อมโยง Log จากหลาย Services เข้าด้วยกัน
Microservices กับ Docker + Kubernetes เป็นคู่ที่ดีที่สุด Docker ทำให้ทุก Service เป็น Container ที่ Run ได้ทุกที่ Kubernetes จัดการ Orchestration ให้ Scale Service ขึ้นลง Health Check Restart Service ที่ล่ม Load Balance Traffic ไปยัง Instances ต่างๆ
# docker-compose.yml — Microservices ในเครื่อง Dev
version: '3.8'
services:
api-gateway:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- user-service
- order-service
- product-service
user-service:
build: ./services/user
environment:
- DB_HOST=user-db
- KAFKA_BROKERS=kafka:9092
depends_on:
- user-db
- kafka
order-service:
build: ./services/order
environment:
- DB_HOST=order-db
- KAFKA_BROKERS=kafka:9092
depends_on:
- order-db
- kafka
product-service:
build: ./services/product
environment:
- DB_HOST=product-db
- REDIS_HOST=redis
depends_on:
- product-db
- redis
user-db:
image: postgres:16
environment:
POSTGRES_DB: users
POSTGRES_PASSWORD: secret
order-db:
image: postgres:16
environment:
POSTGRES_DB: orders
POSTGRES_PASSWORD: secret
product-db:
image: mongo:7
environment:
MONGO_INITDB_DATABASE: products
kafka:
image: confluentinc/cp-kafka:7.6.0
environment:
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
redis:
image: redis:7-alpine
ในระบบ Microservices แต่ละ Service ควรมี CI/CD Pipeline ของตัวเอง เพื่อให้ Deploy ได้อิสระ Pipeline ควรรวม Unit Test, Integration Test, Build Docker Image, Push to Registry และ Deploy to Kubernetes การใช้ Monorepo (เช่น Turborepo, Nx) ช่วยจัดการ Shared Code ระหว่าง Services ส่วน Polyrepo ให้แต่ละ Service มี Repository แยก เหมาะเมื่อทีมต่างกันดูแลแต่ละ Service
# GitHub Actions — CI/CD สำหรับ Microservice
# .github/workflows/order-service.yml
name: Order Service CI/CD
on:
push:
paths:
- 'services/order/**'
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Tests
run: |
cd services/order
pip install -r requirements.txt
pytest --cov=.
build-and-deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build & Push Docker Image
run: |
docker build -t registry.example.com/order-service:${GITHUB_SHA} services/order
docker push registry.example.com/order-service:${GITHUB_SHA}
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/order-service order-service=registry.example.com/order-service:${GITHUB_SHA} --namespace=production
| ข้อผิดพลาด | ปัญหา | วิธีแก้ |
|---|---|---|
| เริ่มด้วย Microservices ตั้งแต่แรก | ยังไม่เข้าใจ Domain แบ่ง Service ผิด | เริ่มจาก Monolith แล้วค่อย Extract Service |
| แชร์ Database ข้าม Services | Tight Coupling, Schema Change กระทบหลาย Services | Database per Service เสมอ |
| Synchronous Call Chain ยาว | Latency สูง, Single Point of Failure | ใช้ Async Event-Driven เมื่อเป็นไปได้ |
| ไม่มี Distributed Tracing | Debug ปัญหาข้ามหลาย Services ไม่ได้ | ใช้ OpenTelemetry + Jaeger ตั้งแต่เริ่ม |
| ไม่มี Circuit Breaker | Cascading Failure ลามทั้งระบบ | ใส่ Circuit Breaker ทุก External Call |
| Service ใหญ่เกินไป | กลายเป็น Distributed Monolith | แบ่งตาม Bounded Context ของ DDD |
| ไม่จัดการ Data Consistency | ข้อมูลไม่ตรงกันข้ามหลาย Services | ใช้ Saga Pattern + Outbox Pattern |
A: ไม่จำเป็น แต่แนะนำอย่างยิ่ง Kubernetes ช่วยจัดการ Service Discovery, Load Balancing, Auto Scaling, Health Check, Rolling Update ให้อัตโนมัติ ถ้าไม่ใช้ K8s ก็ต้องจัดการเองทุกอย่าง ทางเลือกอื่น เช่น Docker Swarm (ง่ายกว่าแต่ Feature น้อยกว่า) หรือ Managed Services (ECS, Cloud Run, App Runner) ที่ให้ Cloud Provider จัดการให้
A: ไม่มีจำนวนขั้นต่ำ ไม่ใช่ว่ามี 3 Services แล้วเรียก Microservices ประเด็นสำคัญคือ แต่ละ Service ต้องเป็นอิสระจริงๆ มี Database แยก Deploy แยก ทีมแยก ถ้ามี 10 Services แต่ใช้ Database เดียวกัน Deploy พร้อมกัน ก็ไม่ใช่ Microservices แค่ Distributed Monolith
A: Micro Frontend คือการนำแนวคิด Microservices มาใช้กับ Frontend แบ่งหน้าเว็บออกเป็นส่วนย่อยที่ทีมต่างกันดูแล เช่น Header เป็น React, Product List เป็น Vue, Cart เป็น Svelte ใช้เทคนิคเช่น Module Federation (Webpack 5), Single-SPA หรือ Web Components เหมาะกับทีมขนาดใหญ่ที่มีหลายทีมดูแล Frontend
A: Choreography (แต่ละ Service Publish/Subscribe Events เอง) เหมาะกับ Flow ที่เรียบง่าย 2-3 Steps เพราะไม่มี Central Coordinator แต่ยากต่อการ Debug Orchestration (มี Saga Orchestrator คอยสั่งทุก Step) เหมาะกับ Flow ที่ซับซ้อน 4+ Steps เพราะเห็น Flow ทั้งหมดในที่เดียว Debug ง่ายกว่า แต่ Orchestrator เป็น Single Point of Failure
A: ใช้ Strangler Fig Pattern คือค่อยๆ ย้ายทีละฟีเจอร์ 1) เลือกฟีเจอร์ที่เปลี่ยนบ่อยหรือต้อง Scale (เช่น Search, Notification) 2) สร้าง Service ใหม่ 3) Route Traffic ส่วนนั้นไปที่ Service ใหม่ 4) เมื่อ Service ใหม่ทำงานได้ดี ลบโค้ดส่วนนั้นออกจาก Monolith 5) ทำซ้ำจนเหลือแต่ Core ใน Monolith หรือ Extract ทั้งหมด