🎞️ Videos → Bun, TypeScript and Postgres Building Reliable Event-Driven Systems Without Kafka
Description
พบกับคุณเอฟ Software Engineer จากเต่าบิน ที่จะมาแชร์เทคนิคการสร้าง event-driven application ด้วย Bun, TypeScript และ Postgres โดยไม่ต้องอาศัย message broker อย่าง Kafka หรือ RabbitMQ ในการจัดการระบบ คุณเอฟอธิบายถึงข้อดีและข้อเสียของการใช้ตารางใน Postgres เพื่อเก็บ event แทนการใช้ infrastructure ภายนอก ซึ่งช่วยให้เกิด transactional consistency ได้อย่างตรงไปตรงมา พร้อมทั้งแนะนำการใช้แพ็กเกจ pg-boss ร่วมกับ Elysia และ Kysely เพื่อจัดการคิวและป้องกันปัญหา race condition สำหรับ worker ที่ทำงานพร้อมกันหลายๆ ตัว นอกจากนี้ยังมีการสาธิตการขยายระบบหรือ scale ตัวแอปพลิเคชันอย่างง่าย
Chapters
- แนะนำหัวข้อ: ใช้ Postgres สร้าง Event-driven app แทน Kafka หรือ RabbitMQ 0:00
- ปัญหาของ Message Broker ทั่วไปคือการขาด Transactional Consistency 2:17
- เก็บ Event ลงใน Database Transaction เดียวกันเพื่อการันตี Consistency 3:28
- แก้ปัญหาคิวซ้ำซ้อน (Race Condition) ด้วย SKIP LOCKED และ NOTIFY 4:48
- ใช้ไลบรารี pg-boss จัดการ Queue ทรงพลังโดยไม่ต้องมัดคิวรีเอง 6:50
- วิธีการรันคำสั่ง Insert และ Publish Event ให้อยู่ใน Transaction เดียวกัน 8:06
- ใช้งาน Kysely ช่วยจัดการ Transaction และป้องกัน Error ด้วย Rollback อัตโนมัติ 9:48
- การจัดการ Dead Letter: ดึง Job ที่พังกลับมาทำใหม่ได้อย่างง่ายดาย 11:33
- เพิ่ม Replica: เพิ่มจำนวน Worker ได้ทันทีโดยไม่ต้องแยก App 14:21
- เดโม Scale ระบบ: จ่ายงาน 100 Request พร้อมกันผ่าน 6 Instances 16:59
- ข้อดี: ได้ Consistency แบบฟรีๆ ลดภาระการดูแล Infrastructure สำหรับโปรเจกต์ขนาดเล็ก 20:35
- ข้อจำกัด: รับ Throughput เยอะหลักพันไม่ได้ และมีข้อจำกัดในการทำ Event Sourcing 22:24
- Q&A: หากรับ Request ปริมาณมากเกินไประบบจะค้าง ควรจัดการ Connection Pool ให้ดี 24:33
- Q&A: วางโครงสร้างผ่าน Clean Architecture เผื่อย้ายระบบไปยัง Message Broker จริงในอนาคต 27:00
Transcript
คำบรรยายต่อไปนี้อาจไม่ถูกต้องทั้งหมด หากคุณพบข้อผิดพลาดใดๆ คุณสามารถคลิกเมาส์ขวาบนข้อความเพื่อรายงานได้ทันที หรือ แก้ไขบน GitHub
แนะนำหัวข้อ: ใช้ Postgres สร้าง Event-driven app แทน Kafka หรือ RabbitMQ0:00
สวัสดีครับ วันนี้จะมาพูดเรื่อง Bun, TypeScript และ Postgres นะครับ เราจะสร้าง event-driven application โดยไม่ต้องใช้พวก Kafka, RabbitMQ หรือ Mosquitto 3 พวกนี้เป็น message broker ทั้งหมดเลย ไม่ต้องใช้ตระกูลนี้เลย ใช้แค่ Postgres อาจจะพอแล้ว (รึเปล่า?)
ใครที่ใช้อยู่ ลองฟังดูก่อนว่าจะลบ message broker ทิ้ง หรือจะใช้ Postgres แทน มันมี pros and cons อยู่ แนะนำตัวนิดนึงครับ ชื่อเอฟนะครับ
เป็น dev อยู่ที่เต่าบิน เป็น software engineer ครับ ผมจบเภสัชมา เป็น software engineer ได้ประมาณ 4 ปีแล้ว
อันนี้เป็น QR LinkedIn ใครอยากจะ connect กันก็ได้ สไลด์นี้ตอนสุดท้ายจะมีให้ดาวน์โหลดครับ อาจจะยังไม่ต้องดูขนาดนั้นก็ได้
ในระบบที่เป็น event-driven application หน้าตาจะเป็นประมาณนี้ คือ client ส่ง request ไปที่ service ของเราครับ เคสที่เป็นคลาสสิกเลย เขาชอบยกตัวอย่างพวก user registration
เวลา user ลงทะเบียนเนี่ย ต้องทำอยู่ 2 อย่าง ใน event-driven system คือต้องเซฟ user คนนั้น ลงไปใน database และต้อง publish event ออกมาให้ message broker ครับว่า มี user ชื่อนี้ อีเมลนี้ register เข้าระบบแล้ว
ทีนี้ worker ไม่ว่าจะเป็นตัวอะไรก็ตาม เป็น handler มันจะคอย subscribe event นั้นไว้
สมมติว่าเป็น service ส่งอีเมล welcome
ตอนที่ user register มันจะฟังอยู่ว่า
มี event ที่ user ลงทะเบียนมาแล้วหรือเปล่า ถ้ามีก็จะเอารายละเอียด payload ใน event นั้น ไปทำงานอะไรต่อครับ
ปัญหาของ Message Broker ทั่วไปคือการขาด Transactional Consistency2:17
การใช้ message broker เป็นท่ามาตรฐานทั่วไป ของการทำ event-driven application แต่ message broker ไม่ได้ลอยมาฟรีๆ จากฟ้า มันคือ infrastructure ชนิดหนึ่ง เราต้องมีความรู้เรื่อง config และดูแลมัน
อีกอย่างที่สำคัญเลยก็คือ
มันไม่ transactional consistency เช่น ถ้าเรา register user แล้ว database เซฟ
แต่ event ไม่ publish จะเกิด eventual inconsistency คือ user register แต่ event ไม่ส่งไป หรือถ้ากลับกันคือ มี event แต่ database ไม่เซฟ
ก็จะเกิดผลเหมือนกัน อันนี้แก้ได้ด้วยการใช้ pattern เช่น outbox แต่มันคือ piece of code ที่ต้องดูแล
มันไม่ได้ build-in เข้ามาว่าทำแล้วได้ฟรี transactional consistency เลยครับ
เก็บ Event ลงใน Database Transaction เดียวกันเพื่อการันตี Consistency3:28
ทีนี้ถ้าเราเปลี่ยนไปว่า โอเค แทนที่เราจะมี message broker เราก็มี table หนึ่งใน database ไปเลย ว่าเอาไว้เก็บ event นะครับ ตอนที่เรา insert user กับตอนที่เรา publish event มันก็คือ transaction เดียวกันเลยนะครับ ถ้าอย่างใดอย่างหนึ่งผิดพลาด เราก็จะ rollback นะครับ ถ้าทั้งสองอย่างผ่านไปด้วยดี เราก็จะเซฟลง database และ publish event ได้ในคราวเดียวกันนะครับ แล้วก็ worker ก็จะ subscribe ไปที่ event นั้น
และทำงานได้ตามปกตินะครับ ทีนี้ในตัวอย่างนี้มันมี worker แค่อันเดียวนะครับ สมมติว่าเราเพิ่ม worker มีหลาย ๆ ตัว
สมมติว่าเรา spin app ขึ้นมา 6 instance พร้อมกัน
สมมติว่ามี instance หนึ่งจับตัว event ตัวหนึ่งได้
แล้วก็เอาไปทำใช่ไหมครับ แต่ว่ามันยังทำงานไม่เสร็จ ระหว่างนั้น worker อีกตัวหนึ่งก็ไปเห็น event เดียวกัน แล้วก็จับมา process เหมือนกัน อันนี้มันจะเกิด race condition ได้ ใช่ไหมครับ มันจะทำให้ 1 event ที่ควรจะถูก process แค่ครั้งเดียว มันถูกทำงานมากกว่า 1 ครั้ง เป็น race condition ครับ
แก้ปัญหาคิวซ้ำซ้อน (Race Condition) ด้วย SKIP LOCKED และ NOTIFY4:48
เราสามารถแก้ปัญหา race condition ใน Postgres ได้ ด้วยการใช้ NOTIFY / LISTEN
กับ SELECT FOR UPDATE SKIP LOCKED มีใครรู้จักไหม 2 query นี้
ตัว NOTIFY / LISTEN มันจะเป็นเหมือน pub/sub system ของ Postgres อีกทีหนึ่ง มันเอาไว้เช็กว่า... สำหรับวิธีใช้ก็คือเราไป...
เราไป register ที่ table หนึ่งก่อนว่าโอเค
ถ้ามี event เกิดขึ้น มี table นี้มันถูก insert ให้ publish event ออกมาว่า เฮ้ย table นี้ถูก insert นะ แล้ว worker ที่ listen ใน table เดียวกันอยู่ มันจะรู้ว่า อ๋อโอเค มี table insert มา มันก็จะไป grab ตัว data นั้นมาทำอะไรต่อได้นะครับ ส่วน SELECT FOR UPDATE SKIP LOCKED เนี่ย มันเอาไว้ใช้สำหรับการล็อก row นั้น เพื่อที่ว่า worker สมมติว่า worker SELECT FOR UPDATE SKIP LOCKED worker มันจะได้ select row นั้นได้แค่คนเดียว คนอื่นถ้าเกิดเจอ row ที่มันล็อกอยู่ มันจะข้าม row นั้นไป มันจะไปหา row อื่นนะครับ ตัวอย่างก็คือ สมมติว่าเรามีตาราง job อยู่ ใช่ไหมครับ
อันนี้คือเราไปหาว่าโอเค ไปหา job ที่ status pending อยู่
เอามาหนึ่งตัวแล้วก็ SELECT FOR UPDATE SKIP LOCKED ไว้ คราวนี้ตัว ID นี้มันก็จะถูกล็อกเอาไว้แล้ว ด้วย worker ที่รัน query ตัวนี้
ส่วนถ้าเรามี worker มากกว่าหนึ่งตัว ตัวอื่นจะข้าม row นี้ไป จะไปอ่าน row อื่นที่ยัง pending อยู่ แล้วเราก็จะเปลี่ยน status เป็น processing ว่า โอเค ฉันกำลังทำอยู่นะ worker ตัวอื่นก็จะไม่มา grab ตัว row นี้ไปทำอีก
เป็นวิธีการแก้ race condition
ใช้ไลบรารี pg-boss จัดการ Queue ทรงพลังโดยไม่ต้องมัดคิวรีเอง6:50
แต่มันดูยุ่งยากไหม ดูแบบเอ๊ย ฉันต้องรู้เรื่อง NOTIFY / LISTEN กับ SELECT FOR UPDATE SKIP LOCKED อะไรอย่างนี้นะ มันก็มีคนทำทั้งหมดนี้รวมเป็น package เอาไว้แล้วนะครับ ชื่อว่า pg-boss นะครับ
เป็น library ใช่ไหมครับ ใช่ เป็น library ใช้ใน Node.js ก็ได้ Deno ก็ได้ Bun ก็ได้นะครับ ได้หลาย runtime เลยนะครับ มีใครรู้จัก Supabase ไหม Supabase Queue Supabase Queue build on top จากไอ้ตัวนี้แหละครับ
ถ้าเราไม่อยากใช้ Supabase ก็มาใช้ตัวนี้แทน แต่อาจจะต้อง implement ตัว event bus เองเนาะ
โอเค โค้ดจะเยอะนิดหนึ่งนะ ค่อย ๆ ไปเนาะ เรามีเวลากันเหลือเฟืออยู่ สมมติว่ามีตัว database URL ตัวนี้
แล้วผมจะแยกสีไว้ว่า ตัวนี้จะเป็นตัวของ Postgres
สำหรับการ insert data ส่วน pg-boss เนี่ย เอาเป็น Postgres สำหรับตัว publish event อันนี้คืออย่างที่เห็นก็คือมันแยกกันอยู่ มันแยกกัน 2 transaction ในนี้
วิธีการรันคำสั่ง Insert และ Publish Event ให้อยู่ใน Transaction เดียวกัน8:06
พอเรา insert ด้วย PG นะครับ PG ที่มาจาก built-in ของ Bun เนาะ เรา insert ตัวนี้เสร็จปุ๊บ แล้วโดยทั่วไปเนี่ย เราก็จะ insert ตัว เราก็จะ publish ให้ pg-boss เนี่ย เป็นตัว send เข้าไปอยู่ใน queue นะครับ แบบนี้ก็ เวลาเรา insert ใน function เนี่ย function main ตัวนี้เนี่ย พอรันครั้งหนึ่งเนี่ย โอเค มันก็จะรันตัวนี้ 1 ครั้ง และตัวนี้ 1 ครั้ง ทีนี้เหมือนเมื่อกี้ผมบอกว่าไง มันใช้อันนี้ปุ๊บ มันจะเป็น transaction เดียวกันได้ใช่ไหม แต่ที่เราเห็นนี่คือ สีแดงกับสีน้ำเงินมันแยกกันอยู่ใช่ไหม มันยังไม่ใช่ transaction เดียวกันนะครับ ถ้าเกิดว่าเรา initiate pg-boss instance แยกกันแบบนี้
คือ pass ตัว connection string มาทีละครั้งแบบนี้
มันจะยังไม่สามารถ utilize ความสามารถของ pg-boss ได้
สิ่งที่เราทำก็คือ pg-boss เนี่ย มันอนุญาตให้เรา implement ตัว class ที่สามารถรัน method นี้ได้นะ
แล้วก็ pass ออกมาเป็น
database object ออกมาตัวนี้ แล้วก็ตัว interface นี้เนี่ย มันต้องรัน code นี้ได้โดยไม่ error เนาะ
เรียก concept นี้ว่า bring your own database ก็ทั่วไป ไม่ได้มีอะไรซับซ้อน ก็คือคราวนี้เราก็จะสามารถ ใช้ transaction เดียวกันได้แล้ว คือ database instance เดียวกัน ในการทั้ง insert user เข้าไปใน database และการใช้ publish event ผ่าน pg-boss นะครับ
ใช้งาน Kysely ช่วยจัดการ Transaction และป้องกัน Error ด้วย Rollback อัตโนมัติ9:48
อันนี้ก็คือเป็น class ที่เรา implement มีใครรู้จัก Kysely ไหม Kysely เป็น query builder มีใครรู้จัก query builder ไหม
มีใครรู้จัก ORM ไหม Object Relational Mapper โอเค ค่อย ๆ มีคนรู้จักทีละคน ตัว Kysely ก็จะเป็น query builder ตัวหนึ่งนะครับ เอาไว้สำหรับเขียน SQL query ใน code TypeScript นะครับ
ก็ลองไปศึกษากันได้นะครับ
โอเค สิ่งที่เราจะทำก็คือ เราจะเปิด transaction นะครับ ใช้ Kysely ในการเปิด transaction แล้วเราก็ pass transaction ตัวนั้นน่ะ มาใช้ในการ insert user แล้วก็ตัว adapter เนี่ย เราก็ pass เข้าไปใน pg-boss
เพื่อให้ pg-boss เนี่ย มันใช้ transaction เดียวกับที่เรา insert ตัวนี้ได้ อย่างนี้มันจะทำให้เกิด transaction เดียวกันได้ แล้วข้อดีของ Kysely ก็คือ เราไม่ต้องมาคอย Kysely.begin
พอทุกอย่างเสร็จปุ๊บ Kysely.commit อะไรอย่างนี้ ถ้าเกิด code ใน loop นี้มัน error มัน rollback ให้เลยอัตโนมัติ อันนี้คือเป็นข้อดีของ Kysely อันนี้ก็เป็น transaction แล้ว โอเค เดี๋ยวมี demo ให้ดูนะครับ
การจัดการ Dead Letter: ดึง Job ที่พังกลับมาทำใหม่ได้อย่างง่ายดาย11:33
อันนี้คือผมรัน ไม่เห็นเลยแฮะ อันนี้จะชัดกว่าไหม หรือว่า zoom
อันนี้คือเราทดลอง มี application
เดี๋ยวจะให้ repo ไปด้วยนะครับ เราก็ทดลอง insert ตัวนี้
มี API ในการ register user นะครับ ชื่ออะไรดี Brian อะ
เดี๋ยวให้ทุกคนสังเกตหน้าจอตรงฝั่งซ้ายมือดี ๆ นะครับ ว่ามันเป็น asynchronous operation นะครับ ก็คือตัว user จะถูก insert ก่อน แล้วก็ค่อยจะมี event ตามมา event จะมี 2 อัน ก็คือ audit service แล้วก็ notification service แบบนี้ครับ อันนี้ insert ก่อนนะครับ แล้วก็ถึงจะมี event ตามมา อันนี้ก็จะให้ดูว่า โอเค เราสามารถ publish event เดียวกัน ก็คือ user แต่ว่าตัวคนฟังอาจจะมีกี่ service ก็ได้ มี audit service มี notification มีอะไรอีก service ก็ได้ ที่จะคอยรับฟัง event เดียวกันเนี่ย เขาเรียกว่า fan-out event publishing เนาะ
ก็ app นี้เขียนด้วย Elysia นะครับ
แล้วก็เราสามารถมา query job ใน database ตัวนี้ได้ ทุกคนอาจจะสงสัยว่า อ้าว แล้วถ้าตัว worker มันทำงานไม่สำเร็จ มัน throw error ออกมา job ตัวนี้มันก็จะมีตัวที่เรียกว่า dead letter นะครับ ก็จะมี state failed พอ failed เสร็จปุ๊บ เราก็สามารถไปดูได้ว่า อ๋อ worker มันทำอะไรผิดไป แล้วเราก็มาเปลี่ยน state นี้ง่าย ๆ เลย คือวิธีการทำ rerun ก็คือ เปลี่ยน state นี้จาก failed ให้กลายเป็น pending worker มันก็จะหยิบไปทำอีกครั้งหนึ่ง แค่นั้นนะครับ ก็คือวิธีการ rerun ก็จะค่อนข้าง simple
เมื่อกี้มี 2 table ใช่ไหม ก็เพิ่มขึ้นมาแล้ว เดี๋ยว แล้วทำไมผมจะโชว์ code ไม่ได้ว้า แป๊บหนึ่ง
(จ้าเสียเหลือเกิน)
(Developer ใช้ white theme ครับ)
มันจะได้เห็นชัดๆ ไงฮะ โอเค
เพิ่ม Replica: เพิ่มจำนวน Worker ได้ทันทีโดยไม่ต้องแยก App14:21
อันนี้คือเป็น entry point ของแอปนะครับ
เป็น entry point ของแอปก็คืออย่างแรกมา เราก็คือ start ตัว event bus ก่อนนะครับ เพื่อให้มันคอยรับ event เข้ามา เสร็จปุ๊บทีนี้ ถ้าใช้ Elysia เนี่ย เราสามารถ extend context มันได้ด้วย .decorate นะครับ แล้วก็มันจะคล้ายๆ dependency injection แต่ว่าแบบ elegant กว่านะครับ
ก็เราก็จะมี event handler ตัวนี้ เป็น Elysia instance นะครับ แล้วก็ตอน create notification plugin
กับ create analytics plugin เนี่ย มันคือการ start ให้ worker เริ่มรับฟังว่า มี event ที่มันตั้งใจฟังอยู่หรือเปล่านะครับ ทีนี้ตัวแอปหลักอะ พอเรา start Elysia instance แบบนี้ใช่ไหม ตัวแอปหลักเราก็ไม่ต้องทำอะไรเยอะ เราก็แค่ use ตัวนี้เลย Simple มาก Elegant สุดๆ
ทีนี้เดี๋ยวเราจะโชว์เดโมแบบ
ถ้าเราอยาก scale แอปแบบนี้ เราสามารถ scale แบบ simple ได้ก็คือ เพิ่มตัว instance เนี่ยแบบตรงๆ ได้เลย ก็คืออย่างที่เห็นตัวนี้คือเรามี 1 Main 1 Worker ใช่ไหม
เพราะว่าอันนี้คือ 1 แอป Main แล้วก็ 1 Worker
ถ้าเราอยากสมมติว่ามี load เพิ่มขึ้นมาเยอะๆ เนี่ย แล้วเราอยากให้มี worker หลายๆ ตัว คอยทำงานได้ เราอาจจะคิดว่าเฮ้ย เราต้องมาเขียน code ใหม่ให้ เอา event handler ออกมาเป็นอีกแอปหนึ่ง แล้วแบบ start แยกหรือเปล่า ไม่ต้องเลยครับ เราก็แค่เพิ่มตัว replica ของไอ้ตัวเนี้ย เพิ่มขึ้นแบบตรงๆ เลย สมมติว่ามีอยู่ตอนแรกมีอยู่ 1 ตัว เราอยากเพิ่ม load อย่างนี้ เราก็เพิ่มเป็น 6 อะไรอย่างนี้ก็ได้ เดี๋ยวโชว์ให้ดู
เดโม Scale ระบบ: จ่ายงาน 100 Request พร้อมกันผ่าน 6 Instances16:59
ที่เห็น อันนี้คือตัวแอปเนี่ย
แอปหลักมี 6 instance นะครับ แล้วก็มี Postgres ตัวนึงนะครับ แล้วก็มี Caddy ใครรู้จัก Caddy บ้าง? Caddy เป็น...
ในที่นี้มันทำตัวเป็น load balancer ให้เรานะครับ อันนี้ก็ elegant เหมือนกัน เพราะว่า load balancer ของ Caddy ทำง่ายมาก มีแค่ประมาณ 3 บรรทัดได้เลยนะครับ เดี๋ยวให้ดูว่ามัน run อะไรอยู่นะครับ
อย่างที่เราเห็นนะครับ ถ้าเราดู log มา คราวนี้แอปมัน start มาทั้งหมด 6 instance แล้วใช่ไหม แล้วเราจะทำการ register user เข้าไปนะครับ
ชื่อ Jane insert เข้าไป
อ้าว โอเค อันนี้คือโชคไม่ดีเท่าไหร่ คือจะเห็นได้ว่า แอป 6 นะครับ เป็นคน insert user แต่ว่าบางทีอะ แอป 6 มันไม่จำเป็น ต้องเป็น worker ในการเอา event มา run เนาะ เดี๋ยวลองอีก ลองแบบ จะให้ดูตัวอย่างว่ามันสามารถที่ แอปหนึ่งเป็นคน insert แต่อีกแอปหนึ่ง เป็นคนทำ event ได้นะครับ
อันนี้เป็นต้นนะครับ ก็คือแอป 6 เป็นคน insert user นะครับ แต่ว่าแอป 3 เป็นคนเอา event มา run นะครับ แบบนี้เป็นต้น ถ้าเรามีหลายๆ event เข้ามาพร้อมๆ กันอะ ถ้าเรามีหลายๆ แอป instance แบบนี้ มันก็จะกระจายงานนะครับ เดี๋ยวโชว์ให้ดู
อันนี้ผมมีทั้งหมด 100 user ที่จะ insert
ด้วย rate query 10 request per second นะครับ ก็รันไป มันก็จะค่อยๆ insert ไปนะครับ
แอปนึง insert แอปนึงทำ แอปนึง insert เดี๋ยวพอเสร็จปุ๊บ อาจจะเห็นได้ว่า การ insert เสร็จแล้ว แต่ event ยัง process อยู่ อันนี้คือการโชว์ว่า event มันทำงาน
ถ้าเรามีหลายแอป instance อย่างนี้ มันก็จะ drain หมดเร็วขึ้น เราใช้คำว่า drain มันก็จะ drain event หมดเร็วขึ้น เอาไว้ใช้เวลาแบบอยู่ๆ มี spike ขึ้นมานะครับ
เสร็จแล้วครับ ประมาณ 10 วินาที ก็ 100 user register เมื่อกี้ ด้วย rate 10 query per second ก็ใช้เวลาห่างกันไม่นาน อันนี้มี 6 instance เนาะ
ข้อดี: ได้ Consistency แบบฟรีๆ ลดภาระการดูแล Infrastructure สำหรับโปรเจกต์ขนาดเล็ก20:35
ทีนี้เรามาดู pros and cons บ้างนะครับ มันดู simple มาก แบบใช้แค่ Postgres เป็น event-driven ก็พอแล้ว ในบริบทของเต่าบิน เรามี event เข้ามาเยอะมาก คือตู้เรามีเยอะมาก Postgres อาจจะไม่เหมาะเท่าไหร่ เรามาดูกันว่า use case แบบไหนเหมาะหรือไม่เหมาะ อย่างเช่น ข้อดีอย่างแรกที่เราได้ คือ transactional consistency มาก่อนเลย เราไม่จำเป็นต้อง implement outbox pattern เราไม่จำเป็นต้อง implement pattern ที่ ensure ว่า event กับ action ไปด้วยกัน
Ideal มากสำหรับแอปที่เพิ่งเริ่ม
คุณเพิ่งเริ่มทำแอปสำหรับ user 2-3 คน แล้วอยากทำเป็น event-driven
ก็ใช้อันนี้ได้ เพราะไม่มี overhead ด้าน RabbitMQ หรือ Kafka คงไม่มีใครใช้ Kafka กับแอป 2 คนเนาะ ที่จะมี event broker เพิ่มมาอีกตัวให้ต้อง maintain
ทุกคนน่าจะเคยใช้ Postgres กันอยู่แล้ว มีอยู่ใน stack ของตัวเองอยู่แล้ว
เราก็แค่ใช้ connection string เดิม
แล้วมีตัว package pg-boss เพิ่มเข้าไปแค่นั้นเอง
อย่างที่บอกไปคือ low complexity ไม่ต้องบริหาร infrastructure อะไรเพิ่ม แล้วก็ effortless scaling นะครับ ไม่ต้องคอยแยก deploy worker กับแอป คุณก็เพิ่มแอปไปเลย เพราะในแอปเป็นทั้ง worker และแอปพลิเคชันหลักในตัวมันเอง เราแค่เพิ่มตัว replica เข้าไป จาก 1 เป็น 6
ข้อจำกัด: รับ Throughput เยอะหลักพันไม่ได้ และมีข้อจำกัดในการทำ Event Sourcing22:24
ทีนี้มาดู cons กันบ้างนะครับ cons ก็คือ มันจะถูก bottleneck by IOPS ของ Postgres นะครับ มันจะไม่สามารถรับ high throughput event เยอะๆ ได้แบบ Kafka นะครับ เพราะว่า Kafka มันแค่ write เข้าไปใน log ใช่ไหม ถ้า use case ของคุณ ต้องการแบบ streaming data เยอะๆ จาก user อย่างนี้ Postgres ไม่เหมาะนะครับ อย่างที่ผมโชว์ให้ดูเมื่อกี้ 10 request per second อย่างนี้ เท่าที่ผมลองนะ ประมาณพันจะเริ่ม bottleneck ละ 1,000 request per sec นะครับ ก็อย่าให้เกิน 100 ละกัน ถ้าเริ่มแตะ 100 request per sec เนี่ย ก็เริ่มมองหาลู่ทางอื่นละกันนะครับ แล้วก็ event sourcing ใครรู้จัก event sourcing บ้างครับ event sourcing มันคือการที่เราสามารถ replay event ทั้งหมดที่เคยเกิดขึ้นกับแอปเรา ย้อนกลับไปได้ เราสามารถเอาแค่ สมมติว่าเรามี event ใหม่ๆ เลย แล้วก็มีแอปพลิเคชันใหม่เอี่ยมอ่องเลย และก็มี event เราสามารถ replay event นี้ ให้แอปพลิเคชันเราอยู่ใน instance เดียว กับตอนที่ event นี้มันเกิดขึ้นได้ อะไรประมาณนี้ เดี๋ยวทุกคนเอา keyword ไป search เองได้เนาะ
มันสามารถทำได้ใน Postgres นะครับ แต่ไม่แนะนำละกัน มันไม่ได้ถูกทำมาให้เหมาะกับ event sourcing ถ้าต้องการทำ event sourcing จริงๆ เป็นไปได้ว่าแอปพลิเคชันของคุณ น่าจะต้องต้องการ Kafka ด้วยเหมือนกัน เพราะ Kafka เหมาะกับการทำ event sourcing มากนะครับ แล้วก็ถ้าเป็น microservice แล้วสร้างด้วย
Python ด้วย Go ด้วย TypeScript แยกกัน ตัว pg-boss มันเป็น JavaScript library เท่านั้นเนาะ
มันก็ไม่เหมาะ
แล้วก็ small limited community นะครับ ก็คือหาข้อมูลอ่านค่อนข้างยาก แบบเวลาที่มีปัญหา คือถ้าเกิดต้องใช้ในรูปแบบที่มัน advanced กว่านี้ มันจะไม่ค่อยมีตัวอย่างให้ดูละ ก็ลองดู repo นี้เป็นตัวอย่างก็ได้เนาะ ถ้าเกิดใครอยากจะทำตามนะครับ อันนี้คือเป็นสไลด์อันนี้นะครับ ถ้าใครอยากได้ก็สแกนไปได้นะครับ แล้วก็เปิด floor ให้ถามคำถามอีก 4 นาที
Q&A: หากรับ Request ปริมาณมากเกินไประบบจะค้าง ควรจัดการ Connection Pool ให้ดี24:33
ได้ครับ ขอบคุณพี่เอฟนะครับ ตบมือหน่อยเร็ว โอเค คราวนี้ครับ เดี๋ยวเราไปดูต่อ กันในไลฟ์สดย้อนหลังอีกทีก็ได้ สำหรับ QR code นะครับ ใครมีคำถามที่อยากจะถามพี่เอฟบ้างไหมครับ เกี่ยวกับพวก messaging ในลักษณะคล้ายแบบนี้ครับ ซึ่ง messaging
ผมชื่อ Harlequin ครับ Harlequin เป็น TUI เป็น SQL editor
ที่สร้างจาก Python ใช่
เจอเครื่องมือ editor สีขาวๆ ถูกใจ แต่จะแนะนำตรงๆ นะ ใช้ DataGrip เถอะ มันฟรีแล้ว
ซึ่งตัวนี้ event broker ก็จะเหมาะเหมือนเราทำพวก concept proof นะ ใช่ไหมครับ แล้วก็เหมือนเราใช้ database Postgres อยู่แล้ว ครับผม มีใครมีคำถาม? ครับผม เอา Postgres มาทำพวกนี้ครับ จัดการ session database ยังไงครับ หรือว่าปล่อยให้พุ่ง เพราะจะเปิด stream ไม่ได้ ถูกไหมครับ อะไรนะฮะ? มันน่าจะเปิด ประมาณว่ามันต้องเปิด ปิด session ตลอดเวลา มันอาจจะมี session ที่ค้างอยู่หรือเปล่าครับ ถ้าเกิดมี request เข้ามาเยอะๆ ครับ ถ้าเกิดเยอะจริงๆ ตอนที่ผมลอง ผมลองระดับหมื่น request per sec มันค้างไปเลย แบบ froze ไปเลย ผมก็คิดว่าน่าจะมี limitation ของมันอยู่ แต่หลักๆ ก็คือคุณก็มี pool
ตัว pg-boss ตัว library มันจะมีให้ set up connection pool ได้อะไรอย่างนี้ครับ ก็หลักๆ คือจัดการ connection pool ตรงนั้น ที่มันอนุญาตให้ทำนะ แต่ว่าในแง่ของการที่แอปพลิเคชันในเต่าบินเอง เป็นตัวนี้ไหม ก็บอกตรงๆ ว่า อันนี้คือแอปในเต่าบินเรา เป็น event-driven ค่อนข้างเต็มระบบ แต่ด้วย throughput ที่เยอะ เราเลยต้องใช้ทั้ง Kafka ทั้ง Rabbit นะครับ แต่ผมเกิดไอเดียขึ้นมาว่า เฮ้ย ถ้าเกิดแอปฉันเพิ่ง start
เป็น startup แบบมีคนใช้แค่ 100 คนอะไรอย่างนี้ แบบสมมติทำแอปในโรงพยาบาล คนใช้แค่ในโรงพยาบาล 100 คน แล้วแบบจะต้องมาปวดหัวตั้ง Kafka ตั้ง Rabbit ให้ user ด้วยเหรอ มันมีวิธีอื่นไหมที่จะทำ event-driven ได้ โดยไม่ต้องใช้อะไรพวกนั้นนะครับ ซึ่งผมคิดว่าอันนั้นเป็น area ที่เหมาะมาก ผมว่าไม่ใช่แค่ POC ด้วย มัน production ได้เลย ถ้า load คุณไม่เยอะนะครับ เท่าที่เทสมาคือ ประมาณ 100-200 request per second ยังได้อยู่ครับ แต่ถ้ามากกว่านั้น เริ่ม bottleneck มากๆ แล้ว
Q&A: วางโครงสร้างผ่าน Clean Architecture เผื่อย้ายระบบไปยัง Message Broker จริงในอนาคต27:00
ผมมีคำถาม ถ้าเกิดสมมติเราใช้ตัวนี้ แล้วถ้าวันใดวันหนึ่งเราอยากจะ scale up เพราะ user เราเยอะขึ้น เรา migrate หรืออะไรอย่างนี้ ยากไหมครับ ในแง่ว่ายากไหม ก็ไม่ยาก เพราะเราทำเป็น interface
แอปเราทำเป็น port and adapter อยู่แล้ว เป็น clean architecture เราสร้าง contract เอาไว้ contract คือ interface event bus event bus ทำได้สองอย่างคือ publish กับ subscribe
method ข้างในจะเป็นยังไง ช่างหัวมัน แล้วแต่ว่าเราจะเอาไปต่อกับอะไร ถ้าเราต่อกับ pg-boss เราก็ใส่ pg-boss เข้าไป ถ้าเราอุ๊ย ฉันต้อง scale และ ก็ไปเอา logic ของการทำไอ้นี่จาก Rabbit เข้ามา implement
ตัว contract ตัวนี้ แค่นั้นเอง มันก็คือการ software engineering ทั่วไป เป็น clean architecture ครับผม คือ message bus มันก็มีอยู่แค่นี้แหละ หลักการ concept เหมือนกัน ขึ้นอยู่กับว่าตัว engine ด้านหลังเราจะใช้เป็นตัวอะไรนะครับ เราก็แค่ทำให้มันตรงกับตัว engine เราก็แบบ concept proof ก่อน ถ้า user เยอะ เราค่อยไปปวดหัวกับเรื่องพวกนั้น โอเค แนะนำก็คือ ถ้าเกิดใครลองถ่ายรูปไปก็ได้นะครับ ก็คือไอ้ตัวแอปนั้นน่ะ คือ repo นี้ นะครับ elysia-eda-ddd นะครับ
ก็คือจะได้ไม่ใช่เฉพาะ template Elysia ก็คือได้ทั้ง template event-driven ได้ทั้ง template domain-driven ไปด้วย ก็คือเราทำแบบ ที่โชว์ให้ดูเมื่อกี้คือทำเต็มระบบเลย มี domain-driven ด้วย แต่ว่ามันพูดเยอะไม่ได้เนาะ ประมาณนี้ แล้วก็นี่ก็เป็นส่วนตัว ใครอยาก follow ก็ยินดี
ได้ครับ ไปติดตามพี่เอฟกัน ขอบคุณครับ สำหรับ event-driven โดยที่ไม่ต้องใช้ message broker นะครับ