- 1. เป้าหมายของ Workshop
- 2. หลักการสำคัญ
- 3. กฎ Data Ownership
- 4. API Gateway
- 5. การสื่อสารระหว่าง Services
- 6. ตัวอย่าง Order Workflow
- 7. Payment Service รับ Event
- 8. ปัญหา Distributed Transaction
- 9. Saga Pattern
- 10. Transactional Outbox
- 11. Idempotency
- 12. โครงสร้าง Docker Compose สำหรับ Workshop
- 13. ตัวอย่าง API
- 14. Kong Routing
- 15. Security
- 16. Observability
- 17. Reliability Patterns ที่ควรเพิ่ม
- 18. Anti-Patterns
- 19. Architecture ที่แนะนำ
- 20. แนวทางเลือก Communication
- 21. สรุป
- Workshop ถัดไป
แนวทางออกแบบระบบที่แต่ละ Microservice มีฐานข้อมูลของตนเอง แต่สามารถทำงานร่วมกันผ่าน REST API และ Event-Driven Architecture
#1. เป้าหมายของ Workshop
Workshop นี้ออกแบบระบบ e-Commerce ตัวอย่าง โดยแยกความรับผิดชอบออกเป็น 4 Services
- User Service — จัดการข้อมูลผู้ใช้
- Product Service — จัดการสินค้าและราคา
- Order Service — จัดการคำสั่งซื้อ
- Payment Service — จัดการการชำระเงิน
แต่ละ Service มีฐานข้อมูลของตัวเองตามหลัก Database per Service และห้าม Service หนึ่งเข้าถึงฐานข้อมูลของอีก Service โดยตรง
Client
|
v
Kong API Gateway
|
+--> User Service ------> User DB
+--> Product Service ---> Product DB
+--> Order Service -----> Order DB
+--> Payment Service ---> Payment DB
|
+---------> Kafka <---------+
#2. หลักการสำคัญ
#2.1 Database per Service
แทนที่จะใช้ฐานข้อมูลกลางร่วมกัน
User Service -----+
Product Service --+--> Shared Database # ไม่แนะนำสำหรับ Microservices ที่ต้องการ ownership ชัดเจน
Order Service ----+
ให้แต่ละ Service เป็นเจ้าของข้อมูลของตัวเอง
User Service --> User DB
Product Service --> Product DB
Order Service --> Order DB
Payment Service --> Payment DB
ข้อดีคือ Service สามารถ deploy, scale, เปลี่ยน schema และเลือก database technology ได้อย่างอิสระมากขึ้น
#3. กฎ Data Ownership
สมมติว่า Order Service ต้องการชื่อสินค้าและราคาสินค้า
ไม่ควรทำแบบนี้
SELECT * FROM product_db.products WHERE id = 1001;
เพราะ Order Service กำลังเข้าถึงฐานข้อมูลของ Product Service โดยตรง
ควรเรียก API
GET /products/1001
หรือรับข้อมูลผ่าน Domain Event เช่น
{
"eventType": "ProductPriceChanged",
"productId": 1001,
"price": 590.00
}
#4. API Gateway
Client ไม่จำเป็นต้องรู้ address ของแต่ละ Service
Client
|
v
API Gateway
|
+-- /users/* -----> User Service
+-- /products/* --> Product Service
+-- /orders/* ----> Order Service
+-- /payments/* --> Payment Service
API Gateway สามารถรับผิดชอบงาน cross-cutting concerns เช่น
- Routing
- Authentication / Authorization
- Rate Limiting
- CORS
- Logging
- Metrics
- Load Balancing
ไม่ควรนำ business logic หลักของระบบไปไว้ใน Gateway
#5. การสื่อสารระหว่าง Services
ระบบ Microservices มักใช้ทั้ง synchronous และ asynchronous communication
#Synchronous
ใช้ REST หรือ gRPC เมื่อ Service ต้องการผลลัพธ์ทันที
Order Service
|
| GET /products/1001
v
Product Service
ตัวอย่าง response
{
"id": 1001,
"name": "Mechanical Keyboard",
"price": 2590,
"stock": 20
}
ข้อเสียคือ runtime coupling: หาก Product Service ล่ม Order Service อาจไม่สามารถดำเนินงานบางส่วนต่อได้ จึงควรพิจารณา timeout, retry อย่างจำกัด และ circuit breaker
#Asynchronous
ใช้ Message Broker เช่น Apache Kafka หรือ RabbitMQ
Order Service
|
| OrderCreated
v
Kafka
|
+------> Payment Service
+------> Inventory Service
+------> Notification Service
Producer ไม่จำเป็นต้องรอให้ทุก Consumer ทำงานเสร็จ
#6. ตัวอย่าง Order Workflow
ผู้ใช้ส่งคำสั่ง
POST /orders
Request
{
"userId": 101,
"items": [
{
"productId": 1001,
"quantity": 2
}
]
}
Order Service สามารถตรวจสอบข้อมูลที่จำเป็นจาก Product Service แล้วบันทึก Order ลง Order DB
orders
---------------------------------
id ORD-001
user_id 101
status PENDING
amount 5180
จากนั้น publish event
{
"eventId": "evt-001",
"eventType": "OrderCreated",
"orderId": "ORD-001",
"userId": 101,
"amount": 5180
}
#7. Payment Service รับ Event
Payment Service subscribe OrderCreated
Kafka
|
| OrderCreated
v
Payment Service
Payment Service บันทึกข้อมูลลง Payment DB ของตนเอง
payments
---------------------------------
id PAY-001
order_id ORD-001
amount 5180
status SUCCESS
เมื่อสำเร็จ publish
{
"eventId": "evt-002",
"eventType": "PaymentCompleted",
"orderId": "ORD-001",
"paymentId": "PAY-001",
"amount": 5180
}
Order Service consume event แล้วเปลี่ยนสถานะ Order
PENDING --> PAID
นี่คือ Eventual Consistency: ข้อมูลในทุก Service ไม่จำเป็นต้องเปลี่ยนพร้อมกันใน transaction เดียว แต่จะเข้าสู่สถานะสอดคล้องกันหลังจาก events ถูกประมวลผล
#8. ปัญหา Distributed Transaction
Monolith อาจใช้ transaction เดียวได้
BEGIN
INSERT order
UPDATE inventory
INSERT payment
COMMIT
เมื่อข้อมูลกระจายอยู่หลายฐานข้อมูล การใช้ transaction แบบเดียวกันครอบทุก Service ไม่ใช่แนวทางหลักของ Microservices
Order DB
Product/Inventory DB
Payment DB
จึงมักใช้ Saga Pattern
#9. Saga Pattern
ตัวอย่าง Saga
Create Order
|
v
Reserve Inventory
|
v
Process Payment
|
v
Confirm Order
กรณี Payment ล้มเหลว
PaymentFailed
|
+--> Release Inventory
|
+--> Cancel Order
การทำงานย้อนกลับนี้เรียกว่า Compensating Transaction
Saga มีสองรูปแบบหลัก
#Choreography
Services สื่อสารกันด้วย Events
OrderCreated
|
v
InventoryReserved
|
v
PaymentCompleted
|
v
OrderConfirmed
เหมาะกับ workflow ที่ไม่ซับซ้อนมากและต้องการลด central coordinator
#Orchestration
มี Saga Orchestrator ควบคุม workflow
Saga Orchestrator
/ | \
v v v
Order Inventory Payment
เหมาะกับ workflow ที่มีหลายขั้นตอน เงื่อนไข และ compensation ซับซ้อน
#10. Transactional Outbox
มี failure case สำคัญดังนี้
1. INSERT Order สำเร็จ
2. Application crash
3. Publish OrderCreated ไม่สำเร็จ
Order DB มีข้อมูล แต่ Service อื่นไม่เคยได้รับ event
Transactional Outbox แก้ปัญหาด้วยการบันทึก business data และ event ลงฐานข้อมูลเดียวกันภายใต้ local transaction เดียว
BEGIN
INSERT INTO orders (...);
INSERT INTO outbox_events (...);
COMMIT
จากนั้น Outbox Publisher อ่าน event แล้วส่งไป Kafka
Order Service
|
+--> Order DB
|
+--> orders
+--> outbox_events
|
v
Outbox Publisher
|
v
Kafka
หลัง publish สำเร็จจึง mark event ว่าส่งแล้ว
#11. Idempotency
Message Broker อาจส่ง message ซ้ำได้ ดังนั้น Consumer ควรออกแบบให้ประมวลผล event เดิมซ้ำแล้วไม่สร้างผลกระทบซ้ำ
ตัวอย่าง event มี eventId
{
"eventId": "evt-002",
"eventType": "PaymentCompleted",
"orderId": "ORD-001"
}
Consumer ตรวจสอบว่า event เคยประมวลผลหรือยัง
SELECT event_id
FROM processed_events
WHERE event_id = 'evt-002';
ถ้ามีแล้วให้ ignore
แนวคิดนี้เรียกว่า Idempotent Consumer
#12. โครงสร้าง Docker Compose สำหรับ Workshop
โครงสร้างโปรเจกต์ตัวอย่าง
microservices-demo/
|
+-- docker-compose.yml
+-- kong/
+-- user-service/
+-- product-service/
+-- order-service/
+-- payment-service/
ตัวอย่าง docker-compose.yml แบบย่อ
services:
user-db:
image: postgres:17
environment:
POSTGRES_DB: userdb
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
product-db:
image: mysql:8.4
environment:
MYSQL_DATABASE: productdb
MYSQL_USER: app
MYSQL_PASSWORD: secret
MYSQL_ROOT_PASSWORD: rootsecret
order-db:
image: postgres:17
environment:
POSTGRES_DB: orderdb
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
payment-db:
image: postgres:17
environment:
POSTGRES_DB: paymentdb
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
kafka:
image: apache/kafka:latest
user-service:
build: ./user-service
depends_on:
- user-db
product-service:
build: ./product-service
depends_on:
- product-db
order-service:
build: ./order-service
depends_on:
- order-db
- kafka
payment-service:
build: ./payment-service
depends_on:
- payment-db
- kafka
สำหรับ production ควร pin image versions, ใช้ secrets manager, health checks และกำหนด resource limits แทนการพึ่ง tag
latest
#13. ตัวอย่าง API
#User Service
POST /users
GET /users/{id}
PUT /users/{id}
#Product Service
POST /products
GET /products
GET /products/{id}
PUT /products/{id}
#Order Service
POST /orders
GET /orders/{id}
GET /users/{userId}/orders
#Payment Service
POST /payments
GET /payments/{id}
GET /orders/{orderId}/payment
#14. Kong Routing
ตัวอย่าง public routes
http://localhost:8000/users
http://localhost:8000/products
http://localhost:8000/orders
http://localhost:8000/payments
Kong route ไปยัง internal services
/users --> user-service
/products --> product-service
/orders --> order-service
/payments --> payment-service
Client จึงไม่จำเป็นต้องทราบ host/port ภายในของแต่ละ Service
#15. Security
Architecture สามารถเพิ่ม Identity Provider เช่น Keycloak
Client
|
| Login
v
Keycloak
|
| JWT
v
Client
|
| Authorization: Bearer <token>
v
Kong
|
v
Microservices
ควรแยก Authentication และ Authorization ให้ชัดเจน และตรวจสอบสิทธิ์ที่ระดับ Service สำหรับ business authorization ที่เฉพาะเจาะจง
#16. Observability
Microservices มี request กระจายหลาย process จึงควรมี observability ตั้งแต่ต้น
Logs --> Loki / ELK
Metrics --> Prometheus --> Grafana
Traces --> OpenTelemetry --> Jaeger/Tempo
ควรส่ง correlation/trace ID ผ่านทุก Service
traceparent: 00-...
เพื่อ trace request เช่น
Gateway
-> Order Service
-> Product Service
-> Kafka
-> Payment Service
#17. Reliability Patterns ที่ควรเพิ่ม
ระบบ production ควรพิจารณา
- Timeout
- Retry with backoff เฉพาะ operation ที่ปลอดภัย
- Circuit Breaker
- Bulkhead
- Dead Letter Queue (DLQ)
- Idempotency Key
- Transactional Outbox
- Schema/version compatibility สำหรับ Events
- Health Check / Readiness Check
ตัวอย่าง client request ที่มี idempotency key
POST /payments
Idempotency-Key: 79cbbe8e-9e72-4b58-9025-example
ช่วยป้องกันการสร้าง Payment ซ้ำจากการ retry
#18. Anti-Patterns
#Shared Database
Service A ----+
Service B ----+--> Database เดียว
Service C ----+
ทำให้ schema coupling สูงและ Service ไม่ได้เป็นเจ้าของข้อมูลจริง
#Direct Database Access
Order Service ----SQL----> Product DB
ควรเป็น
Order Service ----API----> Product Service ----> Product DB
#Distributed Join
ไม่ควรพยายาม JOIN ตารางข้ามฐานข้อมูลของ Services เหมือน Monolith
สำหรับ read-heavy use case สามารถสร้าง Read Model / CQRS projection จาก events ได้
User Event -----+
Order Event ----+--> Reporting Read Model
Payment Event --+
Reporting Service จึง query ข้อมูลที่ denormalized ไว้สำหรับการอ่านได้โดยไม่ทำลาย ownership ของ operational databases
#19. Architecture ที่แนะนำ
+----------------+
| Identity |
| Provider |
+-------+--------+
|
| JWT/OAuth2
v
+--------+ +--------+---------+
| Client |------------->| Kong API Gateway |
+--------+ +--------+---------+
|
+-------------------+-------------------+
| | |
v v v
+-----------+ +-----------+ +-----------+
| User | | Product | | Order |
| Service | | Service | | Service |
+-----+-----+ +-----+-----+ +-----+-----+
| | |
v v v
User DB Product DB Order DB
|
| Events
v
+-----------+
| Kafka |
+-----+-----+
|
v
+-----------+
| Payment |
| Service |
+-----+-----+
|
v
Payment DB
#20. แนวทางเลือก Communication
| Requirement | แนวทาง |
|---|---|
| ต้องการ response ทันที | REST / gRPC |
| แจ้งหลาย Services | Event |
| งาน background | Queue / Event |
| Workflow ข้ามหลายฐานข้อมูล | Saga |
| ป้องกัน DB commit แล้ว event หาย | Transactional Outbox |
| ป้องกัน message ซ้ำ | Idempotency |
| Query ข้อมูลรวมจำนวนมาก | CQRS / Read Model |
#21. สรุป
Microservices ที่มีฐานข้อมูลแยกกันยังสามารถทำงานร่วมกันได้โดยไม่ต้องแชร์ database
หลักสำคัญคือ
Database per Service
+
REST / gRPC
+
Event-Driven Architecture
+
Saga Pattern
+
Transactional Outbox
+
Idempotency
+
Observability
เป้าหมายไม่ใช่เพียงการ “แยก Service” แต่คือการกำหนด Bounded Context, Data Ownership และ Contract ให้ชัดเจน เพื่อให้แต่ละ Service สามารถพัฒนา deploy และ scale ได้โดยลด coupling ระหว่างกัน
#Workshop ถัดไป
สามารถต่อยอดตัวอย่างนี้เป็นระบบที่รันจริงด้วย
Kong API Gateway
+ Laravel / Spring Boot / Node.js
+ PostgreSQL / MySQL
+ Apache Kafka
+ Docker Compose
+ Keycloak
+ Prometheus / Grafana
+ OpenTelemetry
จากนั้นทดสอบ flow:
Create User
-> Create Product
-> Create Order
-> OrderCreated Event
-> Process Payment
-> PaymentCompleted Event
-> Confirm Order
ซึ่งเป็นพื้นฐานสำคัญสำหรับการออกแบบ Microservices ที่มีฐานข้อมูลแยกกันในระบบ Production