Event Sourcing แบบสั้นๆ เนื้อๆ

Chatthana Janethanakarn
4 min readNov 19, 2022

--

เชื่อว่าพวกเราในแวดวง software engineering หรือ software architecture ในระยะหลังๆน่าจะมีโอกาสได้ยินคำว่า Event Sourcing, CQRS, Event-Driven Architecture อะไรต่างๆนาๆมาอย่างแน่นอน ซึ่งเท่าที่สังเกตหลายๆบทความเวลาบริษัทต่างๆมาแนะนำ tech stack ก็อาจจะพ่วงคำเหล่านี้มาด้วย แต่อาจจะไม่ได้มีโอกาสอธิบายถึงรายละเอียดของสิ่งเหล่านี้มากนัก เพราะอาจจะ out of context ไปหน่อย

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

ว่าแล้วก็เริ่มกันเลยละกันครับ เริ่มจากหัวข้อจั่วหัวก่อนเลยละกันครับ มันคืออะไรกันนะ

Event Sourcing คืออะไร?

โดยปกติ เวลาเราทำการอัพเดทข้อมูล สิ่งที่เราทำคือการเซฟทับข้อมูลเดิมไปแล้วเก็บแค่สถานะล่าสุดของข้อมูลนั้นๆ ยกตัวอย่างเช่น เวลาเราเปลี่ยนชื่อของ user ในระบบ สิ่งที่เกิดขึ้นก็คือเราอัพเดท field ใน database ให้เป็นข้อมูลล่าสุด

ในทางกลับกัน Event Sourcing คือ pattern ที่เราจะเก็บสิ่งที่เกิดขึ้นกับข้อมูล (entity) เป็น event แทน ซึ่งไอ้เจ้า event นี่ก็คือสิ่งที่เกิดขึ้นกับข้อมูลนั้นๆ (state transition) ดังนั้นทุกครั้งที่เราทำการสร้าง เปลี่ยนแปลง หรือลบข้อมูลต่างๆ แทนที่เราจะอัพเดทข้อมูลใหม่ทับข้อมูลเก่า เราจะเพิ่ม event ที่เกิดขึ้นลงไปแทน ซึ่งสิ่งที่เราจะใช้เก็บ event เหล่านี้ เราเรียกว่า event store ที่อาจจะเป็น data storage แบบไหนก็ได้ เช่น database หรือ file

สิ่งที่เป็น key หลักเลยก็คือ event เหล่านี้คือ source of truth ของระบบเพียงหนึ่งเดียว ไม่สามารถถูกเปลี่ยนแปลงได้ (Immutable) หากต้องการแก้ไขอะไรก็ตาม สิ่งเดียวที่เราทำได้คือการเพิ่ม event ใหม่เข้าไป

ฟังดูเข้าใจยาก?

พูดถึง concept ข้างบนอาจจะฟังดูเข้าใจยาก เพื่อให้เห็นภาพ เอาเป็นว่าลองยกตัวอย่างสักนิดนึง

สมมุติว่าผมเก็บข้อมูล order ในรูปแบบประมาณนี้

อธิบายคือ order มี order items มี shipping address และมีข้อมูลเกี่ยวการ payment ซึ่งเป็น relationship ระหว่างกัน (ไม่ลงรายละเอียดนะครับ เดี๋ยวยาว) ซึ่งข้อมูลตรงนี้จะเก็บอยู่ใน technology ไหนก็ได้ ไม่ได้เป็นประเด็น จะเป็น MySQL, Document Database อะไรก็ว่าไปครับ

ซึ่งถ้าเราใช้รูปแบบการเก็บข้อมูลแบบ Event Sourcing เราสามารถมองข้อมูลชุดนี้เป็นแบบนี้

จากภาพด้านบนจะสังเกตว่า event ถูกเรียงตามลำดับเวลา โดยเราจะเรียกมันว่า event stream ซึ่งหน้าที่ในการสร้าง event เหล่านี้ขึ้นมาก็คือ domain object เช่น entity หรือ Aggregate Root (ถ้าใช้ Domain-Driven Design) ก่อนจะถูกเอาไปเก็บใน event store ผ่าน repository

อย่างที่บอกไปว่า event เป็น immutable data ไม่สามารถเปลี่ยนแปลงได้ ดังนั้นจากตรงนี้ถ้าผมอยากจะลบ order item สักอัน สิ่งที่ผมต้องทำก็คือเพิ่ม event ลงไปใน event stream แบบนี้ครับ

ข้อสังเกตคือ เมื่อเราเก็บข้อมูลในรูปแบบของ Event Sourcing แล้ว เราสามารถ replay ข้อมูลเหล่านี้เพื่อประโยชน์ได้ในหลายๆสถานการณ์ ยกตัวอย่างเช่น ผมสามารถสร้าง code มาชุดหนึ่งเป็น function ง่ายๆที่รับ list ของ event เหล่านี้เข้าไปแล้ว transform มันออกมาให้เป็น data structure แบบภาพด้านบน หรืออาจจะเอาไปทำเป็น data structure รูปแบบอื่นๆก็ได้ เช่น order report หรือข้อมูลสำหรับ analytics เป็นต้น

ซึ่งขั้นตอนนี้เราเรียกว่า projection ครับ ซึ่งการทำ projection นี้คือส่วนหนึ่งของการสร้าง read model ที่เอาไว้ใช้สำหรับ query ใน pattern แบบ CQRS ที่แยก application เป็นส่วน command (write) กับ query (read) โดย Event Sourcing จะถูก implement ที่ฝั่ง command ไว้มีเวลาเดี๋ยวจะมาเขียนรายละเอียดเกี่ยวกับ CQRS & Event Sourcing อีกทีครับ

เก็บข้อมูลแบบนี้ค่า storage ก็แพงน่ะสิ?

เชื่อว่าหลายๆคนน่าจะกังวลขึ้นมาว่า โห! ถ้าระบบใหญ่ๆนี่ค่า storage จะแพงมหาศาลแน่นอน

ตรงนี้ถ้าเราไปดูข้อมูล trend ค่าใช้จ่ายของ data storage เราจะพบว่ามันมีแนวโน้มว่าจะถูกลงอย่างต่อเนื่อง

ลองมองดูที่ตัวเราเองก็ได้ครับ ถ้าเราประกอบ PC โดยใช้ SSD Samsung 970 1TB ตอนนี้และเทียบกับถ้าเราต้องประกอบ PC ด้วย storage เท่ากันตอนปี 2018 หรือเมื่อสักห้าปีที่แล้ว เราจะพบว่าราคามันต่างกันครึ่งต่อครึ่งเลยนะครับ

ส่วนตัวผมมองว่าเราอาจจะไม่จำเป็นต้องกังวลมากนักในเรื่องของ cost เพราะท้ายที่สุดแล้ว data นั้นมีมูลค่าสูงกว่าค่าใช้จ่ายที่ถูกลงเรื่อยๆอย่างแน่นอน สิ่งที่เราควรกังวลอาจจะเป็นเรื่องของการ scale ตัว event store และ performance โดยรวมมากกว่าซึ่งถึงแม้ว่า immutable data นั้นจะ scale ได้ง่าย แต่ก็อาจจะต้องพิจารณาในเรื่องของ partitioning ให้ดีหากต้องการ performance ของระบบที่สูง และในกรณีที่มี event จำนวนมากๆก็อาจจะต้องมาพิจารณาเรื่องการทำ snapshot เพื่อหลีกเลี่ยงการ replay event จำนวนมากๆอีกด้วย

สรุปข้อดี ข้อเสีย ของ Event Sourcing

Event Sourcing เป็น Pattern ในการเก็บข้อมูลในโลกของ Event-Driven Architecture ซึ่งมีข้อดีตรงที่เราจะไม่สูญเสียข้อมูลใดๆที่เกิดขึ้นกับระบบของเราไปเลย อย่างที่ทราบกันดีว่า data ในปัจจุบันมีแต่จะมีมูลค่าเพิ่มขึ้น ซึ่งตรงจุดนี้ไม่ว่าเราจะเก็บข้อมูลแบบไหนก็ตามที่ไม่ใช่รูปแบบการเก็บแบบ event log แบบ Event Sourcing เราจะสูญเสียข้อมูลเวลาเราอัพเดทข้อมูลเสมอ

Event Sourcing เองมีความ adaptive สูงมากๆ เพราะเราสามารถนำ event ที่เกิดขึ้นไปใช้ทำอะไรได้เยอะมากๆตั้งแต่การทำ read model โดยที่ไม่ต้องมานั่งกังวลเรื่องการทำ migration หรือจะเป็นระบบ analytics เชิงลึก รวมถึงการเพิ่ม system ใหม่ๆเข้ามา เช่น microservice ตัวใหม่ก็ทำได้ง่ายเพราะเราสามารถ transform event stream ให้เหมาะสมกับ service ใหม่ได้เลยโดยไม่ต้องกังวลว่าจะกระทบกับระบบเดิม

นอกเหนือไปจากนั้นแล้ว ในแง่ของการ debug หรือทำ root cause analysis ก็ง่ายขึ้นมากๆ เราสามารถ time travel ไป ณ จุดที่เกิดปัญหาเพื่อดูว่าเกิดอะไรขึ้นและแก้ไขได้อย่างรวดเร็ว โดยที่ไม่ต้องนั่ง patch data ให้ปวดหัว

ในส่วนของข้อเสียก็มีอยู่บ้าง เช่นเรื่อง complexity ในการออกแบบระบบที่สูงขึ้น เพราะการจะเก็บข้อมูลแบบ Event Sourcing ให้ได้ประโยชน์สูงสุดนั้น อาจจะต้องใช้ Layered Architecture ที่ต้องอาศัยความเข้าใจในระดับหนึ่ง เพราะอย่างน้อยเราจะต้องแยก business logic ออกจาก service อยู่ใน domain layer ให้หมดและต่อเนื่องด้วยการจะออกแบบ event ให้ดีนั้นอาจจะต้องคำนึงถึงหลักการของ Domain-Driven Design ที่ออกแบบ event ตาม business scenario ให้อยู่ในรูปแบบของ Domain Event อีกด้วย ซึ่งตรงนี้ต้องการ collaboration กับ business ที่สูงระดับหนึ่งเลยทีเดียว

นอกเหนือจากนั้นแล้ว ถึงแม้ว่าตัว Event Sourcing เองจะไม่ได้เกี่ยวอะไรกับ messaging system เช่นระบบ streaming หรือ pub/sub โดยตรง แต่ด้วยคาวมที่มันมักจะถูกใช้คู่กันกับ CQRS ที่ในทางปฏิบัติมันความจำเป็นต้องนำ concept เหล่านี้มาใช้ทำให้มีอีกหลายอย่างที่ต้องคำนึงถึง เช่น eventual consistency, idempotency, หรือ concurrency control ซึ่งทั้งหมดทั้งมวลนี้ต้องการความเข้าใจใน software architecture, concepts, และอีกหลายเรื่องๆซึ่งมี overhead ตรงนั้นพอสมควร เราจึงอาจจะต้อง trade-off ให้ดีครับว่า business use case ของเรานั้นเหมาะสมจริงๆหรือเปล่าที่จะต้องใช้ Event Sourcing

ต้องใช้ไหม?

ต้องบอกก่อนว่ามันมี business use case หลายอย่างที่เหมาะและไม่เหมาะกับ Event Sourcing นะครับ ถ้าถามว่าเมื่อไหร่ที่ต้องใช้ Event Sourcing ก็คงต้องบอกว่าเมื่อ business use case ของเราต้องการ history ทั้งหมดของสิ่งที่เกิดขึ้นในระบบ

ยกตัวอย่างเช่น ในระบบการเงินการธนาคาร ลองนึกภาพว่าถ้ายอดเงินคงเหลือของเราเป็นแค่ field ใน database จะเกิดอะไรขึ้นครับ? สมมุติว่าคุณสงสัยว่ายอดเงินมันถูกต้องหรือเปล่า คุณโทรไปถาม call center แล้วเค้าบอกว่า อ่อ คุณมีเงินอยู่เท่านี้เพราะไอ้ field balance ใน database มันบอกว่าเหลือเท่านี้! คุณคิดว่าธนาคารแห่งนี้มีความน่าเชื่อถือไหมครับ?

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

ยิ่งไปกว่านั้น ในบาง industry อาจจะมีเรื่องของ regulation ต่างๆเข้ามามีบทบาททำให้เราต้องพิจารณาใช้ Event Sourcing ด้วย เช่น การสร้างระบบบัญชี ถ้าใครเคยเรียนบัญชีจะเห็นว่านักบัญชีจะไม่ใช้ดินสอ แต่พวกเขาจะใช้ปากกากัน นั่นเพราะถ้าเราจดบันทึก transaction ไปแล้วใน journal เราจะไม่ลบมันออกเด็ดขาด ซึ่งนี่เป็นกฏหมายเลยนะครับ เวลานักบัญชีโอนเงินผิด สิ่งที่ทำคือ refund หรือการสร้าง transaction โอนเงินคืน แล้ว โอนเงินจำนวนที่ถูกต้องเข้าไปใหม่ ไม่มีการไปแก้ transaction ที่เกิดไปแล้วโดยเด็ดขาด

แต่ในขณะเดียวกันถ้า business use case ของเราไม่ได้สนใจ history ของระบบสนใจแค่สิ่งที่เป็นอยู่ในปัจจุบัน แบบนี้การใช้งานในรูปแบบ current state เช่น CRUD ก็ถือว่าเพียงพอครับ

และที่สำคัญ Event Sourcing ไม่ใช่ top level architecture เหมือนพวก microservices ดังนั้นเราควรเลือกใช้มันให้เหมาะสมในแต่ละ business scenario เพราะอย่างที่เห็นกันว่าถ้าเราไป implement ในจุดที่ไม่ควร นั่นก็คือการ over engineer นั่นเองครับ

สรุปอีกรอบ

Event Sourcing เป็น pattern ในการจัดเก็บข้อมูลที่มอง event เป็น source of truth ของระบบ โดยทุกๆ state transition ที่เกิดขึ้นกับข้อมูลใดๆจะถูกมองเป็น event stream ตามลำดับเวลาที่เกิดขึ้น และ state ของระบบเป็นผลลัพธ์มาจากการ process event เหล่านี้ (first-level derivative)

ดังนั้น current state ที่เป็น structural model เช่นข้อมูลที่อยู่บน database แบบทั่วๆไปหรือ cache หรืออะไรก็ตามแต่ไม่ใช่ source of truth ของระบบ เป็นเพียงสิ่งที่ถูกสร้างขึ้นมาจาก event ที่เกิดขึ้นทั้งนั้นครับ เพราะฉะนั้นมันอาจจะถูกลบทิ้งแล้วสร้างใหม่เมื่อไหร่ก็ได้ (transient)

จริงๆ concept ของ Event Sourcing ก็มีเท่านี้เลยครับ ไม่เกี่ยวกับ Kafka, Message Broker ใดๆทั้งสิ้นครับ ฝั่งนั้นจะเป็นเรื่อง solution ที่เอาไปใช้งานจริงมากกว่า ซึ่งแต่ละบริษัทก็มีการนำไปใช้ด้วย technology ที่แตกต่างกัน ซึ่งก็… เดี๋ยวไว้พูดถึงโพสถัดๆไปนะครับ

มีตัวอย่าง project ที่ใช้ Event Sourcing ไหม?

เรียนเชิญครับ repo นี้มีทั้ง CQRS Event Sourcing ไว้เดี๋ยวจะมาเล่ารายละเอียดให้ฟังครับ
https://github.com/yerinadler/typescript-event-sourcing-sample-app

จะมีต่อไหม?

ยังมีอีกหลาย concept ในเรื่อง Event-Driven Architecture และ System Design ที่อยากแชร์ครับ เช่น การใช้งาน CQRS คู่กับ Event Sourcing เอาไว้มีเวลาจะมาแชร์อย่างแน่นอนครับ

ยังไงวันนี้ก็ฝากไว้เท่านี้ก่อนนะครับ สวัสดีครับ ถ้าใครมีอะไรแลกเปลี่ยน หรือมีความเห็นยังไงก็ discuss กันได้นะครับ

--

--