โหมดมืด
บทที่ 02 — Inter-Service Communication Overview
← บทที่ 01: Decomposition | สารบัญ | บทที่ 03: REST + gRPC →
TL;DR: บทนี้ตอบ "เลือก sync (REST/gRPC) vs async (broker/event) ยังไง" — 5 use case ของ sync + 5 ของ async, decision flowchart, latency budget, coupling spectrum, protocol selection matrix, war stories (Slack 2021, Discord 2020). ข้ามได้ถ้า: ออกแบบ inter-service communication เป็น
📖 กางศัพท์หลักของบทนี้ก่อนเลย (เจอวนซ้ำตลอด):
- sync (synchronous = "พร้อมกัน") — ส่งแล้วรอ response ก่อนทำต่อ (เหมือนโทรศัพท์)
- async (asynchronous = "ไม่พร้อมกัน") — ส่งแล้วไม่รอ ปลายทางประมวลผลเอง (เหมือนส่ง email/LINE message)
- request-response (ขอ-ตอบ) — ส่ง 1 request แล้วรอ 1 response
- fire-and-forget (ยิงแล้วลืม) — ส่งแล้วไม่รอผล ไม่สนปลายทางทำเสร็จเมื่อไหร่
- publish-subscribe / pub-sub (ประกาศ-บอกรับ) — broadcast ให้ทุกคนที่ "subscribe" ฟัง (เหมือน YouTube channel)
- tight coupling (ผูกแน่น) — service A กับ B พึ่งพากันมาก แก้ A กระทบ B ทันที
- loose coupling (ผูกหลวม) — A กับ B รู้จักกันน้อย แก้ A ไม่กระทบ B ที่ไม่จำเป็น
- backpressure (แรงดันย้อนกลับ) — เมื่อผู้รับ (downstream) ช้ากว่าผู้ส่ง (upstream) ระบบต้องมีวิธีบอกผู้ส่งให้ช้าลง ไม่งั้นข้อมูลท่วม
- downstream / upstream — downstream = ฝั่งปลายทางที่ถูกเรียก, upstream = ฝั่งต้นทางที่เรียก
ตัด service เรียบร้อยแล้ว — ต่อไปคำถาม: service เหล่านี้คุยกันยังไง?
มีตัวเลือกหลัก:
| Style | Protocol | ตัวอย่าง |
|---|---|---|
| Sync request/response | HTTP, gRPC | REST API, gRPC |
| Async messaging | AMQP, Kafka protocol | RabbitMQ, Kafka, NATS |
| Event streaming | Kafka protocol | Kafka, Pulsar |
| RPC | gRPC, Thrift, RSocket | Internal microservice call |
💡 ชื่อเครื่องมือ/โปรโตคอลข้างบน (AMQP, Thrift, RSocket, NATS, Pulsar) ยังไม่ต้องรู้จักทั้งหมดตอนนี้ — แค่รู้ว่ามีตัวเลือกหลายตัวก็พอ ตัวที่บทนี้จะใช้บ่อยคือ REST/gRPC (sync) กับ Kafka/RabbitMQ (async)
บทนี้สอน mental framework ในการเลือก — ไม่ใช่บอกว่าอันไหนดีที่สุด เพราะแต่ละ use case ต่าง
ใช้เวลา 2-3 ชั่วโมง
Part 1: Sync vs Async — แนวคิดพื้นฐาน
1.1 Synchronous (Request/Response)
ตัวอย่าง: HTTP GET /users/1 — wait response
ข้อดี:
- Simple — programming model ตรง
- Immediate response — error/result ทันที
- Easy to debug — call stack ตรง
ข้อเสีย:
- Tight coupling — client + service ต้อง online พร้อมกัน
- Cascading failure — service ล่ม → client ล่มตาม
- Latency stacks — A → B → C → D = sum of all (latency = เวลาหน่วงในการตอบ, stack = ทบกันเป็นผลรวม)
- Backpressure ยาก — slow downstream = block upstream (ดูกล่องกางศัพท์ด้านบน)
1.2 Asynchronous (Fire and Forget / Event)
text
Producer ──publish──→ [Broker] ──→ Consumer
(ไม่ wait)
[Broker เก็บ message → consumer ค่อยอ่าน]ตัวอย่าง: ส่ง message ไป Kafka — Producer return ทันที, Consumer process ทีหลัง
ข้อดี:
- Decoupling — producer ไม่รู้ว่ามี consumer ใคร
- Resilience — consumer down = message รออยู่
- Scalability — เพิ่ม consumer instance
- Buffering — burst load smooth ออก
ข้อเสีย:
- Eventual consistency — ไม่ทันที
- Complexity — debug ยาก, ordering, duplicate
- Operational — broker เป็น infra ที่ต้อง manage
1.3 ภาพรวมเปรียบเทียบ
| Sync | Async | |
|---|---|---|
| Coupling | Tight | Loose |
| Latency | Low | Higher (broker hop) |
| Throughput | Limited | High |
| Resilience | Cascading fail | Isolated |
| Complexity | Low | High |
| Use case | UI request, payment | Event, ETL, fan-out |
Part 2: Fallacies ที่ตอกย้ำ
ทบทวนจากบท 00:
- Network ไม่ reliable → ต้อง retry + timeout
- Latency ไม่ zero → ลด chatty call (= "การคุยซ้ำซากจุกจิก" คือเรียกกันถี่เกินจำเป็น)
📖 ตัวอย่าง chatty call: service A ต้องการข้อมูล 100 รายการจาก B แต่เขียน loop เรียก B ทีละรายการ 100 ครั้ง แทนที่จะส่ง request เดียวขอทั้ง 100 รายการพร้อมกัน (batch) — ผลคือเปลือง round-trip มาก ทำให้ latency รวมพุ่งขึ้นมหาศาล
- Bandwidth ไม่ infinite → compression, pagination
- Network ไม่ secure → mTLS
- Topology เปลี่ยน → service discovery
- Multiple admin → centralized config
- Transport ไม่ฟรี → CDN, regional
- Network ไม่ homogeneous → test across
ทุกครั้งที่ออกแบบ communication — กลับมาดูตาราง
Part 3: เมื่อไหร่ใช้ Sync
💡 กฎทอง 1 ประโยค: ใช้ sync เมื่อ "ผู้ใช้/upstream caller รออยู่จริง ๆ + ต้องการคำตอบทันที"
ลองนึกคำถาม: "ถ้าไม่ตอบใน 1 วินาที — มีคนเดือดร้อนไหม?" ตอบ "ใช่" = sync
✅ 4 use case ที่ sync เหมาะ:
3.1 User-facing query (ผู้ใช้รออยู่)
Scenario: ผู้ใช้เปิด app — กดดูสินค้า — รอเห็นราคา
ทำไม sync:
- ผู้ใช้รออยู่หน้าจอ — ถ้า async คือ "เปิดดูแล้วราคามาทีหลัง" = UX แย่
- Latency expected: < 500ms (user perception)
- ไม่ tolerate downtime — เพราะถ้า service ล่ม = page ว่าง
Trade-off ที่ยอมรับ:
- Tight coupling (Mobile รู้จัก Product Service)
- Cascading fail risk (Product ล่ม → page broken)
Implementation pattern: REST/GraphQL (GraphQL — query language แบบยืดหยุ่น อธิบายละเอียดในบท 04) via API Gateway/BFF (บท 04)
⚠️ Common mistake: ทำ async ทุกอย่าง — แล้วผู้ใช้เห็น "loading..." ตลอด. async ดี สำหรับ "งานหลังบ้าน" ไม่ใช่ "user-facing read"
3.2 Strong consistency (ต้องตรวจก่อนทำ)
Scenario: รับ payment — ต้อง check fraud ก่อนหักเงิน
ทำไม sync:
- การตัดสินใจขึ้นกับ result ของอีก service
- "ต้องเช็คก่อน proceed" — async = race condition (หักเงินไปก่อน fraud ตอบทีหลัง = สาย)
- Fail-fast เป็นข้อดี — ดีกว่า fraud event ตามมาทีหลังต้อง refund
ตัวอย่างจริง:
- Fraud check ก่อน payment
- Inventory check ก่อน confirm order (ถ้าใช้ stock จริง — ไม่ใช่ reservation)
- Auth check ก่อน access resource
- Quota check ก่อน rate-limited action
Trade-off:
- Latency = sum of services (deepest path = total time)
- ถ้า downstream ช้า → upstream ช้าตาม
กฎ: ใช้ timeout + circuit breaker เสมอ (บท 03 + 13)
3.3 Simple read (ดึงข้อมูลที่ต้องการตอนนี้)
Scenario: Order Service ต้องแสดง customer.name บน order page
ทำไม sync:
- ดึงครั้งเดียว, ใช้ทันที, ไม่บ่อย
- ทำ async overhead = ไม่คุ้ม (1 read = 1 message = ซับซ้อน)
Trade-off + ทางเลือก:
| Pattern | Latency | Coupling | Staleness | เหมาะกับ |
|---|---|---|---|---|
| Sync call | 50-100ms | High | 0 | low volume read |
| Cache (read-through) | 1ms (hit) | Medium | TTL | high volume read |
| Local copy (event updated) | 0.1ms | Low | seconds | bounded staleness OK |
| API Composition (BFF) | parallel | Medium | 0 | aggregation page |
กฎทอง: ถ้า hit rate > 100 req/s ต่อ key → ใช้ cache. ถ้า < 10 req/s → sync ก็ได้
3.4 Aggregator / BFF (รวบรวมหลายแหล่ง)
Scenario: Mobile home screen ต้องการ user info + recent orders + recommendations
ทำไม sync:
- Client ต้องการ "1 response ที่ครบ" — ไม่ใช่ 3 callback
- BFF ทำหน้าที่ orchestrate (บท 04)
- parallel call = latency = max ของ slowest call
Implementation:
💡 โค้ดข้างล่างเป็นตัวอย่าง Java ขั้นสูง — ถ้ายังไม่คุ้น
CompletableFutureหรือ virtual threads อ่านแค่แนวคิดพอ: ยิง 3 call ไปยัง service อื่นพร้อมกัน (ไม่รอทีละตัว) แล้วรอให้ครบทั้ง 3 ค่อยรวมผลตอบกลับ — เริ่มจาก Spring MVC + CompletableFuture (Java พื้นฐาน) ก่อน ผลลัพธ์เดียวกับ WebFlux โดยไม่ต้องรู้ reactive
java
// Spring MVC + CompletableFuture — แนะนำสำหรับผู้ที่รู้ Spring MVC อยู่แล้ว
// หมายเหตุ: supplyAsync แบบไม่ระบุ executor จะใช้ ForkJoinPool.commonPool (platform thread ปกติ)
// ถ้าต้องการใช้ virtual thread จริง ๆ (Java 21+) ต้องระบุ executor เอง เช่น
// Executors.newVirtualThreadPerTaskExecutor() เป็น argument ที่สองของ supplyAsync
public HomePage getHome(String userId) throws Exception {
var executor = Executors.newVirtualThreadPerTaskExecutor();
var userF = CompletableFuture.supplyAsync(() -> userClient.getUser(userId), executor); // 40ms
var orderF = CompletableFuture.supplyAsync(() -> orderClient.getRecent(userId), executor); // 80ms
var recoF = CompletableFuture.supplyAsync(() -> recoClient.getRecommendations(userId), executor); // 120ms
CompletableFuture.allOf(userF, orderF, recoF).join(); // รอครบทั้ง 3
return new HomePage(userF.get(), orderF.get(), recoF.get()); // 120ms total
}💡 ทางเลือกที่สอง: Spring WebFlux (reactive) — ผลลัพธ์เดียวกัน แต่ non-blocking I/O เหมาะกับ throughput สูงขึ้น (ไม่ต้องรู้ reactive ก็ใช้ MVC ข้างต้นได้แล้ว):
📖 กางศัพท์ WebFlux:
Mono<T>= "กล่องใส่ค่าที่จะมาทีหลัง" 1 ค่า (แนวคิด reactive — เขียนโค้ดแบบไม่บล็อก thread รอ).Mono.zip(...)= ยิงหลาย call พร้อมกัน (ขนาน) แล้วรอให้ครบทุกตัวค่อยรวมผล. รายละเอียด reactive/WebFlux ดูหมวด spring-boot/java// Spring WebFlux — ยิงหลาย call พร้อมกันแบบ reactive Mono<HomePage> getHome(String userId) { return Mono.zip( // zip = ยิงพร้อมกันแล้วรอครบทุกตัว userClient.getUser(userId), // 40ms orderClient.getRecent(userId), // 80ms recoClient.getRecommendations(userId) // 120ms ).map(tuple -> new HomePage(tuple.getT1(), tuple.getT2(), tuple.getT3())); // รวมผลทั้ง 3 เป็น 1 หน้า }ต่างกันที่:
- MVC + CompletableFuture = ใช้ thread pool — virtual threads (เธรดน้ำหนักเบาที่ Java 21 เพิ่มมาใหม่ สร้างได้เป็นล้านตัวโดยไม่กิน memory มาก ต่างจาก thread ปกติของ OS) ทำให้ scale ได้ดีโดยไม่ต้องเปลี่ยนเป็น reactive — รายละเอียดเพิ่มเติมอยู่หมวด spring-boot/
- WebFlux (reactive) = แนวคิดเขียนโค้ดแบบไม่บล็อก thread รอผล (non-blocking I/O) — เหมาะกับ throughput สูงมาก ๆ
Trade-off:
- 1 service ช้า = response ทั้งหมดช้า → ต้องตั้ง timeout ต่อ call + fallback (บท 13)
- ถ้า service หนึ่งล่ม → partial response (degraded page) ดีกว่า fail ทั้งหมด
Part 4: เมื่อไหร่ใช้ Async
💡 กฎทอง 1 ประโยค: ใช้ async เมื่อ "caller ไม่จำเป็นต้องรอผล" — งานสามารถทำเสร็จทีหลังได้
ลองนึก: "ถ้างานนี้ทำเสร็จใน 5 วินาที / 5 นาที / 5 ชั่วโมง — caller รู้สึกต่างกันไหม?" ตอบ "ไม่ค่อยต่างเท่าไหร่" = async
✅ 5 use case ที่ async เหมาะ:
4.1 Fan-out (1 → many consumers)
Scenario: ลูกค้าสั่งซื้อ — มีหลายระบบที่ต้องทำงาน แต่ละระบบไม่รู้จักกัน
ทำไม async:
- 5 ระบบทำงานอิสระ — ไม่ต้องรอกัน
- Order Service ไม่ต้องรู้จัก 5 ระบบ — แค่ publish event เดียว, ใครจะ consume เป็นเรื่องของแต่ละทีม
- เพิ่มระบบใหม่ในอนาคต (เช่น Fraud Re-check) → subscribe เพิ่มได้โดย Order Service ไม่ต้องแก้
Trade-off:
- Eventual consistency — stock อาจหักช้า 2 วินาที (ผู้ใช้คนถัดไปอาจสั่งสินค้าที่หมดแล้ว — ต้อง reservation pattern หรือ saga)
- Debug ยากขึ้น — ต้องใช้ distributed tracing (บท 15)
Pattern: Pub/Sub via Kafka topic (Pub/Sub = publish/subscribe = "ประกาศ/บอกรับ" รูปแบบที่ผู้ส่งประกาศ event ออกไป ใครสนใจก็ subscribe มารับเอง โดยผู้ส่งไม่ต้องรู้จักผู้รับ)
4.2 Fire-and-forget (ส่งแล้วลืม)
(fire-and-forget = "ยิงแล้วลืม" คือส่ง message ออกไปแล้วไม่รอผล ไม่สนว่าปลายทางทำเสร็จเมื่อไหร่)
Scenario: User สมัครสมาชิก → ส่ง welcome email
ทำไม async:
- Email ส่งช้า 1 นาทีก็ไม่เป็นไร (ผู้ใช้รอกล่อง inbox อยู่แล้ว)
- ถ้า sync —
registerblock รอ SMTP server 5 วินาที = UX แย่ - ถ้า Email service ล่ม → register ยังทำงานได้
ตัวอย่างจริง:
- Welcome email หลัง signup
- Push notification หลัง event
- Slack notification หลัง deploy
- Audit log (write log table)
- Analytics tracking (Mixpanel, Segment)
Trade-off:
- ถ้า message หาย = email ไม่ส่ง (ใช้ at-least-once + DLQ — บท 06)
- ไม่รู้ผลทันที — debug ต้องดู log ของ consumer
4.3 Heavy / long-running (เกิน HTTP timeout)
Scenario: User upload video 500MB → ต้อง transcode เป็น 5 quality (HD/SD/mobile/...)
ทำไม async:
- HTTP timeout มัก 30-120s — งานเกินไม่ถึงตอบ
- User ไม่ได้นั่งหน้าจอ 10 นาที — ปิด tab ไปได้, แจ้งเตือนเมื่อเสร็จ
- Worker scale แยกได้ — มี 100 video upload พร้อมกัน → spin up 10 transcoder
ตัวอย่างจริง:
- Video/image transcoding
- Report generation (PDF 1000 หน้า)
- Data export (CSV 1M row)
- ML model training
- Bulk email send
Pattern: Job queue (Kafka, SQS, RabbitMQ, Redis Streams)
4.4 Bulk / batch (1 → ปริมาณมาก)
Scenario: ส่ง statement รายเดือนให้ลูกค้า 1 ล้านคน
ทำไม async:
- ห้าม sync — 1M HTTP call serially = หลายวัน
- Backpressure ที่ Email Worker — broker เก็บไว้, worker ดึงตามจังหวะ (บท 06 Part 7)
- Worker ล่ม → retry/resume ได้ (event ยังอยู่)
- Rate limit downstream — SMTP server รับได้แค่ 100 emails/sec — broker buffer ให้
ตัวอย่างจริง:
- Monthly statement
- Daily aggregation job
- Database migration (10M row)
- Bulk import / export
กฎ: bulk ที่ขนาด > 1000 records = async เสมอ
4.5 Event sourcing / audit (เก็บประวัติทั้งหมด)
Scenario: ระบบ banking ต้องเก็บทุก transaction เป็น immutable log สำหรับ audit
ทำไม async:
- Event เป็น source of truth — state เป็น projection ของ event
- Append-only log = ทำ analytics ย้อนหลังได้
- ใช้ replay เพื่อ debug หรือสร้าง view ใหม่
- Audit / compliance (GDPR/SOX/HIPAA = กฎหมาย/มาตรฐานที่บังคับให้เก็บประวัติข้อมูลไว้ตรวจสอบย้อนหลังได้) ต้องการ immutable trail
ตัวอย่างจริง:
- Banking transaction log
- Healthcare record changes
- E-commerce order state machine
- IoT sensor data
- Git history (เป็น event sourcing แบบหนึ่ง!)
ลึกที่บท 14 — Event Sourcing + CQRS + Projection
Part 5: สรุป Sync vs Async — Decision flowchart
🎯 Decision flowchart — Sync vs Async
text
มี communication ระหว่าง 2 service ต้องเลือก →
Q1: caller รออยู่ + ต้องการคำตอบทันทีไหม?
✅ ใช่ — user/upstream รอ → Sync (REST/gRPC)
❌ ไม่ — งานทำเสร็จทีหลังได้ → ต่อ Q2
Q2: caller ต้องรู้ผลของ downstream operation ไหม?
✅ ใช่ (เช่น "ลด stock ได้ไหม") → Sync (หรือ Saga + Compensation)
❌ ไม่ — fire-and-forget → Async pub/sub
Q3: มี 1 หรือ หลาย consumer ของ event นี้?
• 1 consumer งานเดียว → Async queue (RabbitMQ, SQS)
• หลาย consumer broadcast → Async topic (Kafka, SNS)
Q4: งาน > HTTP timeout ไหม?
✅ ใช่ — long running → Async + status poll/webhook
❌ ไม่ → ไปตาม Q1-3
Q5: ต้องการ replay/audit ในอนาคตไหม?
✅ ใช่ → Async stream (Kafka — retention ยาว)📋 ตารางสรุป trade-off
| Sync | Async | |
|---|---|---|
| Latency | ทันที (50ms-1s) | Eventual (sec-min-hr) |
| Coupling | Tight | Loose |
| Consistency | Strong (immediate) | Eventual |
| Fault tolerance | Cascading risk | Decoupled |
| Complexity | ต่ำ | สูง (broker, idempotent, DLQ — ดู gloss ด้านล่าง) |
| Debug | Stack trace ตรง | Need tracing (บท 15) |
| Testing | Unit + Integration | + Contract + Replay |
📖 กางศัพท์: idempotent = "ทำซ้ำได้ผลเดิม" คือถ้า retry ส่ง message ซ้ำ ผลลัพธ์ต้องเหมือนเดิม ไม่ใช่ทำงานซ้ำ (เช่นหักเงินซ้ำ). DLQ (Dead Letter Queue) = คิวพักสำหรับ message ที่ consumer process ไม่สำเร็จซ้ำแล้วซ้ำอีก — กันไม่ให้ message เสียหายไปติดขวางคิวหลัก
Part 6: Pattern แต่ละแบบ
6.1 Request/Response (Sync)
แบบพื้นฐานที่สุด — service A ถาม service B แล้วรอคำตอบทันที (เหมือนโทรศัพท์) เหมาะเมื่อต้องการผลลัพธ์เดี๋ยวนั้น แต่ทำให้ A ผูกกับ B (ถ้า B ล่ม A ก็พลอยช้า/พัง):
text
A ──HTTP GET──→ B
←─JSON──────Tools: REST, gRPC, GraphQL
6.2 Pub/Sub (Async, fan-out)
"Pub/Sub" — ผู้ส่ง publish event ไป topic เดียว แล้ว subscriber หลายตัวรับไปทำงานต่างกัน (fan-out) ผู้ส่งไม่ต้องรู้ว่าใครรับบ้าง ทำให้เพิ่ม consumer ใหม่ได้โดยไม่แก้ผู้ส่ง:
Tools: Kafka topic, RabbitMQ fanout, Redis Pub/Sub (fire-and-forget — ไม่มี persistence ใช้เฉพาะ ephemeral notification เท่านั้น), Redis Streams (persistent log with consumer groups — ใช้สำหรับ durable async messaging แทน Pub/Sub ถ้าต้องการไม่สูญหาย)
⚠️ Redis Pub/Sub ≠ Redis Streams: Pub/Sub เป็น fire-and-forget ไม่มี persistence — subscriber ที่ disconnect จะ miss message ทั้งหมด. Redis Streams (XADD/XREAD) เป็น persistent append-only log มี consumer group + ack — ใช้อันนี้ถ้าต้องการ durable messaging
6.3 Point-to-point Queue (Async, task)
ต่างจาก pub/sub — "queue" ส่งงานให้ consumer "ตัวเดียว" หยิบไปทำ (ไม่ใช่ทุกตัว) เหมาะกับการกระจายงาน (task distribution) ให้ worker หลายตัวช่วยกันทำโดยไม่ซ้ำ:
Tools: RabbitMQ queue, SQS, NATS
🚀 โซนขั้นสูง — ข้ามได้ (หัวข้อ 6.4–6.6): Event Sourcing, CQRS, Saga เป็น pattern ขั้นสูง — มือใหม่อ่านผ่าน ๆ พอให้รู้จักชื่อ ยังไม่ต้องเข้าใจลึก เดี๋ยวลงรายละเอียดเต็มที่บท 12 (Event-Driven) และ 14 (Data Management)
6.4 Event Sourcing
แทนที่จะเก็บ "สถานะปัจจุบัน" Event Sourcing เก็บ "ทุกเหตุการณ์ที่เคยเกิด" เป็นลำดับ — สถานะ ณ ตอนนี้ได้จากการ replay event ทั้งหมด ข้อดีคือมีประวัติครบ ย้อนดูได้ แต่ซับซ้อนกว่า:
text
ทุก action → "Event" → append to event store
State = replay eventsTools: Kafka, EventStoreDB, Axon
6.5 CQRS (Command Query Responsibility Segregation)
CQRS แยก "ฝั่งเขียน" (command ที่เปลี่ยนข้อมูล) ออกจาก "ฝั่งอ่าน" (query) เป็นคนละ model — ทำให้ optimize แต่ละด้านได้อิสระ (เช่นฝั่งอ่านทำ denormalized view ให้เร็ว) มักใช้คู่ event sourcing:
text
Write side: command → aggregate → event
Read side: query → optimized view (denormalized)
[Sync write] → [Async update read model]ดูบท 14
6.6 Saga (distributed transaction)
เมื่อ transaction กินหลาย service เราใช้ database transaction เดียวครอบไม่ได้ — "Saga" แก้โดยทำเป็นลำดับขั้น แต่ละขั้น commit แยก และถ้าขั้นใดล้มเหลวก็เรียก "compensating action" ย้อนขั้นก่อนหน้า (รายละเอียดในบท event-driven):
text
Order placed → Payment authorized → Stock reserved → Shipped
ถ้าขั้นไหน fail → compensating actions ย้อนกลับดูบท 12
Part 7: REST vs gRPC vs Messaging — ตัวอย่าง
7.1 Scenario: Place Order
Option A: All Sync REST
ข้อดี: simple ข้อเสีย:
- Latency = sum
- Inventory ล่ม → all fail
- ต้องมี rollback logic ถ้า partial success
Option B: Sync + Event
ข้อดี: client เร็ว, fail-tolerant ข้อเสีย: eventual consistency
Option C: Saga (Orchestration)
text
[Order Service] starts Saga:
1. Reserve stock → wait response
2. Charge payment → wait response
3. If both OK → confirm
4. If any fail → compensateใช้ workflow tool (Temporal, Camunda)
Choice depends on:
- User expectation (instant vs eventual)
- Reliability requirement
- Throughput
Part 8: Coupling Spectrum
→ เลือกอันที่ "loose enough" สำหรับ requirement
Part 9: Latency Budget
ตัวอย่าง: SLO = P99 < 500ms
📖 กางศัพท์: SLO (Service Level Objective) = "เป้าหมายระดับบริการ" ที่เราตั้งไว้ เช่น "ต้องตอบเร็วกว่า 500ms". P99 (99th percentile) = "เปอร์เซ็นไทล์ที่ 99" หมายถึง 99% ของ request ตอบเร็วกว่าค่านี้ (มองที่ตัวที่ช้า ไม่ใช่ค่าเฉลี่ย เพราะค่าเฉลี่ยซ่อนตัวที่ช้าไว้). ดังนั้น "P99 < 500ms" = "99 request จาก 100 ต้องตอบภายใน 500ms"
- Total = 50 + 5 + 100 + max(20, 30, 15) = ~185ms (parallel)
- Total = 50 + 5 + 100 + 20+30+15 = ~220ms (sequential)
→ Sequential 4 service → close to budget
ทุก service เพิ่ม ต้องตรวจ ว่า latency ยังไหวไหม
💡 เทคนิคลด p99 (tail latency) ที่ยังไม่ได้พูดถึง — request hedging: ส่ง 2 request พร้อมกันไปยัง replica/instance ต่างกัน แล้วใช้ตัวที่ตอบกลับมาก่อน (ทิ้งอีกตัว). ช่วยลด p99 ได้มากเพราะ probability ที่ทั้ง 2 instance จะช้าพร้อมกันต่ำ — แลกกับ load เพิ่ม 2 เท่า. รายละเอียดดูบท 13 Resilience
Part 10: Service Contract
ไม่ว่าใช้ protocol ไหน — ต้องมี contract ที่ชัดเจน
10.1 Sync: OpenAPI / Proto
ฝั่ง synchronous เขียน contract เป็นเอกสารทางการ — OpenAPI (สำหรับ REST) หรือ Protobuf (สำหรับ gRPC) ที่ทั้งสองฝั่งยึดถือ และใช้ generate code/client ได้ ลดความเข้าใจผิดเรื่องรูปแบบข้อมูล:
yaml
# OpenAPI
paths:
/users/{id}:
get:
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/User'proto
// gRPC
message User {
int64 id = 1;
string name = 2;
}
service UserService {
rpc GetUser(GetUserRequest) returns (User);
}10.2 Async: Schema Registry
ฝั่ง asynchronous event ก็ต้องมี contract — ใช้ "schema registry" (เช่น Avro + Confluent Schema Registry) กำหนดรูปแบบ event และคุมการเปลี่ยนแปลงให้เข้ากันได้ (backward compatible) ป้องกัน consumer พังเมื่อ producer เปลี่ยน schema:
json
// AsyncAPI (spec มาตรฐานสำหรับ document event-driven API — เหมือน OpenAPI แต่สำหรับ async message) or Avro schema
{
"type": "record",
"name": "OrderPlaced",
"fields": [
{"name": "orderId", "type": "string"},
{"name": "customerId", "type": "string"},
{"name": "totalAmount", "type": "double"}
]
}→ Producer + Consumer ใช้ schema เดียวกัน. registry ตรวจ compatibility
10.3 Versioning
- Additive: เพิ่ม field optional — backward compatible
- Breaking: ลบ field, เปลี่ยน type — ต้อง v2 separate
ดูบท 03 + 06 + 08
Part 11: Decision Framework
ใช้ flow chart นี้:
หรือถาม 5 คำถาม:
- User waiting? → Sync
- Multiple consumers? → Event/Pub-Sub
- Long running? → Async
- Need replay? → Kafka
- Strict ordering? → Kafka partition or RabbitMQ single queue
Part 12: Anti-Patterns
12.1 Sync Everything
กับดักแรก: ใช้ synchronous call ทุกที่ — service เรียกกันต่อ ๆ เป็นทอด ทำให้ผูกกันแน่นและถ้าตัวใดช้า/ล่ม กระทบทั้งสาย กลายเป็น distributed monolith ที่แค่เปลี่ยนการเรียกในเครื่องเป็น HTTP:
ทุก service call sync → distributed monolith ในรูปแบบ HTTP
12.2 Async Everything
กับดักตรงข้าม: ทำทุกอย่างเป็น async/event แม้แต่ส่วนที่ผู้ใช้รอผลทันที — ทำให้ UI ต้องเจอ eventual consistency (ข้อมูลยังไม่อัปเดต) สร้างความสับสนและ UX แย่ ควรใช้ sync ตรงที่ผู้ใช้รอผล:
ทุกอย่าง event → eventual consistency แม้กับ user-facing UI
12.3 Mixed Without Pattern
บางครั้ง sync บางครั้ง async ใน flow เดียวกัน → debug ยาก, contract ไม่ชัด
12.4 Shared DB (worst!)
text
[Service A] ──→ DB ←── [Service B]
↑
ทั้งคู่ write ตาราง เดียวกัน→ ไม่ใช่ microservice อีกต่อไป
Part 12.5: Production War Stories
🔥 บริษัท e-commerce (2022): sync chain 7 services ทำ Black Friday timeout cascade
checkout → sync REST chain ผ่าน 7 service → 1 service ช้า (P99 800ms) → cumulative 5s → timeout → retry storm → ทุก service ล่ม
Lesson:
- Async event-driven สำหรับ business flow ที่ไม่ต้อง response ทันที (notification, fulfillment)
- ใช้ eventual consistency + idempotency-key + outbox
- Sync chain > 3 hop = สัญญาณ design ผิด
🔥 Discord (2020): WebSocket sticky session ทำ scaling พัง
WebSocket ต้องอยู่กับ same server → load balancer sticky → 1 server load สูง, อื่น idle → cascade
Lesson:
- WebSocket + autoscale → ต้อง broker pattern (Redis pub/sub, NATS) ไม่ใช่ sticky
- Stateless server + state ใน external store
- Graceful drain ตอน scale-in: hold WebSocket open + drain over 30s
🔥 Slack (2021): gRPC bidi-streaming ผ่าน K8s ALB ล่ม 3 ชม.
ALB cycle connection ทุก 60s (default idle timeout) → bi-directional stream ขาดทุก 60s → client reconnect storm
Lesson:
- gRPC streaming + cloud LB → ต้องตั้ง idle timeout > stream lifetime
- หรือใช้ keepalive ping (gRPC client + server config)
- Test long-running stream ใน staging ด้วย ALB จริง
Part 12.6: 📋 Cheat Sheet
Sync vs Async — เลือกตอนไหน
text
ใช้ Sync (REST/gRPC) เมื่อ:
✅ User waiting for response
✅ Simple request-response (read, simple write)
✅ Low fan-out (1-2 downstream)
✅ Strong consistency required
✅ Real-time (P99 < 100ms requirement)
ใช้ Async (Event/Message) เมื่อ:
✅ Long-running process (> 1s)
✅ Fan-out > 3 services
✅ Background work (email, ML, batch)
✅ Decouple producer/consumer
✅ Audit log / event sourcing
✅ Burst tolerance (queue smooths spike)
ใช้ Stream (gRPC streaming, WebSocket, SSE) เมื่อ:
✅ Real-time updates (chat, live dashboard)
✅ Large response (paginate stream)
✅ Bi-directional (chat, gaming)Protocol selection matrix
| Use case | Protocol |
|---|---|
| Public API for browser/mobile | REST/JSON (universal) |
| Internal service-to-service, high perf | gRPC/Protobuf |
| Flexible client query | GraphQL (BFF) |
| One-to-many notification | Pub/Sub broker |
| Task queue + ack | Queue broker (RabbitMQ, SQS) |
| Event log + replay | Kafka |
| Server push to browser | SSE (Server-Sent Events = server ส่งข้อมูลไป browser แบบ one-way stream, simple) หรือ WebSocket (bi-di) — ดู note ⓘ ด้านล่างเรื่อง WebTransport |
| File / large blob | Object storage URL + signed link (not in API body) |
| Real-time game / chat | WebSocket + binary protocol |
ⓘ 2026 alternative — ขั้นสูง ข้ามได้: WebTransport (over HTTP/3) เริ่ม stable ใน Chrome/Edge แล้ว (ตรวจสอบเวอร์ชัน/browser support ล่าสุดก่อนใช้จริง) — ใช้แทน WebSocket ได้ในบาง use case เพราะรองรับหลาย stream คู่ขนานใน 1 connection และใช้ UDP เป็นพื้นฐาน (แทน TCP) ซึ่งช่วยลด head-of-line blocking (ปัญหาที่ 1 packet หายแล้วทุก stream ต้องรอ — อธิบายละเอียดในบท 03) ใช้ได้กับ browser-only client ที่ใหม่พอเท่านั้น SSE over HTTP/2 multiplexed ก็เป็นทางเลือกถ้าต้องการ server push แบบ simple
Communication anti-patterns
text
❌ Sync chain > 3 services for 1 user request
→ Use async or compose at BFF (parallel calls)
❌ Async for "I need answer now"
→ User เห็น "processing..." UI - acceptable เฉพาะถ้ามี polling/notification
❌ Event-carried state เกินจำเป็น (payload 100KB)
→ Send just ID + downstream fetch detail
❌ Tight coupling via event schema
→ Schema Registry + backward compat + versioned event
❌ No idempotency in retry
→ Idempotency-Key (sync) หรือ inbox table (async)Part 13: Checkpoint
- Sync vs Async — เลือกอันไหนสำหรับ user login? สำหรับ ส่ง email confirmation?
- ทำไม "sync everything" → distributed monolith?
- Cascading failure คืออะไร? วิธีลด?
- Latency budget ใน microservices ทำไมยาก?
- Eventual consistency ยอมรับเมื่อไหร่ — ไม่ยอมรับเมื่อไหร่?
- Pub/Sub ต่าง Point-to-Point queue ยังไง?
- ทำไมต้องมี Service Contract (OpenAPI / Schema)?
- (ขั้นสูง — ข้ามได้) Saga ใช้แก้ปัญหาอะไร?
- (ขั้นสูง — ข้ามได้) CQRS คืออะไรสั้น ๆ?
- Service A เรียก B 5 ครั้งต่อ request — smell อะไร?
Part 14: สรุปบทนี้
- Sync = simple, low-latency, แต่ tight coupling + cascading fail
- Async = decoupled, resilient, แต่ eventual consistency
- Pattern = req/res, pub/sub, queue, event source, CQRS, saga
- Contract = OpenAPI / gRPC proto / Avro schema
- Decision = user wait, multiple consumer, long-running, replay, ordering
- Anti-pattern = sync everything, shared DB, chatty call
บทถัดไป — REST + gRPC Between Services — ลึกของ sync communication (timeout, retry, idempotency)