

เมื่อระบบหนึ่งเปลี่ยนข้อมูลแล้วต้องแจ้งอีกระบบ เรามักเริ่มจากเรียงงานตรงๆ คือบันทึกฐานให้สำเร็จก่อน แล้วค่อยส่ง event ไปบอกปลายทาง วิธีนี้อ่านตามโค้ดได้ง่าย แต่ยังมีช่วงระหว่างสองคำสั่งที่โปรแกรมหยุดหรือการเชื่อมต่อขาดได้
ถ้าฐานบันทึกแล้วแต่ข้อความยังไม่ไป อีกฝั่งก็อาจไม่รู้ว่ามีการเปลี่ยน ส่วนถ้ากลับลำดับให้ส่งข้อความก่อนแล้วฐานค่อยบันทึก ก็มีอีกกรณีที่ข้อความออกไปแล้วแต่ข้อมูลกลับ rollback การสลับลำดับจึงย้ายช่องว่างไปอีกด้าน โดยยังไม่ได้ทำให้สองระบบยืนยันผลร่วมกัน
Transactional outbox เป็นแนวทางหนึ่งที่ใช้เก็บเจตนาว่าต้องส่งอะไรไว้ใน transaction เดียวกับข้อมูลครับ แล้วให้ส่วนส่งตามงานที่บันทึกไว้ต่อภายหลัง
สมมติว่ามีระบบจัดการโครงการซึ่งต้องแจ้งเมื่อรายการเปลี่ยนสถานะ เราอาจบันทึกสถานะใหม่และเพิ่มแถว event ใน outbox ภายใต้ transaction เดียวกัน ถ้า commit ได้ก็มีทั้งข้อมูลใหม่กับรายการรอนำส่ง ถ้า rollback ก็ไม่ควรมี event ของการเปลี่ยนนั้นถูกนำไปประกาศ
ขอบเขตที่ได้จึงอยู่ที่การรักษาข้อมูลกับเจตนาส่งภายในฐานที่รองรับ transaction ร่วมกัน ไม่ได้ทำให้การส่งเครือข่ายและการประมวลผลของผู้รับเข้ามาอยู่ใน transaction เดียวกันโดยอัตโนมัติ
ขั้นเขียนต้องใช้ transaction เดียวจริงตาม driver หรือ ORM ที่เลือก การเรียกสองคำสั่งใกล้กันหรือทำ Promise.all ไม่ได้ยืนยันว่าฐานจะ commit หรือ rollback พร้อมกัน หากเผลอใช้ connection หรือบริบทอีกชุดสำหรับ outbox ก็ต้องตรวจว่าความรับรองที่ตั้งใจยังอยู่หรือไม่

หลังจากนั้นอาจมี worker อ่าน outbox เป็นรอบแล้วส่งข้อความ หรือใช้ change data capture ตามโครงสร้างที่เลือก เช่น Debezium มีแนวทาง Outbox Event Router สำหรับเปลี่ยนข้อมูลจากตารางตามสัญญาไปเป็น event แต่การเลือกเครื่องมือยังต้องดูภาระและระบบที่มีอยู่
แบบอ่านเป็นรอบต้องดูการรับงานเมื่อมีหลาย worker ไม่ให้ทุกตัวหยิบแถวเดียวกันโดยไม่มีขอบเขตเจ้าของ ส่วนแบบ CDC ก็มีสิ่งที่ต้องดูแลเรื่องจุดอ่านและเส้นทางส่งข้อมูลของ connector ทั้งสองแบบต้องมีวิธีรู้ว่างานใดส่งแล้ว งานใดรอ และงานใดต้องตามต่อ
สถานะส่งสำเร็จต้องนิยามตามหลักฐานที่ปลายทางให้ เช่น ยืนยันว่าระบบรับข้อความเก็บไว้แล้ว ไม่ได้แปลว่าผู้รับปลายสุดทำผลธุรกิจเสร็จครบ การตามการนำส่งกับการตามผลประมวลผลจึงยังอาจเป็นข้อมูลคนละชุดกัน
ถ้าข้อความถึงปลายทางแล้ว แต่ worker หยุดก่อนบันทึกหลักฐานการส่ง เมื่อกลับมามันอาจเห็นงานเดิมว่ายังไม่เสร็จและส่งอีกครั้ง การมี outbox จึงไม่ควรถูกอธิบายว่าได้ผลครั้งเดียวตลอดทั้งระบบโดยไม่ต้องมีเงื่อนไขอื่น
ควรมี event ID ที่คงเดิมตามเหตุการณ์ ไม่สร้างใหม่ทุกครั้งที่นำส่ง เพื่อให้ผู้รับตรวจการทำซ้ำได้ แต่ผู้รับยังต้องบันทึกสถานะประมวลผลกับผลที่สร้างอย่างเหมาะสม ไม่ใช่เก็บว่าเคยเห็นรหัสแล้วก่อนทำงานจริงจนรอบกู้คืนข้ามงานที่ยังไม่เสร็จ
การ retry ก็ควรมีขอบเขตเวลา จังหวะและทางตามกรณีทำต่อไม่ได้ ไม่ส่งงานเสียซ้ำถี่ๆ จนแย่งทรัพยากรของเหตุการณ์อื่นทั้งหมด หากต้องย้ายไปสถานะรอตรวจ ต้องรักษา event เดิมกับเหตุผลไว้เพื่อให้คนหรือกระบวนการที่รับผิดชอบตามต่อได้

เหตุการณ์เปลี่ยนสถานะของรายการเดียวกันอาจต้องถูกอ่านตามลำดับที่สัมพันธ์กัน แต่ไม่ได้หมายความว่าทุก event ในระบบจำเป็นต้องมีคิวรวมเส้นเดียว ต้องระบุว่าขอบเขตที่ต้องรักษาคือรายการ โครงการ หรือสิ่งใดตามความหมายงาน
ตัวอย่างในเอกสาร Debezium แยก event ID จาก aggregate key โดย key มีบทบาทต่อการส่งเข้าพาร์ทิชันและการรักษาลำดับตามกลไกนั้น การมีรหัสเหตุการณ์ที่ไม่ซ้ำกับการมี key ที่จัดกลุ่มงานจึงเป็นคนละหน้าที่ ไม่ควรเลือกแทนกันเพราะทั้งคู่เป็นช่องที่มีคำว่า ID
แม้เส้นทางนำส่งรักษาลำดับได้ ผู้รับที่ประมวลผลขนานหรือ retry แยกกันก็ยังต้องดูว่าจะใช้ event เก่ากับสถานะใหม่อย่างไร การมี version หรือ sequence ตามสัญญาช่วยให้ตัดสินเหตุการณ์ที่มาช้าหรือขาดช่วงได้ แต่ต้องกำหนดว่าจะรอ ข้าม หรือจัดการอย่างไร ไม่ทิ้งค่าที่ไม่คุ้นเคยโดยไม่มีหลักฐาน
ถ้า event หมายถึงสิ่งที่เกิดขึ้นในจังหวะหนึ่ง ข้อมูลที่จำเป็นควรสัมพันธ์กับจังหวะนั้น หากผู้ส่งเก็บเพียงรหัสแล้วให้ worker ไปอ่านแถวล่าสุดตอนส่ง ซึ่งอาจเกิดหลังการเปลี่ยนอีกหลายรอบ ก็ต้องระวังว่าจะได้ payload ที่ไม่ใช่การเปลี่ยนเดิมแล้ว
อีกแบบหนึ่งอาจตั้งใจให้ event เป็นสัญญาณว่าข้อมูลเปลี่ยนและให้ผู้รับไปอ่านสถานะปัจจุบันได้ตามสิทธิ์ ทั้งสองแบบทำได้ตามงาน แต่ต้องบอกความหมายให้ผู้รับรู้ ไม่ใช้ชื่อเหตุการณ์เหมือนบันทึกอดีตแล้วส่งค่าใหม่ล่าสุดอย่างเงียบๆ
ควรเลือก field เท่าที่ผู้รับต้องใช้และมีสัญญาเปลี่ยนรุ่น ไม่คัดลอกทั้งแถวที่มีข้อมูลลับหรือข้อมูลส่วนบุคคลไปอยู่ใน outbox กับ broker เพียงเพราะสะดวกตอนสร้าง JSON เพราะข้อมูลชุดนั้นจะมีวงจรเก็บและสิทธิ์อ่านเพิ่มอีกเส้นทางหนึ่ง
สิ่งที่ควรตามมีทั้งจำนวนที่รอ อายุของงานเก่าสุด ความล้มเหลวในการส่งและจุดอ่านของส่วนที่เกี่ยวข้อง ไม่ดูแค่ว่า worker ยังรันอยู่ หาก outbox โตต่อเนื่องแปลว่าข้อมูลภายในกับสิ่งที่แจ้งออกไปอาจห่างกันมากขึ้นตามภาระหรือปัญหาปลายทาง
การเก็บกวาดต้องอิงหลักฐานการส่งและนโยบายตามงาน ไม่ลบแถวเพียงเพราะเก่าก่อนรู้ว่าส่วนส่งอ่านผ่านแล้วหรือยัง อีกทั้ง outbox ไม่จำเป็นต้องเป็นประวัติถาวรที่ครบสำหรับการตรวจทุกชนิด หากต้องใช้ audit ควรกำหนดขอบเขตการเก็บสำหรับสิ่งนั้นแยกให้ชัด
การทดสอบควรจำลองหยุดหลัง commit หยุดหลังปลายทางรับแต่ก่อนบันทึกผล และส่งเหตุการณ์เดิมกลับมาอีกครั้ง แล้วตรวจข้อมูลของทั้งผู้ส่งกับผู้รับตามสัญญา บทนี้ยังไม่ได้ติดตั้ง connector หรือทดสอบส่ง event ในระบบจริง จึงไม่มีอัตราการนำส่งหรือผล exactly once มาอ้าง
Outbox ช่วยปิดช่องว่างระหว่างการบันทึกข้อมูลกับการเก็บงานที่ต้องแจ้งออกไป โดยย้ายการนำส่งมาเป็นขั้นที่ตามและฟื้นต่อได้ เมื่อใช้ transaction ถูกขอบเขต พร้อม event ID ลำดับ payload และผู้รับที่รองรับการทำซ้ำ เราจะจัดการกรณีระบบหยุดได้ชัดขึ้น ส่วนผลปลายทางยังต้องมีหลักฐานตามเส้นทางของมันต่อไป