Event Sourcing Architecture: ออกแบบระบบโดยใช้เหตุการณ์เป็นแหล่งข้อมูลหลัก


1. Event Sourcing คืออะไร?

Event Sourcing คือรูปแบบการออกแบบซอฟต์แวร์ที่ใช้เหตุการณ์ (Events) ซึ่งบันทึกการเปลี่ยนแปลงของข้อมูล เป็นแหล่งข้อมูลหลักสำหรับสร้างสถานะของระบบ แทนการจัดเก็บเฉพาะสถานะล่าสุดแล้วเขียนทับค่าเดิม

ระบบฐานข้อมูลทั่วไปมักเก็บสถานะปัจจุบัน เช่น ยอดเงินคงเหลือของบัญชี แต่ระบบ Event Sourcing จะเก็บเหตุการณ์ที่ทำให้ยอดเงินเปลี่ยนแปลง เช่น การฝากเงิน การถอนเงิน และการโอนเงิน

ตัวอย่างเช่น บัญชีธนาคารมีเหตุการณ์ดังนี้

AccountOpened       +0 บาท

MoneyDeposited   +5000 บาท

MoneyWithdrawn   -1200 บาท

MoneyDeposited   +2000 บาท

ระบบสามารถนำเหตุการณ์เหล่านี้มาประมวลผลตามลำดับ เพื่อคำนวณยอดเงินปัจจุบันได้

ยอดเงินคงเหลือ = 0 + 5,000 - 1,200 + 2,000 = 5,800 บาท

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

อย่างไรก็ตาม การจัดเก็บเหตุการณ์ไม่ได้หมายความว่าจะเก็บข้อมูลทุกอย่างได้ตลอดไป เพราะยังต้องคำนึงถึงนโยบายการเก็บรักษาข้อมูล ความเป็นส่วนตัว และข้อกำหนดทางกฎหมาย

2. Traditional CRUD vs Event Sourcing

เพื่อให้เข้าใจแนวคิดนี้ชัดเจน ควรเปรียบเทียบกับการออกแบบระบบแบบ CRUD ที่ใช้กันทั่วไป

สมมติว่าระบบต้องจัดเก็บยอดเงินในบัญชี

2.1 Traditional CRUD

ฐานข้อมูลอาจเก็บสถานะดังนี้

accounts

--------------------------------

account_id | balance

--------------------------------

ACC-001    | 5800

เมื่อมีการฝากหรือถอนเงิน ระบบจะอัปเดตยอดเงินในแถวเดิม

ข้อดีคือเข้าใจง่ายและเหมาะกับระบบธุรกิจทั่วไปจำนวนมาก แต่หากไม่มี Transaction History แยกต่างหาก การตรวจสอบว่าการเปลี่ยนแปลงเกิดขึ้นอย่างไรอาจทำได้ยาก

2.2 Event Sourcing

ระบบจะเก็บเหตุการณ์ที่เกี่ยวข้องกับบัญชี

account_events

------------------------------------------------

event_id | account_id | event_type | amount

------------------------------------------------

EVT-001  | ACC-001    | Opened     | 0

EVT-002  | ACC-001    | Deposited  | 5000

EVT-003  | ACC-001    | Withdrawn  | 1200

EVT-004  | ACC-001    | Deposited  | 2000

ระบบสามารถนำเหตุการณ์มาคำนวณสถานะปัจจุบัน และสร้างมุมมองข้อมูลอื่นได้

ประเด็น

Traditional CRUD

Event Sourcing

ข้อมูลหลัก

สถานะปัจจุบัน

ประวัติเหตุการณ์

การเปลี่ยนแปลง

อัปเดตข้อมูลตามโมเดล

เพิ่มเหตุการณ์ใหม่

ประวัติย้อนหลัง

ต้องออกแบบเก็บแยก

มีอยู่ใน Event History

การสร้างสถานะใหม่

อ่านข้อมูลปัจจุบัน

Replay เหตุการณ์

ความซับซ้อน

โดยทั่วไปต่ำกว่า

สูงกว่า

การตรวจสอบย้อนหลัง

ขึ้นอยู่กับระบบ Audit

ติดตามเหตุการณ์ได้โดยตรง

Event Sourcing ไม่ได้ดีกว่า CRUD ในทุกสถานการณ์ แต่เหมาะกับระบบที่ประวัติการเปลี่ยนแปลงมีความสำคัญต่อการทำงานของธุรกิจ

3. องค์ประกอบของ Event Sourcing Architecture

ระบบ Event Sourcing โดยทั่วไปประกอบด้วยองค์ประกอบต่อไปนี้

3.1 Command

Command คือคำขอให้ระบบดำเนินการบางอย่าง เช่น

  • OpenAccount
  • DepositMoney
  • WithdrawMoney
  • CancelOrder

Command แสดงเจตนาที่ต้องการให้เกิดขึ้น ไม่ได้หมายความว่าการดำเนินการนั้นสำเร็จแล้ว

3.2 Command Handler

Command Handler ทำหน้าที่รับ Command ตรวจสอบเงื่อนไข และตัดสินใจว่าระบบสามารถดำเนินการต่อได้หรือไม่

ตัวอย่างเช่น เมื่อได้รับ WithdrawMoney ระบบต้องตรวจสอบว่ายอดเงินเพียงพอหรือไม่ ก่อนสร้างเหตุการณ์ MoneyWithdrawn

3.3 Event Store

Event Store คือพื้นที่จัดเก็บเหตุการณ์ที่ระบบใช้เป็นแหล่งข้อมูลหลัก

ตัวอย่าง Event:

{

  "event_id": "evt-001",

  "aggregate_id": "ACC-001",

  "event_type": "MoneyDeposited",

  "amount": 5000,

  "currency": "THB",

  "version": 1

}

ระบบจริงอาจเก็บ Metadata เพิ่มเติม เช่น เวลาเกิดเหตุการณ์ ผู้สร้างเหตุการณ์ และ Schema Version

3.4 Aggregate

Aggregate เป็นแนวคิดจาก Domain-Driven Design ที่ใช้กำหนดขอบเขตของสถานะและกฎทางธุรกิจที่ต้องรักษาความถูกต้องร่วมกัน

ตัวอย่างเช่น BankAccount อาจเป็น Aggregate ที่ควบคุมยอดเงินและตรวจสอบเงื่อนไขการถอนเงิน

3.5 Projection

Projection คือการนำเหตุการณ์มาประมวลผลเพื่อสร้างมุมมองข้อมูลที่เหมาะกับการอ่านหรือการใช้งานเฉพาะด้าน

ตัวอย่างเช่น นำเหตุการณ์ฝากและถอนเงินมาสร้างตารางยอดเงินคงเหลือสำหรับแสดงผลในแอปพลิเคชัน

3.6 Event Handler

Event Handler ทำหน้าที่ตอบสนองต่อเหตุการณ์ เช่น ส่งการแจ้งเตือน อัปเดตมุมมองข้อมูล หรือเริ่ม Workflow ถัดไป

Event Handler อาจทำงานอยู่ภายในบริการเดียวกันหรือเป็นส่วนหนึ่งของบริการอื่นในระบบกระจายศูนย์

4. ลำดับการทำงานของ Event Sourcing

ตัวอย่างระบบบัญชีธนาคาร

User

 |

 v

Command

 |

 v

Command Handler

 |

 v

Validate Business Rules

 |

 v

Generate Domain Event

 |

 v

Event Store

 |

 v

Update Projection

 |

 v

Application Query

 |

 v

Display Current Balance

อธิบายทีละขั้นตอน

  1. ผู้ใช้ส่งคำสั่งฝากเงิน
  2. Command Handler ตรวจสอบคำสั่งและเงื่อนไขทางธุรกิจ
  3. ระบบสร้างเหตุการณ์ MoneyDeposited เมื่อคำสั่งผ่านการตรวจสอบ
  4. บันทึกเหตุการณ์ลง Event Store
  5. Projection Processor นำเหตุการณ์ไปปรับปรุงมุมมองยอดเงิน
  6. แอปพลิเคชันอ่านยอดเงินจากมุมมองข้อมูลเพื่อแสดงผล

ในระบบจริงต้องจัดการกรณีที่การบันทึก Event Store ล้มเหลว รวมถึงกรณีที่บันทึกสำเร็จแล้วแต่ Projection ยังไม่อัปเดต

นอกจากนี้ การตรวจสอบและบันทึกเหตุการณ์ต้องป้องกันไม่ให้คำสั่งที่แข่งขันกันทำให้สถานะผิดพลาด เช่น การถอนเงินสองรายการพร้อมกันจนเกินยอดเงินที่มีอยู่

5. Event Replay: สร้างสถานะจากเหตุการณ์ย้อนหลัง

หนึ่งในความสามารถสำคัญของ Event Sourcing คือการนำเหตุการณ์เก่ากลับมาประมวลผล เพื่อสร้างสถานะของระบบขึ้นใหม่

ตัวอย่าง:

Event 1: AccountOpened

Event 2: MoneyDeposited(5000)

Event 3: MoneyWithdrawn(1200)

Event 4: MoneyDeposited(2000)

ระบบนำเหตุการณ์ตามลำดับมาประมวลผล

Initial Balance = 0


After Event 1 = 0

After Event 2 = 5000

After Event 3 = 3800

After Event 4 = 5800

Event Replay มีประโยชน์เมื่อ

  • ต้องสร้าง Projection ใหม่
  • ต้องตรวจสอบความผิดปกติของข้อมูล
  • ต้องสร้างรายงานจากประวัติธุรกรรม
  • ต้องทดสอบกฎทางธุรกิจเวอร์ชันใหม่
  • ต้องกู้คืนมุมมองข้อมูลหลังเกิดความเสียหาย

อย่างไรก็ตาม การ Replay ไม่ได้หมายความว่าควรเรียกใช้ผลข้างเคียงภายนอกทั้งหมดซ้ำ เช่น การเรียกเก็บเงินหรือส่งอีเมลจริง ระบบควรแยกการคำนวณสถานะออกจากการดำเนินการภายนอกที่ไม่ควรเกิดซ้ำ

6. Snapshot: ลดต้นทุนการสร้างสถานะใหม่

เมื่อระบบมีเหตุการณ์จำนวนมาก การอ่านเหตุการณ์ตั้งแต่ต้นทุกครั้งอาจใช้เวลานาน

Snapshot จึงถูกนำมาใช้เพื่อบันทึกสถานะ ณ จุดหนึ่ง

ตัวอย่าง:

Events 1 - 1000

       |

       v

Create Snapshot

Balance = 250000


Events 1001 - 1050

       |

       v

Replay from Snapshot

       |

       v

Current Balance

แทนที่จะอ่านเหตุการณ์ทั้งหมด 1,050 รายการ ระบบสามารถโหลด Snapshot แล้วประมวลผลเฉพาะเหตุการณ์หลัง Snapshot

Snapshot เป็นข้อมูลที่สร้างขึ้นจาก Event History ไม่ใช่สิ่งทดแทน Event Store ในสถาปัตยกรรม Event Sourcing ทั่วไป

หาก Snapshot สูญหาย ระบบควรมีวิธีสร้างใหม่จากเหตุการณ์ที่เก็บไว้

7. CQRS: ทำงานร่วมกับ Event Sourcing อย่างไร?

CQRS ย่อมาจาก Command Query Responsibility Segregation เป็นแนวทางที่แยกโมเดลสำหรับการเปลี่ยนแปลงข้อมูลออกจากโมเดลสำหรับการอ่านข้อมูล

CQRS สามารถใช้ร่วมกับ Event Sourcing ได้ แต่ทั้งสองแนวคิดไม่ใช่สิ่งเดียวกัน และไม่จำเป็นต้องนำมาใช้พร้อมกันเสมอไป

ตัวอย่างสถาปัตยกรรม:

                Commands

                   |

                   v

             Command Model

                   |

                   v

               Event Store

                   |

                   v

             Event Processor

                   |

                   v

              Read Model

                   |

                   v

                 Queries

ข้อดีคือสามารถออกแบบ Read Model ให้เหมาะกับการค้นหาและรายงานได้ โดยไม่ต้องใช้โครงสร้างเดียวกับโมเดลธุรกิจที่รับผิดชอบการเปลี่ยนแปลงข้อมูล

ข้อแลกเปลี่ยนคือ Read Model อาจอัปเดตช้ากว่า Event Store เล็กน้อย ทำให้ระบบมี Eventual Consistency

8. Event Sourcing กับ Apache Kafka

Apache Kafka สามารถใช้เป็นส่วนหนึ่งของสถาปัตยกรรม Event Sourcing ได้ แต่ต้องแยกบทบาทให้ชัดเจน

Kafka เป็น Distributed Event Streaming Platform ส่วน Event Store เป็นองค์ประกอบเชิงสถาปัตยกรรมที่ทำหน้าที่จัดเก็บเหตุการณ์หลักของระบบ

Kafka สามารถจัดเก็บ Event Streams และรองรับการอ่านย้อนหลังตาม Retention Policy ได้ แต่การใช้ Kafka เพียงอย่างเดียวไม่ได้รับประกันว่าจะมีความสามารถด้าน Event Store ครบถ้วน เช่น การโหลดเหตุการณ์ตาม Aggregate ID การตรวจสอบเวอร์ชันของ Aggregate และการป้องกันการเขียนเหตุการณ์ที่ขัดแย้งกัน

ตัวอย่างการใช้งานร่วมกัน:

Application

     |

     v

Event Store

     |

     v

Event Publisher

     |

     v

Apache Kafka

     |

     +------> Analytics

     |

     +------> Notification

     |

     +------> Search Index

     |

     +------> Data Platform

หากระบบใช้ฐานข้อมูลเป็น Event Store และเผยแพร่เหตุการณ์ไปยัง Kafka ควรพิจารณา Transactional Outbox Pattern เพื่อป้องกันความไม่สอดคล้องระหว่างการบันทึกเหตุการณ์ในฐานข้อมูลกับการเผยแพร่ Event

9. ข้อดีของ Event Sourcing

9.1 ตรวจสอบประวัติการเปลี่ยนแปลงได้

ระบบมีประวัติเหตุการณ์ที่ช่วยอธิบายว่าสถานะปัจจุบันเกิดขึ้นได้อย่างไร

9.2 สร้างมุมมองข้อมูลใหม่ได้

สามารถ Replay เหตุการณ์เพื่อสร้าง Projection หรือรายงานรูปแบบใหม่ได้

9.3 รองรับการวิเคราะห์เชิงลึก

ประวัติเหตุการณ์ช่วยให้วิเคราะห์รูปแบบการใช้งานและลำดับการทำธุรกรรมได้ละเอียดขึ้น

9.4 แยกโมเดลการเขียนและการอ่านได้

เมื่อใช้ร่วมกับ CQRS ระบบสามารถปรับแต่งการประมวลผลคำสั่งและการค้นหาให้เหมาะกับงานแต่ละประเภทได้

9.5 ช่วยในการตรวจสอบข้อผิดพลาด

ทีมพัฒนาสามารถใช้ประวัติเหตุการณ์เพื่อทำความเข้าใจสถานะและลำดับการเปลี่ยนแปลงของระบบ

ทั้งนี้ หากต้องการ Audit Trail ที่มีคุณสมบัติป้องกันการแก้ไขข้อมูลอย่างเข้มงวด ต้องออกแบบสิทธิ์การเข้าถึง การเก็บรักษา และกลไกตรวจสอบเพิ่มเติม เพราะ Event Sourcing เพียงอย่างเดียวไม่ได้รับประกันว่าไม่มีผู้ใดแก้ไขหรือลบข้อมูลได้

10. ข้อจำกัดและความท้าทาย

Event Sourcing เพิ่มความสามารถในการติดตามประวัติ แต่ก็เพิ่มความซับซ้อนของระบบด้วย

Event Versioning: เมื่อโครงสร้าง Event เปลี่ยนแปลง ต้องมีแนวทางรองรับข้อมูลเก่า

Concurrency: ต้องป้องกันการบันทึกเหตุการณ์ที่ขัดแย้งกัน โดยเฉพาะเมื่อหลายคำสั่งแก้ไข Aggregate เดียวกัน

Eventual Consistency: Projection อาจไม่สะท้อนสถานะล่าสุดทันที

Replay Cost: การสร้างสถานะใหม่จากเหตุการณ์จำนวนมากอาจใช้ทรัพยากรสูง

Side Effects: การประมวลผลย้อนหลังต้องไม่ทำให้ธุรกรรมภายนอกเกิดซ้ำโดยไม่ตั้งใจ

Data Privacy: ข้อมูลส่วนบุคคลที่ถูกเก็บไว้ใน Event อาจต้องมีนโยบายลบ ปกปิด หรือจำกัดการเข้าถึงตามข้อกำหนดที่เกี่ยวข้อง

ด้วยเหตุนี้ Event Sourcing จึงเหมาะกับระบบที่ประโยชน์จากประวัติเหตุการณ์มีมูลค่ามากกว่าต้นทุนและความซับซ้อนที่เพิ่มขึ้น

11. ตัวอย่างการนำไปใช้ในระบบธุรกิจ

ระบบ

เหตุการณ์ตัวอย่าง

ประโยชน์

E-Commerce

OrderCreated, OrderCancelled

ตรวจสอบวงจรชีวิตของคำสั่งซื้อ

Banking

MoneyDeposited, MoneyWithdrawn

ติดตามประวัติธุรกรรม

Inventory

StockReceived, StockReserved

ตรวจสอบการเปลี่ยนแปลงสต็อก

Booking

ReservationCreated, ReservationCancelled

ตรวจสอบสถานะการจอง

Logistics

ShipmentCreated, ShipmentDelivered

ติดตามการจัดส่ง

AI Agent

TaskStarted, ToolCalled, TaskCompleted

ติดตาม Workflow ของ Agent

สำหรับ AI Agent ควรแยก Domain Event ที่ใช้ขับเคลื่อนสถานะของระบบออกจาก Observability Log ที่บันทึกเพื่อการตรวจสอบและวิเคราะห์ เพราะข้อมูลสองประเภทนี้มีวัตถุประสงค์และข้อกำหนดในการเก็บรักษาที่แตกต่างกัน

12. แนวทางการพัฒนา Event Sourcing ให้พร้อมใช้งานจริง

ก่อนนำไปใช้ใน Production ควรพิจารณาประเด็นต่อไปนี้

  1. กำหนด Aggregate และขอบเขตความถูกต้องของธุรกิจ
  2. ออกแบบ Event Schema ให้สื่อความหมายและรองรับการเปลี่ยนแปลง
  3. ใช้ Optimistic Concurrency Control หรือกลไกที่เหมาะสม
  4. ออกแบบ Projection ให้สามารถสร้างใหม่ได้
  5. กำหนดนโยบาย Snapshot ตามปริมาณข้อมูล
  6. ป้องกันการประมวลผลซ้ำและการเกิด Side Effects
  7. วางแผนการสำรองข้อมูลและกู้คืนระบบ
  8. กำหนดนโยบายด้าน Security และ Data Privacy
  9. ทดสอบการ Replay และการเปลี่ยน Schema
  10. ติดตามความล่าช้าของ Event Processing และ Projection

ไม่จำเป็นต้องใช้ Event Sourcing กับทุกโมดูลของระบบ อาจใช้เฉพาะส่วนที่ต้องการประวัติธุรกรรมที่ละเอียด เช่น การชำระเงิน การจัดการคำสั่งซื้อ หรือการควบคุมสถานะ Workflow

13. บทสรุป

Event Sourcing เปลี่ยนแนวคิดการจัดเก็บข้อมูลจากการมองเฉพาะสถานะปัจจุบัน ไปสู่การมองเหตุการณ์ที่ทำให้สถานะนั้นเกิดขึ้น

เมื่อทำงานร่วมกับ CQRS, Projection, Snapshot และ Distributed Event Streaming ระบบจะสามารถติดตามประวัติ สร้างมุมมองข้อมูลใหม่ และเชื่อมต่อกับบริการอื่นได้อย่างยืดหยุ่น

อย่างไรก็ตาม การออกแบบ Event Sourcing ต้องให้ความสำคัญกับ Concurrency, Event Versioning, Replay, Consistency และ Data Privacy ตั้งแต่ต้น

หลักการสำคัญ: ใช้ Event Sourcing เมื่อประวัติการเปลี่ยนแปลงเป็นส่วนสำคัญของโมเดลธุรกิจ ไม่ใช่เพียงเพราะต้องการใช้เทคโนโลยีใหม่

ความคิดเห็น

โพสต์ยอดนิยมจากบล็อกนี้

อะไรคือ NPU (Neural Processing Unit) มีความสำคัญอย่างไร แนวคิดมาจากไหน

TomCat สำหรับติดตั้ง แก้ไข คอนฟิก ใช้งาน JSP

Micro SaaS "ขนาดพอดีคำ" สร้างธุรกิจเริ่มต้นอย่างง่ายด่าย จากไอเดียสู่สร้างรายได้ โดยใช้ AI Agent ตอบสนองความต้องการซอฟต์แวร์ที่เล็ก ง่าย และทรงพลัง