ไวยากรณ์ของ Post-it — สัญลักษณ์ใน Event Storming
สองบทที่แล้วเราแปะ Post-it สีส้มเรียงกันเป็นเส้นเวลา จนเห็นเรื่องราวของธุรกิจทั้งเส้นบนผนังเดียว บทนี้จะเติมสีที่เหลือเข้าไป ทีละสี จนบอร์ดของเรามี “ไวยากรณ์” ครบ — พอรู้ว่าสีไหนแปลว่าอะไร คุณจะอ่านบอร์ด EventStormingEventStormingเวิร์กช็อปสำรวจ domain แบบร่วมมือกันทั้งทีม คิดค้นโดย Alberto Brandolini ใช้ Post-it สีส้ม (domain event) แปะบนผนังยาว ๆ เพื่อดึงความรู้จากหัวคนหลายฝ่ายออกมาให้เห็นพร้อมกันภายในไม่กี่ชั่วโมงProcess ของใครก็ได้เหมือนอ่านประโยคภาษาไทย และที่สำคัญกว่านั้น คุณจะ “เขียน” มันเองเป็น
เคล็ดลับที่ทำให้ Event Storming เรียนง่ายคือมันมีคำศัพท์อยู่ไม่กี่คำ Brandolini ออกแบบให้จงใจ — สีน้อย กฎน้อย เพื่อให้คนที่ไม่ใช่โปรแกรมเมอร์ก็ร่วมวงได้ จะไล่ทีละสีตามลำดับที่มันเชื่อมกันจริงๆ ไม่ใช่เรียงตามพจนานุกรม
ทบทวน 30 วินาที: สีส้มคือหัวใจ
หัวข้อที่มีชื่อว่า “ทบทวน 30 วินาที: สีส้มคือหัวใจ”Post-it สีส้มคือ Domain EventDomain Eventสิ่งที่ “เกิดขึ้นแล้ว” และมีความหมายต่อผู้เชี่ยวชาญธุรกิจ เขียนเป็นกริยาอดีต (เช่น OrderPlaced, PaymentCaptured) แทนด้วย Post-it สีส้ม เป็นหน่วยตั้งต้นของ EventStorming ทุกแบบTactical Design — “สิ่งที่เกิดขึ้นแล้ว” เขียนเป็นกริยาอดีตเสมอ เช่น OrderPlaced, PaymentCaptured, FoodDelivered มันคือความจริงที่เกิดไปแล้วและย้อนไม่ได้ ทุกสีที่เหลือมีไว้เพื่อตอบคำถามเดียว: “แล้วอะไรทำให้ event สีส้มแต่ละใบเกิดขึ้น?”
ถ้าจำได้แค่ประโยคเดียวจากทั้งบท ขอให้เป็นประโยคนี้ — สีส้มคือ “ผล” ส่วนสีอื่นคือ “เหตุ” และ “ตัวกลาง” ที่พาไปสู่ผลนั้น
สีฟ้า: Command — ความตั้งใจก่อนที่ event จะเกิด
หัวข้อที่มีชื่อว่า “สีฟ้า: Command — ความตั้งใจก่อนที่ event จะเกิด”ก่อนที่ OrderPlaced จะเกิด ต้องมีใครสักคน “ตั้งใจสั่ง” ให้มันเกิดก่อน ความตั้งใจนั้นคือ CommandCommandความตั้งใจหรือการตัดสินใจที่ “สั่งให้เกิด” domain event เขียนเป็นกริยาคำสั่ง (เช่น PlaceOrder) แทนด้วย Post-it สีฟ้า มักมี actor เป็นผู้ลงมือTactical Design — Post-it สีฟ้า เขียนเป็นกริยาคำสั่ง (รูปปัจจุบัน) เช่น PlaceOrder, CapturePayment, AcceptOrder
สังเกตคู่ตรงข้ามให้ดี มันคือกุญแจของทั้งเรื่อง:
PlaceOrder(ฟ้า, คำสั่ง) →OrderPlaced(ส้ม, เกิดแล้ว)CapturePayment(ฟ้า) →PaymentCaptured(ส้ม)
command คือ “ปุ่มที่ถูกกด” ส่วน event คือ “สิ่งที่เกิดหลังกดปุ่ม” command อาจล้มเหลวได้ (กดแล้วไม่สำเร็จ) แต่ event เป็นความจริงที่เกิดไปแล้วเสมอ นี่คือเหตุผลที่เราแยกสองสีนี้ออกจากกัน
Actor — ใครเป็นคนกดปุ่ม
หัวข้อที่มีชื่อว่า “Actor — ใครเป็นคนกดปุ่ม”ติดกับ command สีฟ้ามักมี Post-it สีเหลืองแผ่นเล็กๆ แปะอยู่มุมหนึ่ง นั่นคือ ActorActorคน/บทบาทที่ออก command (เช่น ลูกค้า ร้านอาหาร ไรเดอร์) แทนด้วย Post-it สีเหลืองแผ่นเล็กแปะมุมของ command ช่วยตอบว่า “ใครเป็นคนทำ”Process — คนหรือบทบาทที่ลงมือออก command เช่น ลูกค้า (Customer), ร้านอาหาร (Restaurant), ไรเดอร์ (Rider) actor ตอบคำถามสั้นๆ ว่า “ใครเป็นคนทำ” เราจงใจให้แผ่นมันเล็ก เพราะมันเป็นแค่ป้ายกำกับ command ไม่ใช่ตัวเอกของเรื่อง
เหลืองใหญ่: Aggregate — ที่ที่ command กลายเป็น event
หัวข้อที่มีชื่อว่า “เหลืองใหญ่: Aggregate — ที่ที่ command กลายเป็น event”command ถูกส่งไปหาอะไรสักอย่างที่ “ตัดสินใจ” ว่าจะรับหรือปฏิเสธ แล้วปล่อย event ออกมา สิ่งนั้นคือ AggregateAggregateกลุ่มของ object ที่ถือกฎความสอดคล้อง (invariant) ไว้ด้วยกัน ใน EventStorming คือ “สิ่งที่รับ command แล้วปล่อย event ออกมา” (เช่น Order, Payment) แทนด้วย Post-it สีเหลืองแผ่นใหญ่Tactical Design — Post-it สีเหลืองแผ่นใหญ่ (เหลืองซีดกว่าปกติ) ในโลก Event Storming เรานิยาม aggregate แบบบ้านๆ ว่า
“ก้อนที่รับ command เข้า แล้วปล่อย event ออก”
ในตัวอย่างฟู้ดเดลิเวอรีของเรา aggregate หลักคือ Order, Payment, Delivery เวลาลูกค้ายิง PlaceOrder เข้ามา ตัว Order คือคนรับเรื่อง ตรวจว่าถูกต้องไหม (ตะกร้ามีของหรือเปล่า, ร้านเปิดอยู่ไหม) ถ้าผ่าน มันจึงปล่อย OrderPlaced ออกมา
เราเรียก aggregate ว่า “หน่วยความสอดคล้อง” (consistency boundary) เพราะมันคือคนถือกฎธุรกิจ (invariant) ไว้ในมือ เช่น กฎว่า “สั่งอาหารต้องมีอย่างน้อย 1 รายการ” เป็นหน้าที่ของ Order ที่จะไม่ยอมปล่อย OrderPlaced ถ้ากฎนี้ไม่ผ่าน ตอนนี้เข้าใจแค่ระดับนี้พอ — เดี๋ยวบทที่ 4 จะซูมเข้าไปดูข้างในมันละเอียดกว่านี้
หลายคนพอเห็นคำว่า aggregate ก็รีบนึกถึง class ใน code หรือตารางในฐานข้อมูล — ตอนติด Post-it ยัง ไม่ต้อง คิดขนาดนั้น บนบอร์ด aggregate เป็นแค่ “ชื่อของก้อนที่รับ command แล้วปล่อย event” เท่านั้น การรีบผูกกับโครงสร้าง code เร็วเกินไปจะปิดกั้นบทสนทนากับฝ่ายธุรกิจ
ไวยากรณ์หนึ่งสเต็ป: ต่อสี่สีเข้าด้วยกัน
หัวข้อที่มีชื่อว่า “ไวยากรณ์หนึ่งสเต็ป: ต่อสี่สีเข้าด้วยกัน”พอมีครบสี่สี — actor, command, aggregate, event — เราต่อมันเป็นหนึ่งสเต็ปของไวยากรณ์ได้แล้ว อ่านจากซ้ายไปขวา: ลูกค้า ออกคำสั่ง PlaceOrder ไปที่ Order ซึ่งปล่อย OrderPlaced ออกมา ไดอะแกรมข้างล่างเติมสีที่ห้าเข้าไปด้วย (สีม่วง) ซึ่งจะอธิบายต่อในหัวข้อถัดไป
flowchart LR A["ลูกค้า"] --> C["PlaceOrder"] C --> AG["Order"] AG --> E["OrderPlaced"] E --> P["Policy — whenever OrderPlaced ให้เก็บเงิน"] P --> NC["CapturePayment"] classDef actor fill:#fde047,stroke:#a16207,color:#1a1a1f; classDef cmd fill:#3b82f6,stroke:#1d4ed8,color:#f8fafc; classDef agg fill:#fef3c7,stroke:#a16207,color:#1a1a1f; classDef evt fill:#e8743b,stroke:#b45309,color:#1a1a1f; classDef pol fill:#c4b5fd,stroke:#6d28d9,color:#1a1a1f; class A actor; class C,NC cmd; class AG agg; class E evt; class P pol;
สีม่วง: Policy — กาวที่ทำให้บอร์ดขยับเอง
หัวข้อที่มีชื่อว่า “สีม่วง: Policy — กาวที่ทำให้บอร์ดขยับเอง”ถ้ามีแต่ actor คอยกดปุ่มทุกครั้ง ระบบก็คงช้าน่าดู ในความจริงหลายๆ ก้าวเกิดขึ้น “โดยอัตโนมัติ” ทันทีที่มี event บางอย่าง กฎอัตโนมัติแบบนี้คือ PolicyPolicyกฎเชิงปฏิกิริยาแบบ “whenever X → do Y” ที่คอยฟัง event หนึ่งแล้วสั่ง command ต่อโดยอัตโนมัติ (เช่น เมื่อ PaymentCaptured → แจ้งร้านให้กดรับ) แทนด้วย Post-it สีม่วงอ่อนProcess — Post-it สีม่วงอ่อน เขียนในรูปแบบเดียวเสมอ:
whenever
<event>→<command>
อ่านว่า “เมื่อไหร่ก็ตามที่ event นี้เกิด ให้สั่ง command นี้ต่อ” policy คือ “ตัวแทน” ที่ไม่ใช่คน มันนั่งฟัง event อยู่เงียบๆ พอได้ยินปุ๊บก็ยิง command ถัดไปทันที ในไดอะแกรมด้านบน สีม่วงคือ policy “whenever OrderPlaced → ให้เก็บเงิน” ซึ่งไปกดปุ่ม CapturePayment ต่อให้เอง โดยไม่มีมนุษย์มาเกี่ยว
policy สำคัญมากเพราะมันคือจุดที่บอร์ด “ขยับเอง” — event ตัวหนึ่งจุดชนวน command ตัวถัดไป ซึ่งไปเกิด event ใหม่ ซึ่งจุดชนวน policy ใหม่ ต่อกันเป็นลูกโซ่ นี่แหละคือเครื่องยนต์ที่ขับเคลื่อนทั้งระบบ
บนบอร์ดสั่งอาหารของเรามี policy สำคัญสองตัวที่ทำให้ทุกอย่างเดินหน้าเองโดยไม่ต้องรอคนกด:
- whenever
PaymentCaptured→ แจ้งร้านให้กดรับ (สั่งAcceptOrder) - whenever
OrderAcceptedByRestaurant→ หาไรเดอร์ให้ (สั่งAssignRider)
ลองอ่านออกเสียงดู มันเป็นภาษาคนล้วนๆ — “พอตัดเงินสำเร็จ ให้แจ้งร้าน” ฝ่ายธุรกิจพยักหน้าตามได้ทันที นี่คือพลังของ policy สีม่วง
สีเขียว: Read Model — สิ่งที่ผู้ใช้อ่านก่อนตัดสินใจ
หัวข้อที่มีชื่อว่า “สีเขียว: Read Model — สิ่งที่ผู้ใช้อ่านก่อนตัดสินใจ”ก่อน actor จะกล้าออก command เขามักต้อง “อ่าน” อะไรบางอย่างก่อน ลูกค้ากดสั่งอาหารได้ก็ต่อเมื่อเห็นตะกร้าและราคารวมชัดๆ ข้อมูลหรือหน้าจอที่ผู้ใช้อ่านเพื่อประกอบการตัดสินใจนี้คือ Read ModelRead Modelข้อมูล/หน้าจอที่ผู้ใช้ “อ่าน” เพื่อประกอบการตัดสินใจก่อนออก command (เช่น ตะกร้า หน้าติดตามออเดอร์) แทนด้วย Post-it สีเขียว เชื่อมโยงกับฝั่ง Query ของ CQRSTactical Design — Post-it สีเขียว
ในตัวอย่างของเรา read model ที่เด่นที่สุดคือ ตะกร้า (Cart) ที่ลูกค้าเห็นก่อนกด PlaceOrder และ หน้าติดตามออเดอร์ (Order tracking) ที่ลูกค้าเปิดดูว่าไรเดอร์ถึงไหนแล้ว สังเกตว่า read model มักวางไว้ “ก่อน” command บนเส้นทางการอ่าน แล้วชี้เข้าหา actor เพื่อบอกว่า “คนอ่านอันนี้แล้วจึงตัดสินใจกด” มันคือฝั่ง “อ่าน” ของระบบ ต่างกับ event/command ที่เป็นฝั่ง “เขียน”
สีชมพูและสีแดง: ขอบระบบและจุดที่ยังตอบไม่ได้
หัวข้อที่มีชื่อว่า “สีชมพูและสีแดง: ขอบระบบและจุดที่ยังตอบไม่ได้”สองสีสุดท้ายเป็นเหมือน “หมายเหตุ” ที่ทำให้บอร์ดตรงกับความจริงมากขึ้น
สีชมพู — External SystemExternal Systemระบบภายนอกที่เราไม่ได้ควบคุมแต่ต้องคุยด้วย (เช่น Payment Gateway, บริการแจ้งเตือน, แผนที่) แทนด้วย Post-it สีชมพู ช่วยชี้จุดเชื่อมต่อและความเสี่ยงArchitecture: ระบบภายนอกที่เราต้องคุยด้วยแต่ไม่ได้เป็นเจ้าของ เช่น Payment Gateway (ตัวตัดบัตร), Push Notification (ตัวส่งแจ้งเตือน), Map/Routing (แผนที่นำทางไรเดอร์) เราวาดมันด้วยสีชมพูเพื่อเตือนตัวเองว่า “ตรงนี้เราคุมไม่ได้” — มันช้าได้ ล่มได้ เปลี่ยน API ได้ ทุกจุดที่ต่อกับสีชมพูคือจุดเสี่ยงที่ต้องออกแบบเผื่อ
สีแดง — HotspotHotspotจุดที่เป็นปัญหา ข้อขัดแย้ง หรือคำถามที่ยังตอบไม่ได้ระหว่างเวิร์กช็อป แทนด้วย Post-it สีแดงวางเฉียง ๆ เก็บไว้กลับมาคุยทีหลังโดยไม่ขัดจังหวะการไหลของกลุ่มProcess: จุดที่เป็นปัญหา ข้อขัดแย้ง หรือคำถามที่ยังตอบไม่ได้ระหว่างวง เราแปะ Post-it สีแดงเอียงๆ ไว้ตรงนั้นแล้ว “เดินหน้าต่อ” ทันที ไม่หยุดเถียงกลางวง เช่น “ร้านไม่กดรับใน 5 นาที ทำยังไง?” หรือ “ยกเลิกหลังจ่ายเงินแล้ว คืนเงินยังไง?” hotspot คือของขวัญ — มันคือรายการคำถามที่มีค่าที่สุดที่จะเอาไปคุยต่อหลังเวิร์กช็อป
มือใหม่มักสับสนสีฟ้ากับสีส้ม วิธีเช็กคือถามว่า “มันสำเร็จแน่หรือยัง?” — PlaceOrder (สั่ง) ยัง ไม่การันตี ว่าสำเร็จ จึงเป็น command สีฟ้า ส่วน OrderPlaced (สั่งแล้ว) เกิดขึ้นจริงแล้ว จึงเป็น event สีส้ม อีกกับดักคือ “ทำทุกอย่างเป็น aggregate” — ถ้าก้อนไหนไม่ได้รับ command และไม่ปล่อย event มันอาจเป็นแค่ read model หรือ external system
legend รวมทุกสีในภาพเดียว
หัวข้อที่มีชื่อว่า “legend รวมทุกสีในภาพเดียว”เก็บทั้งแปดสีไว้ในที่เดียวเป็นแผนที่ไว้เปิดดู เวลายืนหน้าบอร์ดจริงคุณจะเหลือบมองมุมนี้บ่อยกว่าที่คิด
flowchart TD ACT["Actor เหลืองเล็ก — คนที่ออก command"] CMD["Command ฟ้า — ความตั้งใจที่สั่งให้ event เกิด"] AGG["Aggregate เหลืองใหญ่ — รับ command แล้วปล่อย event"] EVT["Domain Event ส้ม — สิ่งที่เกิดขึ้นแล้ว กริยาอดีต"] POL["Policy ม่วง — กฎ whenever event ให้สั่ง command"] RM["Read Model เขียว — ข้อมูลหรือหน้าจอที่ผู้ใช้อ่าน"] EXT["External System ชมพู — ระบบภายนอกที่เราไม่ควบคุม"] HOT["Hotspot แดง — ปัญหาหรือคำถามที่เก็บไว้คุยทีหลัง"] classDef actor fill:#fde047,stroke:#a16207,color:#1a1a1f; classDef cmd fill:#3b82f6,stroke:#1d4ed8,color:#f8fafc; classDef agg fill:#fef3c7,stroke:#a16207,color:#1a1a1f; classDef evt fill:#e8743b,stroke:#b45309,color:#1a1a1f; classDef pol fill:#c4b5fd,stroke:#6d28d9,color:#1a1a1f; classDef rm fill:#86efac,stroke:#15803d,color:#1a1a1f; classDef ext fill:#f9a8d4,stroke:#be185d,color:#1a1a1f; classDef hot fill:#f87171,stroke:#b91c1c,color:#1a1a1f; class ACT actor; class CMD cmd; class AGG agg; class EVT evt; class POL pol; class RM rm; class EXT ext; class HOT hot;
ไวยากรณ์หลัก: หนึ่งประโยคที่ต้องจำ
หัวข้อที่มีชื่อว่า “ไวยากรณ์หลัก: หนึ่งประโยคที่ต้องจำ”ถ้าเรียงสีตามลำดับที่มันเชื่อมกันจริง จะได้ “ประโยคหลัก” ของ Event Storming ที่วนซ้ำต่อเนื่องตลอดทั้งบอร์ด:
Actor → Command → Aggregate → Event → Policy → Command → …
อ่านเป็นไทย: คน ออก คำสั่ง ไปที่ ก้อนธุรกิจ ซึ่งปล่อย เหตุการณ์ ออกมา แล้ว กฎอัตโนมัติ ก็ยิง คำสั่งถัดไป วนต่อเนื่อง โดยมี read model สีเขียวคอยป้อนข้อมูลให้ actor อ่านก่อนตัดสินใจ และมี external system สีชมพูกับ hotspot สีแดงเป็นหมายเหตุประกอบขอบๆ
ไดอะแกรมสุดท้ายรวมทุกอย่างเข้าด้วยกัน — read model นำหน้า actor, external system ต่อกับ command, และ hotspot แปะเตือนตรงจุดที่ยังตอบไม่ได้:
flowchart LR RM["Read Model — ตะกร้า (Cart)"] --> A["ลูกค้า"] A --> C["PlaceOrder"] C --> AG["Order"] AG --> E["OrderPlaced"] PG["External — Payment Gateway"] -.-> C2["CapturePayment"] C2 --> AG2["Payment"] AG2 --> E2["PaymentCaptured"] H["Hotspot — ยกเลิกหลังจ่ายเงินแล้ว คืนเงินยังไง?"] -.-> AG2 classDef actor fill:#fde047,stroke:#a16207,color:#1a1a1f; classDef cmd fill:#3b82f6,stroke:#1d4ed8,color:#f8fafc; classDef agg fill:#fef3c7,stroke:#a16207,color:#1a1a1f; classDef evt fill:#e8743b,stroke:#b45309,color:#1a1a1f; classDef rm fill:#86efac,stroke:#15803d,color:#1a1a1f; classDef ext fill:#f9a8d4,stroke:#be185d,color:#1a1a1f; classDef hot fill:#f87171,stroke:#b91c1c,color:#1a1a1f; class A actor; class C,C2 cmd; class AG,AG2 agg; class E,E2 evt; class RM rm; class PG ext; class H hot;
พอจับไวยากรณ์ประโยคนี้ได้ คุณจะเริ่มเห็นมันทุกที่ — ไม่ว่าจะบอร์ดฟู้ดเดลิเวอรี ระบบจองตั๋ว หรือระบบธนาคาร มันคือประโยคเดียวกันหมด เปลี่ยนแค่คำนาม บทที่ 4 จะเอาไวยากรณ์นี้ไปซูมเข้า bounded context เดียวแล้วขยายรายละเอียดจนเกือบพร้อมเขียน code
เจาะลึกแนวคิดต่อได้ที่คลังอ้างอิง DevIQ:
- Domain Events — Post-it สีส้มบนบอร์ดกลายเป็น pattern domain event ใน code จริงอย่างไร ต่อยอดจากหัวข้อสีส้มโดยตรง
- Command — มอง command สีฟ้าในเชิง pattern การออกแบบ ช่วยให้เห็นว่า “ความตั้งใจที่ห่อเป็นวัตถุ” มีประโยชน์อย่างไรตอนลง code
- Aggregate — อธิบาย aggregate ในฐานะหน่วยความสอดคล้องของ DDD ลงลึกกว่านิยามบ้านๆ “รับ command แล้วปล่อย event” ที่ใช้
เช็กความเข้าใจ — บทที่ 3
ข้อ 1 / 3Post-it สีม่วง (policy) แทนกฎแบบใด?