บทที่ 2 · Part 1 — Foundation

Architecture

Cluster, Worker, Task Queue, Event History, Namespace และเส้นทางเดินของ 1 transaction

ส่วนประกอบมีไม่เยอะ แต่ความสัมพันธ์ระหว่างกันสำคัญมาก โดยเฉพาะข้อเท็จจริงที่ว่า โค้ดของคุณไม่เคยรันบน Temporal Cluster

จบบทนี้คุณจะ

  • แยกออกว่าอะไรคือ Cluster อะไรคือ Worker และใครรันโค้ดของคุณ
  • เข้าใจ Task Queue, Event History และ Namespace
  • ไล่เส้นทางของ 1 transaction ตั้งแต่ต้นจนจบได้

ภาพรวม

จุดที่คนเข้าใจผิดบ่อยที่สุด

Temporal Cluster ไม่เคยเห็นและไม่เคยรันโค้ดของคุณ มันเป็นแค่ที่เก็บ state และคิวงาน โค้ดทั้งหมดรันใน Worker process ที่คุณ deploy เอง แปลว่าข้อมูลลูกค้าและ credential ที่ใช้เรียก bank API อยู่ในโครงสร้างพื้นฐานของคุณ ไม่ได้อยู่ที่ Temporal (เรื่องนี้สำคัญมากตอนคุยกับทีม security — ดูบทที่ 12)

Temporal Cluster

Cluster คือ backend ที่ทำหน้าที่หลัก 3 อย่าง:

  • Event History — log ถาวรของทุกสิ่งที่เกิดขึ้นกับ workflow แต่ละตัว นี่คือ source of truth ของสถานะ
  • Task Queues — คิวที่ Worker มา poll เอางานไปทำ
  • Visibility store — index สำหรับค้นหา/ลิสต์ workflow (เช่น "หา top-up ที่ค้างอยู่ของลูกค้ารายนี้")

รันได้ 3 แบบ:

แบบคำสั่ง/รูปแบบใช้เมื่อไหร่
CLI dev servertemporal server start-devdevelopment และ test เท่านั้น — เป็น single process, ค่าเริ่มต้นเก็บใน memory
Self-hosteddeploy เอง + ฐานข้อมูล (Cassandra / PostgreSQL / MySQL) + Elasticsearchเมื่อต้องเก็บทุกอย่างในโครงสร้างพื้นฐานของตัวเอง (ข้อกำหนดที่พบบ่อยในสถาบันการเงิน)
Temporal Cloudmanaged serviceไม่อยากดูแล cluster เอง — ต้องผ่าน vendor risk assessment ก่อน

ห้ามใช้ dev server บน production

แม้จะใส่ --db-filename ให้ persist ลงไฟล์ SQLite ได้ก็ตาม dev server ออกแบบมาเพื่อ development เท่านั้น ไม่มี HA ไม่มี scaling

Worker

Worker คือ process ที่คุณเขียนและ deploy เอง (ในที่นี้คือ Go binary ตัวหนึ่ง) มันวนลูป poll → execute → report กับ Task Queue ที่ระบุไว้

Worker 1 ตัว host โค้ด 2 ประเภท:

WorkflowActivity
หน้าที่สั่งงาน จัดลำดับ ตัดสินใจลงมือทำงานจริงที่มี side effect
ข้อบังคับต้อง deterministic ห้าม I/Oทำอะไรก็ได้ — เรียก API, เขียน DB, อ่านไฟล์
ตัวอย่างในเคส wallet"ตรวจ limit ก่อน แล้วค่อยหักธนาคาร ถ้าหักไม่ผ่านให้จบเลย""POST ไปที่ /v1/debit ของธนาคาร"
ถ้าล้มถูก replay ใหม่จาก historyถูก retry ตาม Retry Policy

การแบ่งนี้คือหัวใจของ Temporal: ความไม่แน่นอนทั้งหมดถูกกักไว้ใน Activity ส่วน Workflow เป็นตรรกะบริสุทธิ์ที่สร้างขึ้นใหม่ได้เสมอ

Task Queue

Task Queue เป็นแค่ ชื่อ (string) ที่ใช้จับคู่ระหว่างงานกับ Worker ไม่ต้องสร้างล่วงหน้า พอมี Worker มา poll ชื่อนี้ และมีคน start workflow ด้วยชื่อนี้ ก็จับคู่กันเอง

ในระบบ wallet จริง มักแยก Task Queue ตามคุณสมบัติของงาน:

wallet-core          → workflow หลัก (เบา, จำนวนมาก)
bank-integration     → activity ที่ยิง bank API (ต้องคุม concurrency ตาม rate limit ธนาคาร)
notification         → push / SMS (ล้มได้ ไม่กระทบเงิน)
reconciliation       → batch กลางคืน (กิน CPU/memory สูง)

เหตุผลที่ต้องแยก: ถ้า batch reconciliation กิน worker หมด การเติมเงินของลูกค้าจะไม่มี worker เหลือทำ การแยก queue ทำให้จำกัดผลกระทบได้ (เรื่อง capacity และการกันไม่ให้ tenant ใหญ่กิน worker หมด อยู่ในบทที่ 11)

Event History

ทุก workflow execution มี event history ของตัวเอง เป็น log แบบ append-only ที่บันทึกทุกอย่างที่เกิดขึ้น ลองดูของ top-up 1 รายการ:

 1  WorkflowExecutionStarted        input: {walletID, amount: 1000}
 2  WorkflowTaskScheduled
 3  WorkflowTaskStarted
 4  WorkflowTaskCompleted
 5  ActivityTaskScheduled           CheckLimit
 6  ActivityTaskStarted
 7  ActivityTaskCompleted           result: {ok: true}
 8  WorkflowTaskScheduled
 ...
12  ActivityTaskScheduled           ChargeBank
13  ActivityTaskStarted
14  ActivityTaskFailed              "connection reset"   ◀── ธนาคารล่ม
15  ActivityTaskScheduled           ChargeBank (attempt 2)
16  ActivityTaskStarted
17  ActivityTaskCompleted           result: {bankRef: "BK-8891"}
 ...
25  WorkflowExecutionCompleted

สิ่งที่ได้จาก history นี้:

  • Audit trail ฟรี — ตอบได้ว่าธุรกรรมนี้เกิดอะไรขึ้นบ้าง เวลาไหน ลองใหม่กี่ครั้ง เพราะอะไร
  • กู้สถานะได้ — worker ตัวใหม่อ่าน history แล้วสร้างสถานะเดิมขึ้นมาใหม่
  • Debug ได้จริง — เปิด Web UI แล้วเห็น timeline ทั้งหมด ไม่ต้องไล่ log ข้าม service

ข้อจำกัดที่ต้องจำ

  • payload เดี่ยว สูงสุด 2 MB
  • gRPC message สูงสุด 4 MB
  • history ทั้งก้อน สูงสุด 50 MB (ในทางปฏิบัติควรคุมให้ต่ำกว่า 10 MB)

เพราะฉะนั้น อย่าส่งข้อมูลก้อนใหญ่ผ่าน workflow เช่น ไฟล์ statement หรือ list ธุรกรรม 100,000 แถว ให้ส่ง reference (S3 key, transaction ID) แทน แล้วให้ activity ไปโหลดเอง — ดูบทที่ 11

Namespace

Namespace คือหน่วยแบ่งแยกเชิงตรรกะภายใน cluster — workflow ID จะไม่ชนกันข้าม namespace และตั้งค่า retention, RBAC, rate limit แยกกันได้

รูปแบบการแบ่งที่ใช้บ่อยในองค์กรการเงิน:

  • ตาม environment: wallet-dev, wallet-uat, wallet-prod
  • ตามระดับความอ่อนไหวของข้อมูล เพื่อให้กำหนดสิทธิ์เข้าถึงต่างกันได้
  • ตาม business unit เมื่อแต่ละทีมต้องการ retention policy ของตัวเอง

Retention period สำคัญเป็นพิเศษ — เป็นตัวกำหนดว่า history ของ workflow ที่จบแล้ว จะถูกเก็บนานแค่ไหน ซึ่งต้องสอดคล้องกับข้อกำหนดการเก็บเอกสารของธุรกิจคุณ อย่าถือว่า Temporal เป็นที่เก็บ audit log ระยะยาว ถ้าต้องเก็บหลายปี ให้ส่งออกไปยังระบบจัดเก็บของคุณเอง

เส้นทางของ 1 transaction

สังเกตขั้นตอน "รัน workflow ตั้งแต่ต้น แต่ใช้ผลเดิมจาก history" — workflow ถูกรัน ตั้งแต่บรรทัดแรกใหม่ทุกครั้ง แต่ step ที่มีผลลัพธ์อยู่ใน history แล้ว จะไม่ถูกเรียกซ้ำ SDK จะคืนค่าเดิมกลับมาให้ทันที

นี่คือสาเหตุที่ workflow ต้อง deterministic และเป็นหัวข้อของบทถัดไป ซึ่งเป็นบทที่สำคัญที่สุดของคอร์สนี้

คำศัพท์ที่ควรจำจากบทนี้

Command = สิ่งที่ workflow code ขอให้ทำ (เช่น "schedule activity นี้") · Event = สิ่งที่ถูกบันทึกลง history จริง · ความไม่ตรงกันระหว่าง 2 อย่างนี้คือที่มาของ non-determinism error