บทที่ 4 · Part 1 — Architecture Starts with the Domain

Bounded Contexts and Ownership

หา language, model, policy และ ownership boundary โดยไม่รีบเท่ากับ microservice หรือ database

Bounded Contexts and Ownership

คำว่า Customer ปรากฏใน Checkout, Billing และ Wallet ทีมจึงสร้าง package กลางชื่อ customer และตารางเดียวเพื่อ “ไม่ duplicate” ไม่นาน field status มี 3 ความหมาย Checkout ต้องการ buyer contact, Billing ต้องการ legal/tax profile ส่วน Wallet ต้องการ account eligibility การแก้ model กลางครั้งเดียวทำให้ 3 ทีม deploy พร้อมกัน

จบบทนี้คุณจะ

เสนอ Bounded Context จาก language, model, invariant, lifecycle, data และ ownership อธิบายว่า context ไม่เท่ากับ microservice และใช้ Context Map กับ Anti-Corruption Layer เพื่อออกแบบความสัมพันธ์ระหว่าง Customer, Payment และ Balance models

[!IMPORTANT] วิธีอ่านหลักฐานในบทนี้ คำที่เป็น Definition/Pattern มีแหล่งต้นทางอยู่ใกล้ claim ส่วน decision table, starting structure, checklist และ lab เป็น Heuristic/Trade-off หรือ Course Convention สำหรับฝึกตัดสินใจ ไม่ใช่มาตรฐานสากล เว้นแต่บทจะระบุแหล่งและขอบเขตไว้ชัดเจน

ขอบเขตของความหมาย

DDD Reference นิยาม Bounded Context เป็นขอบเขตที่ model หนึ่งมีความหมายสอดคล้องกัน ภายใน boundary ทีมต้องรู้ว่า term, rule และ lifecycle ใช้แบบใด ภายนอก boundary คำที่สะกดเหมือนกันไม่จำเป็นต้องเป็น object เดียว

ให้ค้นหาหลักฐาน 5 กลุ่ม:

  1. Language — คำเดียวมี definition หรือ statement ต่างกันหรือไม่
  2. Invariant — กฎใดต้องถูกบังคับพร้อมกันและใครมีอำนาจตัดสิน
  3. Lifecycle — object เกิด เปลี่ยน และสิ้นสุดด้วยเหตุการณ์ชุดเดียวกันหรือไม่
  4. Ownership — ทีมและ business owner ใดตอบคำถามและรับ incident
  5. Change pattern — requirement เปลี่ยนร่วมกันหรือทำให้หลายส่วน deploy พร้อมกันเสมอ

Boundary เป็น hypothesis จนผ่าน scenario หลายแบบ อย่าใช้ org chart เพียงอย่างเดียว เพราะทีมอาจถูกจัดตาม technology และอย่าใช้ database schema เป็นคำตอบ เพราะ schema อาจสะท้อน legacy coupling มากกว่า domain

คำเดียว หลาย Model

เปรียบเทียบ Customer 3 บริบท:

ContextModel สนใจไม่ควรเป็นเจ้าของ
Checkoutbuyer contact, session relationship, consent snapshottax registration lifecycle
Billingbilling profile, invoice address, tax identityWallet account restriction
Walletaccount holder reference, eligibility, account stateinvoice delivery preference

แต่ละ context เก็บ reference หรือ snapshot ที่จำเป็นต่อ decision ของตนได้ โดยต้องระบุ source, freshness และ correction policy การคัดลอก field ไม่เท่ากับ duplicate business logic หาก model มีความหมายและ lifecycle ต่างกัน

Balance ยิ่งต้องระวัง Wallet อาจเป็นเจ้าของ ledger-derived posted/available balance ขณะที่ Portal มี read model สำหรับ display การเรียกทั้งคู่ว่า Balance ไม่ทำให้ Portal มีสิทธิ์อนุมัติ transfer จาก cache ของตน

Decision ต้องกลับไปหา owning context

การอนุมัติ Transfer หรือ Refund ต้องให้ context เจ้าของ authoritative state ตรวจ invariant ณ transaction/concurrency boundary ที่เหมาะสม Snapshot และ read model ใช้แสดงผลหรือเตรียมข้อมูลได้ แต่ห้ามกลายเป็นผู้ตัดสินเงินโดยไม่มี contract ชัด

Bounded Context ไม่ใช่ Deployment Unit

Bounded Context เป็น semantic/model boundary ส่วน module, process, service, repository และ database เป็น implementation/deployment decisions Context 2 อันอาจเริ่มใน modular monolith เดียวโดยมี package/API boundary ชัด ขณะเดียวกัน service หนึ่งที่รวมหลาย model โดยไม่มี language boundary ไม่ได้กลายเป็น context เพียงเพราะ deploy แยก

Microservice Trade-Offs เตือนถึงต้นทุน distribution เช่น remote call, operational complexity และ eventual consistency ดังนั้นคำถาม “ควรแยก service ไหม” ต้องตามหลังคำถาม “model และ ownership แยกหรือยัง”

ใช้ตารางนี้ป้องกันการปนระดับ:

Decisionตัวอย่างเปลี่ยนแยกได้หรือไม่
Model boundaryPayment Attempt กับ Wallet Entryได้
Code modulecheckout, wallet packagesได้
Runtime processbinary เดียวหรือหลาย serviceได้
Data deploymentschema/database เดียวหรือแยกได้ แต่ ownership ต้องชัด
Team ownershipPayments, Wallet teamsได้ตามองค์กร

ทำ Context Map ของความสัมพันธ์

Context Map ไม่ได้มีแค่กล่องและลูกศร ต้องบอก power, contract และ translation รูปแบบจาก Evans เช่น Customer/Supplier, Conformist, Published Language และ Anti-Corruption Layer ช่วยตั้งคำถามว่าฝ่ายใดกำหนด model

ตัวอย่าง Checkout เรียก Payment Integration:

ACL มีหน้าที่แปล vendor lifecycle, identifier และ error ให้เป็นภาษาที่ Checkout เป็นเจ้าของ มันอาจเป็น adapter ใน process เดียว ไม่จำเป็นต้องเป็น service แยก และไม่ควรแอบเป็นเจ้าของ refund policy ซึ่งอยู่ใน domain อื่น

สำหรับ integration แต่ละเส้น บันทึก:

  • Upstream/downstream และ accountable owners
  • Published command/event/query contract
  • Identifier/correlation rule
  • Translation และข้อมูลที่ตั้งใจไม่เผย
  • Freshness, consistency และ failure expectation
  • Versioning/deprecation policy

แบบฝึกปฏิบัติ: ท้าทายสมมติฐาน 3 Boundary

เริ่มจาก candidate contexts Customer, Payment, Wallet แล้วกรอกตาราง:

Candidate boundary:
Language and example statements:
Owned invariants:
Lifecycle:
Authoritative data:
Team / business policy owner:
Public contracts:
Dependencies and relationship pattern:
Counterexample that might merge or split it:
Deployment decision today:

จากนั้นทดสอบด้วย 2 scenario: customer เปลี่ยน legal name หลัง invoice ถูกออก และ payment confirmed แต่ Wallet credit ยังไม่เกิด หาก boundary อธิบายไม่ได้ว่าใครเก็บ historical snapshot ใครแก้ state และใคร reconcile แสดงว่ายังเป็นเพียงชื่อกล่อง

คะแนนแบบฝึกหัด:

  • ผ่าน เมื่อทุก boundary มี language/invariant/owner มากกว่ารายชื่อตาราง
  • ดี เมื่อ relationship มี contract, translation และ failure responsibility
  • ควรทำใหม่ เมื่อกล่องทุกใบเท่ากับ service/team/database โดยไม่มี semantic evidence

รายการตรวจสอบ

  • Boundary มี model และ language ที่สอดคล้องภายใน
  • Shared term ถูกตรวจความหมาย ไม่แชร์ struct โดยอัตโนมัติ
  • Invariant และ authoritative data มี owner เดียวที่ชัด
  • Integration ระบุ contract และ translation
  • Bounded Context ถูกแยกจาก module/service/database decision
  • Boundary hypothesis ถูกท้าด้วย failure และ historical scenarios

สรุปบทนี้

Bounded Context ปกป้องความหมายของ model ไม่ใช่บังคับ topology เมื่อ boundary ชัด เราสามารถเริ่มด้วย module ใน process เดียวแล้วเลือก deployment ตาม scale, failure isolation และทีมภายหลัง Context Map ทำให้ความสัมพันธ์และ translation เป็นข้อตกลงที่ review ได้

อ่านเพิ่มเติม