บทที่ 30 · Part 6 — Adoption and Evolution

Brownfield Adoption and Operating Model

นำ DDD เข้า legacy system ทีละ seam พร้อม cadence, ownership, metrics และเกณฑ์หยุดหรือขยายผล

Brownfield Adoption and Operating Model

องค์กรส่วนใหญ่ไม่ได้เริ่ม DDD บน whiteboard ว่าง Billing monolith อาจมี 800 tables, batch jobs ที่ไม่มี owner, shared Customer model และ partner files ซึ่งห้ามหยุด การประกาศ rewrite ตาม Bounded Context ใหม่ทั้งระบบทำให้ต้องเดาทั้ง domain และ migration พร้อมกัน แนวทางที่ปลอดภัยกว่าคือใช้ DDD หา seam รอบ change ที่มีคุณค่า แล้วเปลี่ยนทีละ slice พร้อม feedback

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

สร้าง As-Is/To-Be Context Map เลือก pilot จาก business pain ใช้ Anti-Corruption Layer และ Strangler Fig แยก model ทีละเส้น วาง migration ที่ rollback ได้ และสร้าง operating cadence, ownership กับ metrics ที่ทำให้ Ubiquitous Language และ boundary มีชีวิตหลัง workshop

[!IMPORTANT] Brownfield ไม่ใช่ชื่อเชิงลบ Legacy system มักเก็บกฎและ exception ที่ทำให้ธุรกิจอยู่รอด การไม่มี test หรือ documentation ไม่ได้แปลว่าไม่มี model ก่อนย้าย behavior ต้องหา observable evidence, ผู้ทำงานจริง และ characterization tests เพื่อแยก “ quirk ที่เลิกได้” จาก “ policy ที่ยังมีผล”

เริ่มจาก Business Change ไม่ใช่ Rewrite Target

pilot ที่ดีมี 5 คุณสมบัติ:

  1. business outcome มี owner และกำลังจะเปลี่ยนจริง
  2. pain สังเกตได้ เช่น lead time, correction, handoff หรือ incident
  3. scope มี start/end scenario และไม่ต้องย้ายทั้งฐานข้อมูลก่อนส่งคุณค่า
  4. ทีม cross-functional เข้าถึง Domain Expert และ production feedback
  5. failure/rollback จำกัดวงได้

ตัวอย่าง Partial Refund เหมาะกว่าหัวข้อ “แยก Payments Monolith” เพราะมี actor, policy, lifecycle และ outcome ให้ตรวจ สามารถสร้าง boundary ใหม่รอบ request/approval ก่อน แล้วค่อยแปล ไปยัง provider execution กับ legacy Wallet โดยไม่อ้างว่า boundary ทั้งระบบถูกค้นพบครบแล้ว

ทำ As-Is Context Map อย่างซื่อสัตย์

As-Is map ต้องแสดงสิ่งที่เกิดจริง แม้จะไม่น่าภูมิใจ:

  • model/term ที่แต่ละทีมใช้และจุดที่ความหมายชนกัน
  • table, file, API และ job ที่แต่ละฝ่ายอ่าน/เขียนจริง
  • implicit owner, approval และ manual repair path
  • upstream/downstream power และ release dependency
  • authoritative/derived/unknown data พร้อม freshness
  • incident, change frequency และ sensitive/compliance concerns

อย่าเปลี่ยนชื่อ legacy module ให้เป็น candidate context แล้วถือว่างาน modelling เสร็จ Shared table ที่ 5 teams เขียนอาจเป็น coupling ที่ต้องแก้ หรืออาจเป็น operational database ของ product เดียว หลักฐานต้องมาจาก language, invariant, lifecycle และ change pattern

To-Be map ควรแสดง next viable state ไม่ใช่ภาพปลายทาง 3 ปี เช่น Refund Approval เป็น module ใหม่ที่เป็นเจ้าของ policy, legacy Payments ยัง execute provider call ผ่าน ACL และ Wallet ยังรับ คำสั่งเดิมโดยมี idempotency key แผนต้องระบุสิ่งที่ยัง intentionally coupled

สร้าง Seam ด้วย Anti-Corruption Layer

ACL แปล legacy/vendor model เป็นภาษาของ context ใหม่:

Portal request
  -> Refund application command
  -> Refund Approval model
  -> ExecuteRefund port
  -> Legacy Payments ACL
  -> legacy procedure / provider

ACL ไม่ควรเป็น pass-through DTO mapper อย่างเดียว มันต้องรับผิดชอบความต่างของ identity, status, amount/currency, error และ ambiguous outcome ตัวอย่าง legacy ค่า S อาจหมายถึง “ส่งไป provider แล้ว” ไม่ใช่ RefundSucceeded ใน model ใหม่ ACL ต้อง map เป็น outcome ที่ซื่อสัตย์ เช่น SUBMITTED หรือ UNKNOWN และเปิด reconciliation path

Bubble Context โตตาม evidence ไม่ใช่ตามจำนวน tables ระหว่างทาง legacy ยังเป็น authority ตามที่ระบุ และการย้าย write ownership เกิดหลัง reconciliation, rollback และ support พร้อมเท่านั้น

ใช้ branch-by-abstraction ภายใน process เมื่อเปลี่ยน implementation หลัง interface ได้โดยไม่ต้อง route traffic ระหว่างระบบ ใช้ Strangler Fig เมื่อสามารถ route capability/request บางส่วนไประบบใหม่ แล้วค่อยลดของเก่า AWS Strangler Fig Guidance เตือนว่าการแยกก่อนเข้าใจ domain มีต้นทุน และยก DDD/EventStorming เป็นเครื่องมือหา boundary

Migration Slice ที่พิสูจน์ได้

วาง migration เป็นลำดับเล็ก:

  1. Observe — trace scenario, capture representative inputs/outcomes และทำ glossary
  2. Characterize — เขียน tests รอบ behavior ที่ต้องรักษา รวม duplicate, timeout และ manual path
  3. Introduce seam — port/ACL ครอบ call เดิมโดย behavior ไม่เปลี่ยน
  4. Model one decision — ย้าย invariant/policy เดียวเข้า module ใหม่พร้อม owner/source/date
  5. Shadow or compare — คำนวณผลใหม่โดยยังไม่เปลี่ยนเงิน แล้วเทียบ mismatch ที่ explain ได้
  6. Route a safe cohort — เปิดตาม tenant/use case พร้อม kill switch และ monitoring
  7. Move authority — ย้าย write ownership หลัง reconciliation, rollback และ support พร้อม
  8. Retire — หยุด writer/read path เก่า แล้วลบหลัง retention/consumer evidence ครบ

ห้ามใช้ Dual Write แบบไม่มี Recovery Contract

การเขียน database เก่ากับใหม่แยกกันอาจสำเร็จเพียงข้างเดียว สำหรับเส้นทางเงินต้องกำหนด source of truth ในแต่ละ phase, stable operation identity, outbox/inbox หรือ repair mechanism, reconciliation owner และ rollback behavior การย้าย authority ต้องได้รับการอนุมัติจากเจ้าของ financial/control policy พร้อมหลักฐานและวันที่

Backfill ไม่ควรเดา current state จาก display cache และไม่ควร rewrite historical facts เพื่อให้ schema ใหม่สวยขึ้น กำหนด cutoff, snapshot semantics, late-arriving data, checksum และ exception queue ให้ Operations ตรวจได้

แยก Module ก่อนแยก Deployable

การสร้าง in-process boundary ช่วยทดสอบ language, public API และ data ownership โดยยังไม่มี network failure เมื่อ module มี contract ชัดจึงค่อยถามว่า independent deployment ให้คุณค่าหรือไม่:

EvidenceModule เดียวอาจพอSeparate deployable อาจคุ้ม
Teamทีมเดียว release พร้อมกันowner/cadence แยกและ coordination วัดได้
Scaleprofile ใกล้กันworkload/scaling ต่างมาก
Failureshared failure budget ยอมรับได้ต้อง isolate ตาม SLO/risk
Security/Dataprocess เดียวคุม access ได้trust/network/data boundary ต้องแยก
Consistencylocal transaction สำคัญeventual workflow มี contract/repair พร้อม

กรณี DOMA ของ Uber เกิดหลัง service landscape ใหญ่มากและมุ่งจัด dependency ผ่าน domains/gateways/layers มันเป็นหลักฐานว่าการแตก service ไม่ได้ลบ complexity แต่ย้าย complexity ไปที่ relationship บริษัทเล็กจึงไม่ควรใช้ topology นั้นโดยไม่มีแรงเดียวกัน

Operating Model ที่ทำให้ Model มีชีวิต

DDD ล้มเหลวได้แม้ workshop แรกดี หาก glossary ไม่เปลี่ยนพร้อม policy หรือ context contract ไม่มี owner สร้าง cadence เท่าที่จำเป็น:

Cadenceผู้ร่วมคำถามArtifact ที่ update
ต่อ featureProduct + Domain Expert + Engineerlanguage/invariant/exception เปลี่ยนไหมscenario, examples, code/tests
รายเดือนcontext ownerscontract, dependency, hotspot ใด driftcontext registry/map
รายไตรมาสbusiness + architecture + finance/riskstrategy หรือ Core classification เปลี่ยนไหมsubdomain portfolio, investment memo
หลัง incidentOps/SRE + owning contextmodel หรือ boundary ซ่อน failure ใดrecovery scenario, ADR, tests

องค์กรเล็กอาจรวมทุก cadence ใน product review เดียว องค์กรใหญ่ใช้ guild/enabling team เพื่อช่วย facilitate และสร้างเครื่องมือ แต่ central DDD team ไม่ควรเป็นเจ้าของ model แทน stream-aligned team Team Topologies แยก facilitating interaction ออกจาก ownership ระยะยาว ซึ่งใช้เป็น heuristic ที่ดีสำหรับ DDD enabling function

Context registry ขั้นต่ำเก็บ:

Context / business purpose:
Business and technical owners:
Ubiquitous Language link:
Owned decisions and authoritative data:
Public commands, events and queries:
Upstream/downstream relationship:
Policy sources and effective dates:
Runtime/data deployment today:
Known debt and reevaluation date:

Registry ไม่ใช่ canonical enterprise model แต่เป็น index ไปยัง model ที่ context เป็นเจ้าของ

วัด Flow, Correctness และ Learning

อย่าวัด adoption ด้วยจำนวน workshops, Aggregates หรือ services ใช้ balanced evidence:

  • Flow: lead time ของ policy change, handoff/wait time, teams touched ต่อ change
  • Correctness: escaped domain defects, financial corrections, duplicate/unknown outcomes
  • Independence: cross-context data writes, coordinated releases, contract-breaking changes
  • Learning: policy questions ที่ได้ owner ก่อน code, glossary conflicts ที่ resolve ด้วย examples
  • Operations: time to identify owning context และ time to reconcile incomplete journey

สร้าง baseline ก่อน pilot และดูแนวโน้มหลายรอบ อย่าให้ metric เดียวเป็น target จนทีม game เช่น ลด teams touched ด้วยการซ่อน dependency หรือปิด incident เป็นหลาย ticket สรุป causality อย่างถ่อมตัว: deployment pipeline, staffing หรือ product scope อาจเปลี่ยนพร้อมกัน

Kill, Simplify และ Expand Criteria

  • Kill: Domain Expert ไม่พร้อม, outcome ไม่มี owner, pilot ไม่ได้แก้ priority จริง
  • Simplify: artifact ไม่ถูกใช้, model มี CRUD เป็นหลัก, abstraction เพิ่ม lead time
  • Continue: language conflict ลด, invariant อยู่จุดเดียว, correction/coordination ลดด้วยหลักฐาน
  • Expand: ทีมอื่นมี pain คล้ายกันและ pilot มี facilitator/owner ที่ถ่ายโอนได้

การหยุด practice ที่ไม่คุ้มเป็นการใช้ DDD อย่างมีวินัย ไม่ใช่ความล้มเหลว Supporting Subdomain อาจกลับไปใช้ Transaction Script หรือ SaaS ได้เมื่อ strategy เปลี่ยน

แบบฝึกปฏิบัติ: Brownfield Pilot Plan

ใช้ Partial Refund สร้างแผน 8 สัปดาห์:

Business pain and baseline:
As-Is model, owner, data and repair path:
Candidate context and smallest decision to move:
ACL translations and deliberately unsupported semantics:
Characterization and invariant tests:
Authority per migration phase:
Shadow/cohort/kill-switch design:
Reconciliation and rollback owner:
Flow/correctness/learning evidence:
Review date and kill/simplify/expand criteria:

ทดสอบแผนด้วย provider timeout หลัง legacy สำเร็จแต่ก่อนระบบใหม่บันทึกผล, duplicate callback, policy version เปลี่ยนกลางทาง และ rollback หลังมี operation ใหม่ 1 รายการ หาก source of truth หรือผู้ซ่อมไม่ชัด แผนยังไม่พร้อมย้าย authority

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

  • pilot เริ่มจาก business outcome และ observed pain
  • As-Is map ซื่อสัตย์ต่อ shared writes และ manual paths
  • To-Be map เป็น next viable state ไม่ใช่ภาพ rewrite ทั้งระบบ
  • ACL แปล semantic difference ไม่ใช่เพียง rename field
  • migration มี authority, reconciliation, rollback และ kill switch ทุก phase
  • module boundary ถูกพิสูจน์ก่อน network boundary เมื่อทำได้
  • glossary/context registry มี owner และ cadence
  • metrics วัด flow, correctness และ learning โดยมี baseline
  • มีเงื่อนไข kill/simplify/expand ที่ตกลงล่วงหน้า

สรุปบทนี้

การนำ DDD เข้า brownfield ที่ปลอดภัยเริ่มจาก scenario ซึ่งธุรกิจต้องเปลี่ยน ทำ model และ seam รอบ decision เล็ก ใช้ ACL ปกป้องภาษา ย้าย authority ทีละ phase และมี recovery ทุกขั้น หลังส่งมอบ ต้องมี owner, cadence และ evidence ให้ model เปลี่ยนพร้อมธุรกิจ มิฉะนั้น Context Map จะกลายเป็น ภาพอดีตอีกชุดหนึ่ง

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