Background Tasks และ Webhooks กับ Functions
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”Database Webhook ทำให้ row ที่เปลี่ยนแปลงใน Postgres เรียก Edge Function ได้อัตโนมัติ ส่วน EdgeRuntime.waitUntil ทำให้ function นั้นตอบกลับทันทีในขณะที่งานที่ช้ากว่ายังทำต่อในเบื้องหลัง
Database Webhooks ให้ฐานข้อมูลเป็นฝ่ายเรียกออกไปเอง
หัวข้อที่มีชื่อว่า “Database Webhooks ให้ฐานข้อมูลเป็นฝ่ายเรียกออกไปเอง”Edge Function ทุกตัวที่ผ่านมาถูกเรียกโดย client อย่างชัดเจนด้วย supabase.functions.invoke() แต่งานจริงจำนวนมากควรเกิดขึ้นเพราะการเปลี่ยนแปลงในฐานข้อมูล ไม่ใช่เพราะ client จำได้ว่าต้องเรียก เช่น ส่ง welcome email ตอนมี row ใหม่ใน profiles หรือแจ้งระบบปลายทางตอนมีการสั่งซื้อ Database Webhooks ตอบโจทย์นี้ตรง ๆ ตั้งค่าบน table สำหรับ event insert, update หรือ delete แล้ว Supabase จะยิง HTTP request ให้ทุกครั้งที่ event นั้นเกิดขึ้น
เบื้องหลัง Database Webhook คือ wrapper สะดวกใช้ที่ครอบ Postgres trigger อยู่ สร้างบน extension pg_net pg_net ยิง HTTP request จากใน Postgres แบบasynchronous ดังนั้น network call ที่ช้าหรือ fail จะไม่บล็อกการเขียนข้อมูลที่ทำให้เกิด trigger นั้นเลย คำสั่ง insert commit ได้ไม่ว่า HTTP call ของ webhook จะใช้เวลานานแค่ไหน
-- Roughly what a Database Webhook configures under the hood:-- a trigger that calls pg_net on insert into public.profilescreate trigger "profiles_insert_webhook"after insert on public.profilesfor each rowexecute function supabase_functions.http_request( 'https://<project-ref>.supabase.co/functions/v1/welcome-email', 'POST', '{"Content-Type":"application/json"}', '{}', '5000');ในทางปฏิบัติคุณตั้งค่านี้ผ่าน Dashboard ในส่วน Database Webhooks แทนที่จะเขียน trigger เอง แต่การรู้ว่าเป็น “trigger บวก pg_net” ช่วยอธิบายว่าทำไม webhook ถึง asynchronous และทำไมถึงชี้ไปที่ URL ของ Edge Function ได้เหมือน HTTP client ทั่วไป
ตอบกลับเร็วในขณะที่งานยังทำต่อ EdgeRuntime.waitUntil
หัวข้อที่มีชื่อว่า “ตอบกลับเร็วในขณะที่งานยังทำต่อ EdgeRuntime.waitUntil”งานบางอย่างที่ trigger แบบนี้ไม่ควรทำให้ response ช้าลง เช่น ส่ง confirmation email เขียน analytics log หรือเรียก third-party API ที่ใช้เวลาสักไม่กี่วินาที การให้ผู้เรียกเดิม (ซึ่งมักเป็นกลไก webhook ของฐานข้อมูลเอง) รอสิ่งเหล่านั้นทั้งหมดเป็นเรื่องเสียเวลาเปล่า EdgeRuntime.waitUntil(promise) แก้ปัญหานี้ ทำเครื่องหมาย promise ว่าเป็น background task ที่ยังรันต่อได้แม้ function จะส่ง response ไปแล้ว
Deno.serve(async (req) => { const payload = await req.json()
// Do not await this — let it run after the response is sent EdgeRuntime.waitUntil(sendConfirmationEmail(payload))
return new Response('ok', { status: 200 })})
async function sendConfirmationEmail(payload: unknown) { // Slow third-party API call, logging, etc.}จุดสำคัญคือไม่มี await อยู่หน้า sendConfirmationEmail(payload) ถ้า await ตรงนั้น response จะต้องรอจนกว่า email จะส่งเสร็จ การส่ง promise ที่ยังไม่ resolve เข้า waitUntil คือการบอก Deno runtime ให้เก็บ instance ของ function ไว้จนกว่า promise นั้นจะจบ โดยไม่ทำให้ HTTP response ต้องรอ
รวมสองอย่างเข้าด้วยกัน flow ยืนยันคำสั่งซื้อ
หัวข้อที่มีชื่อว่า “รวมสองอย่างเข้าด้วยกัน flow ยืนยันคำสั่งซื้อ”ตัวอย่างจริงแบบครบวงจร Database Webhook ยิงตอน insert เข้า orders เรียก Edge Function ที่ตอบกลับทันทีเพื่อยืนยันว่าเขียนข้อมูลสำเร็จ ในขณะที่ confirmation email ถูกส่งอยู่เบื้องหลัง
Deno.serve(async (req) => { const { record } = await req.json() // the new orders row, from the webhook payload
EdgeRuntime.waitUntil(sendOrderConfirmationEmail(record))
return new Response(JSON.stringify({ received: true }), { headers: { 'Content-Type': 'application/json' }, })})
async function sendOrderConfirmationEmail(order: { id: string; email: string }) { // Call an email provider here; this runs after the response is already sent}ฐานข้อมูลไม่ต้องรอ email provider เลย ผู้เรียก webhook ได้ 200 เร็ว ๆ และ email ก็ยังถูกส่งอยู่ดี เพียงแต่ส่งหลังจาก HTTP round trip จบไปแล้ว
flowchart LR insertRow["insert into orders"] --> webhook["Database Webhook (pg_net, async)"] webhook --> edge["Edge Function"] edge --> respond["Immediate response: 200 received"] edge -->|"EdgeRuntime.waitUntil"| bg["Background task: send confirmation email"]