Skip to content

บทที่ 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 เหล่านี้คุยกันยังไง?

มีตัวเลือกหลัก:

StyleProtocolตัวอย่าง
Sync request/responseHTTP, gRPCREST API, gRPC
Async messagingAMQP, Kafka protocolRabbitMQ, Kafka, NATS
Event streamingKafka protocolKafka, Pulsar
RPCgRPC, Thrift, RSocketInternal 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 ภาพรวมเปรียบเทียบ

SyncAsync
CouplingTightLoose
LatencyLowHigher (broker hop)
ThroughputLimitedHigh
ResilienceCascading failIsolated
ComplexityLowHigh
Use caseUI request, paymentEvent, ETL, fan-out

Part 2: Fallacies ที่ตอกย้ำ

ทบทวนจากบท 00:

  1. Network ไม่ reliable → ต้อง retry + timeout
  2. Latency ไม่ zero → ลด chatty call (= "การคุยซ้ำซากจุกจิก" คือเรียกกันถี่เกินจำเป็น)

    📖 ตัวอย่าง chatty call: service A ต้องการข้อมูล 100 รายการจาก B แต่เขียน loop เรียก B ทีละรายการ 100 ครั้ง แทนที่จะส่ง request เดียวขอทั้ง 100 รายการพร้อมกัน (batch) — ผลคือเปลือง round-trip มาก ทำให้ latency รวมพุ่งขึ้นมหาศาล

  3. Bandwidth ไม่ infinite → compression, pagination
  4. Network ไม่ secure → mTLS
  5. Topology เปลี่ยน → service discovery
  6. Multiple admin → centralized config
  7. Transport ไม่ฟรี → CDN, regional
  8. 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 + ทางเลือก:

PatternLatencyCouplingStalenessเหมาะกับ
Sync call50-100msHigh0low volume read
Cache (read-through)1ms (hit)MediumTTLhigh volume read
Local copy (event updated)0.1msLowsecondsbounded staleness OK
API Composition (BFF)parallelMedium0aggregation 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 — register block รอ 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

SyncAsync
Latencyทันที (50ms-1s)Eventual (sec-min-hr)
CouplingTightLoose
ConsistencyStrong (immediate)Eventual
Fault toleranceCascading riskDecoupled
Complexityต่ำสูง (broker, idempotent, DLQ — ดู gloss ด้านล่าง)
DebugStack trace ตรงNeed tracing (บท 15)
TestingUnit + 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 events

Tools: 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 คำถาม:

  1. User waiting? → Sync
  2. Multiple consumers? → Event/Pub-Sub
  3. Long running? → Async
  4. Need replay? → Kafka
  5. 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 caseProtocol
Public API for browser/mobileREST/JSON (universal)
Internal service-to-service, high perfgRPC/Protobuf
Flexible client queryGraphQL (BFF)
One-to-many notificationPub/Sub broker
Task queue + ackQueue broker (RabbitMQ, SQS)
Event log + replayKafka
Server push to browserSSE (Server-Sent Events = server ส่งข้อมูลไป browser แบบ one-way stream, simple) หรือ WebSocket (bi-di) — ดู note ⓘ ด้านล่างเรื่อง WebTransport
File / large blobObject storage URL + signed link (not in API body)
Real-time game / chatWebSocket + 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

  1. Sync vs Async — เลือกอันไหนสำหรับ user login? สำหรับ ส่ง email confirmation?
  2. ทำไม "sync everything" → distributed monolith?
  3. Cascading failure คืออะไร? วิธีลด?
  4. Latency budget ใน microservices ทำไมยาก?
  5. Eventual consistency ยอมรับเมื่อไหร่ — ไม่ยอมรับเมื่อไหร่?
  6. Pub/Sub ต่าง Point-to-Point queue ยังไง?
  7. ทำไมต้องมี Service Contract (OpenAPI / Schema)?
  8. (ขั้นสูง — ข้ามได้) Saga ใช้แก้ปัญหาอะไร?
  9. (ขั้นสูง — ข้ามได้) CQRS คืออะไรสั้น ๆ?
  10. 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)


← บทที่ 01 | สารบัญ | บทที่ 03: REST + gRPC