ข้ามไปยังเนื้อหา
แก่น Kaen
ค้นหา
Ctrl
K
ยกเลิก
เลือกธีม
มืด
สว่าง
อัตโนมัติ
แยก Bounded Context แล้วเชื่อมกลับ — Modular Monolith สู่ Service
จาก Ordering context เดียว สู่ 4 bounded context ที่เชื่อมกันด้วย integration event — 8 บทเรียน ตั้งแต่วาด context map จนถึงแตกเป็น service จริง
เริ่มบทที่ 1
ความคืบหน้า
0 / 8 บทเรียน
เส้นทางการเรียน
หัวข้อที่มีชื่อว่า “เส้นทางการเรียน”
●
01
ทำไมต้องแยก context
เริ่มตรงนี้
Bounded Context
ขอบเขตที่ ubiquitous language และ model หนึ่งชุดมีความหมายคงเส้นคงวา นอกขอบเขตนี้คำเดียวกันอาจหมายถึงคนละอย่าง เช่น 'order' ใน Ordering กับ 'ticket' ใน Kitchen คือมุมมองของสิ่งเดียวกันคนละ model
Subdomain
ส่วนย่อยของปัญหาทางธุรกิจ (problem space) แบ่งเป็น core (Ordering — จุดได้เปรียบ), supporting (Kitchen, Delivery — จำเป็นแต่ไม่ใช่จุดขาย), generic (Payment — ใช้ของสำเร็จรูปได้) คนละเรื่องกับ Bounded Context ซึ่งเป็นคำตอบ (solution space)
Ubiquitous Language
ภาษากลางที่ทีมและผู้เชี่ยวชาญธุรกิจใช้ร่วมกัน แต่ยึดกับ context เดียว ไม่ใช่ทั้งองค์กร — คำว่า order สื่อความหมายต่างกันในปากคนของ Ordering, Kitchen และ Delivery แม้พูดถึงเหตุการณ์เดียวกัน
○
02
Context Map — วาดความสัมพันธ์
Context Map
แผนภาพที่วาดทุก Bounded Context และความสัมพันธ์ระหว่างกัน (Partnership, Customer-Supplier, ACL ฯลฯ) ให้เห็นภาพรวมว่าใครเชื่อมกับใคร และใครมีอำนาจกำหนด model มากกว่า (upstream/downstream)
Partnership
ความสัมพันธ์ที่2 context ต้องประสานแผนงานร่วมกันเพราะพึ่งพากันสองทาง หากฝ่ายใดล้มเหลวอีกฝ่ายก็ไปต่อไม่ได้ จึงต้องปล่อย feature ที่เกี่ยวข้องกันพร้อมกันเสมอ
Customer-Supplier
ความสัมพันธ์ต้นน้ำ-ปลายน้ำที่ทีมต้นน้ำ (supplier) รับความต้องการของทีมปลายน้ำ (customer) เข้าไปวางแผนด้วย เช่น Ordering (supplier) ออกแบบ event ให้ Kitchen (customer) ใช้ได้จริง
Conformist
ทีมปลายน้ำยอม 'ตามใจ' model ของทีมต้นน้ำทั้งหมดโดยไม่แปลภาษา ใช้เมื่อทีมต้นน้ำไม่มีแรงจูงใจจะรองรับเรา แลกกับความเสี่ยงที่ model ภายนอกจะรั่วเข้ามา
Anti-Corruption Layer (ACL)
ชั้นแปลภาษา/model ที่กั้นระหว่าง context สองอัน ไม่ให้ model ของอีกฝ่ายรั่วเข้ามาปนเปื้อน เช่น PaymentGatewayAdapter ที่แปล Money/Order ของ Ordering เป็น DTO ของผู้ให้บริการชำระเงินภายนอก
Open Host Service (OHS)
ทีมต้นน้ำเปิดโพรโทคอล/API ที่นิยามไว้ชัดเจนให้ผู้บริโภคหลายฝ่ายเชื่อมต่อได้โดยไม่ต้องเจรจาทีละคู่ เหมาะเมื่อมีหลาย context ต้องพึ่งพา service เดียวกัน
Published Language
ภาษากลางที่เอกสารไว้ชัดเจนสำหรับแลกเปลี่ยนข้อมูลข้าม context เช่น สัญญา (contract) ของ integration event อย่าง FoodReadyIntegrationEvent มักมาคู่กับ Open Host Service
Separate Ways
ตัดสินใจไม่เชื่อม2 context เข้าด้วยกันเลย เพราะต้นทุนการเชื่อมต่อไม่คุ้มกับประโยชน์ ปล่อยให้แต่ละฝ่ายแก้ปัญหาของตัวเองแบบง่าย ๆ
○
03
แยก Ordering เป็น module มีขอบเขต
Modular Monolith
สถาปัตยกรรมที่ทุก context เป็น module/assembly แยกกันชัดเจนแต่รันในโพรเซสเดียวกัน แต่ละ module เป็นเจ้าของ model/schema ของตัวเอง ห้ามอ้างอิง domain type ข้าม module ตรง ๆ เชื่อมได้เฉพาะผ่าน integration event หรือ query ที่แปลแล้ว
Architecture Tests
test อัตโนมัติที่ตรวจกฎโครงสร้าง code เช่น Kitchen ต้องไม่ reference ชนิดของ Ordering โดยตรง ด้วยเครื่องมืออย่าง NetArchTest/ArchUnitNET ทำให้ขอบเขตของ modular monolith ไม่หลุดโดยไม่มีใครรู้
Bounded Context
ขอบเขตที่ ubiquitous language และ model หนึ่งชุดมีความหมายคงเส้นคงวา นอกขอบเขตนี้คำเดียวกันอาจหมายถึงคนละอย่าง เช่น 'order' ใน Ordering กับ 'ticket' ใน Kitchen คือมุมมองของสิ่งเดียวกันคนละ model
○
04
Integration Event ใน process เดียว
Integration Event
event ที่สื่อสาร 'ข้าม' Bounded Context เป็น contract แบบ serializable ที่มีแต่ id/ค่าพื้นฐาน (ไม่มี domain object) เช่น OrderConfirmedIntegrationEvent(Guid OrderId, DateTimeOffset ConfirmedAt) ต่างจาก Domain Event ที่อยู่ในโพรเซสเดียว
Domain Event
สิ่งที่เกิดขึ้นแล้วภายใน context เดียว ทำงาน in-process และอ้างอิง domain object ได้ตรง ๆ เช่น OrderConfirmed ภายใน Ordering — เมื่อจะส่งข้ามขอบเขต context ต้องแปลงเป็น Integration Event ก่อนเสมอ ห้ามส่ง domain event ข้ามไปตรง ๆ
○
05
Anti-Corruption Layer — แปลภาษาที่ขอบ
Anti-Corruption Layer (ACL)
ชั้นแปลภาษา/model ที่กั้นระหว่าง context สองอัน ไม่ให้ model ของอีกฝ่ายรั่วเข้ามาปนเปื้อน เช่น PaymentGatewayAdapter ที่แปล Money/Order ของ Ordering เป็น DTO ของผู้ให้บริการชำระเงินภายนอก
Published Language
ภาษากลางที่เอกสารไว้ชัดเจนสำหรับแลกเปลี่ยนข้อมูลข้าม context เช่น สัญญา (contract) ของ integration event อย่าง FoodReadyIntegrationEvent มักมาคู่กับ Open Host Service
○
06
Outbox + ความเชื่อถือได้ของ event
Dual-write Problem
ปัญหาที่เกิดเมื่อ code ต้องเขียนลงสองที่ให้สำเร็จพร้อมกัน (บันทึกลง DB + ส่ง event) แต่ทั้งสองไม่ได้อยู่ใน transaction เดียวกัน — ถ้าล่มระหว่างกลาง อาจบันทึกสำเร็จแต่ event หาย หรือ event ออกไปแต่ DB ไม่บันทึก
Transactional Outbox
pattern แก้ dual-write problem: เขียน event ลงตาราง outbox ใน transaction เดียวกับข้อมูลจริง แล้วให้ relay แยกต่างหากส่ง event ออกไปหลัง commit เท่านั้น — WolverineFx ทำสิ่งนี้ให้บน EF Core ผ่าน IDbContextOutbox<T>
Idempotent Consumer
ผู้รับ event ที่ออกแบบให้ประมวลผลข้อความเดิมซ้ำได้โดยผลลัพธ์เหมือนเดิม (ไม่ใช่ผลซ้ำซ้อน) จำเป็นเพราะ at-least-once delivery การันตีแค่ 'ส่งถึงอย่างน้อยหนึ่งครั้ง' ไม่ใช่ 'พอดีหนึ่งครั้ง'
At-least-once Delivery
การรับประกันของ message broker ว่าข้อความจะถูกส่งถึงผู้รับ 'อย่างน้อยหนึ่งครั้ง' แต่อาจซ้ำได้ในกรณีล่ม/retry ผู้รับจึงต้องเป็น idempotent consumer เพื่อรองรับความซ้ำนี้
○
07
เมื่อไรควร (และไม่ควร) แตกเป็น service
Modular Monolith
สถาปัตยกรรมที่ทุก context เป็น module/assembly แยกกันชัดเจนแต่รันในโพรเซสเดียวกัน แต่ละ module เป็นเจ้าของ model/schema ของตัวเอง ห้ามอ้างอิง domain type ข้าม module ตรง ๆ เชื่อมได้เฉพาะผ่าน integration event หรือ query ที่แปลแล้ว
Message Broker
ตัวกลางที่รับ-ส่งข้อความระหว่าง context ที่แยกเป็น service จริงแล้ว เช่น RabbitMQ ทำให้ผู้ส่งไม่ต้องรู้จักผู้รับโดยตรง (decoupled) — เป็นสิ่งที่ต้องมีเมื่อแตกจาก modular monolith ไปเป็น distributed service
○
08
แตก 1 context ออกเป็น service จริง
Message Broker
ตัวกลางที่รับ-ส่งข้อความระหว่าง context ที่แยกเป็น service จริงแล้ว เช่น RabbitMQ ทำให้ผู้ส่งไม่ต้องรู้จักผู้รับโดยตรง (decoupled) — เป็นสิ่งที่ต้องมีเมื่อแตกจาก modular monolith ไปเป็น distributed service
Integration Event
event ที่สื่อสาร 'ข้าม' Bounded Context เป็น contract แบบ serializable ที่มีแต่ id/ค่าพื้นฐาน (ไม่มี domain object) เช่น OrderConfirmedIntegrationEvent(Guid OrderId, DateTimeOffset ConfirmedAt) ต่างจาก Domain Event ที่อยู่ในโพรเซสเดียว