บทที่ 6 · Part 2 — Choosing Code Architecture
Architecture Decision Heuristics
ประเมิน complexity, change, consistency, integration, risk และทีม เพื่อเลือก architecture แบบมี exit criteria
Architecture Decision Heuristics
หลังจำแนก Subdomain ทีมมักสร้างสูตรว่า Core ใช้ Hexagonal, Supporting ใช้ Layered และ Generic ใช้ CRUD สูตรนี้ดูตัดสินใจง่ายแต่ละเลย module ที่เป็น Generic และมี security risk สูง หรือ Supporting integration ที่เปลี่ยนตาม provider บ่อย Architecture ต้องตอบแรง หลายแกน ไม่ใช่ label เดียว
จบบทนี้คุณจะ
ประเมิน business complexity, change, consistency, integration volatility, risk, ownership, codebase size และ operational independence เลือก starting pattern พร้อม บันทึก trade-off และ signal ที่บังคับให้ทบทวนโดยไม่อ้าง decision matrix เป็นกฎสากล
[!IMPORTANT] วิธีอ่านหลักฐานในบทนี้ คำที่เป็น Definition/Pattern มีแหล่งต้นทางอยู่ใกล้ claim ส่วน decision table, starting structure, checklist และ lab เป็น Heuristic/Trade-off หรือ Course Convention สำหรับฝึกตัดสินใจ ไม่ใช่มาตรฐานสากล เว้นแต่บทจะระบุแหล่งและขอบเขตไว้ชัดเจน
ประเมินแรงผลักดันของการออกแบบ
ใช้คำถาม 8 แกนก่อนพูดชื่อ pattern:
| Force | ต่ำ | สูง |
|---|---|---|
| Business-rule complexity | validate แล้วบันทึก | state machine, policy หลายชุด, exception มาก |
| Rate of change | schema/config เปลี่ยนน้อย | experiment และ policy เปลี่ยนทุก sprint |
| Consistency | field เดียว atomic | หลาย invariant ต้องเห็น state เดียวกัน |
| Integration volatility | dependency คงที่ | vendor/lifecycle/error เปลี่ยนและมี ambiguous result |
| Security/compliance risk | ข้อมูลทั่วไป | เงิน, identity, tenant isolation, audit |
| Ownership | ทีมเดียวชัด | หลายทีมเปลี่ยน contract พร้อมกัน |
| Codebase size | flow เดียวอ่านจบ | หลาย capability และ dependency cycle |
| Operational independence | scale/failure profile เดียว | ต้อง isolate, scale หรือ release แยก |
อย่าให้คะแนนตัวเลขแล้วรวมเป็นผลลัพธ์อัตโนมัติ ตารางมีไว้เปิดบทสนทนา ตัวอย่าง module ที่ rule complexity ต่ำแต่ provider volatility สูงอาจยังใช้ Transaction Script ภายใน พร้อม Ports & Adapters ที่ integration boundary โดยไม่ต้องสร้าง Rich Domain Model
ตัดสินใจมากกว่า 1 ระดับ
หนึ่ง module มีหลาย decision พร้อมกันได้:
- Domain logic pattern: Transaction Script หรือ Domain Model
- Application boundary: function โดยตรง, Service Layer หรือ ports/use cases
- Persistence: SQL ใกล้ use case, Repository/Data Mapper หรือ specialized query
- Integration: direct library call หรือ adapter/Anti-Corruption Layer
- Code organization: ไฟล์เดียว, feature slice หรือ roles ภายใน slice
- Deployment: modular monolith หรือ service แยก
เช่น Notification Preference อาจใช้ Transaction Script, feature slice และ SQL ตรง ขณะที่คุยกับ email provider ผ่าน adapter เพราะ dependency ภายนอก unstable การเลือก adapter ไม่ได้บังคับให้ domain model ซับซ้อน
Heuristic สำหรับเริ่มต้น
ตารางต่อไปเป็น heuristic ของคอร์ส ไม่ใช่ mapping จาก Evans หรือ Fowler:
| Situation | Starting architecture | สิ่งที่ยังต้องพิสูจน์ |
|---|---|---|
| CRUD/use case เรียบง่าย ทีมเดียว | Transaction Script ใน business slice | duplication และ rule leakage ยังต่ำ |
| Flow ชัดแต่มี transport/persistence หลายแบบ | local Layered Architecture | layer มี responsibility จริง |
| State/rule/lifecycle ซับซ้อน | Rich Domain Model + application boundary | Aggregate ไม่ใหญ่และ invariant จริง |
| External provider เปลี่ยนหรือผลลัพธ์กำกวม | Ports & Adapters รอบ integration | port เป็นของ consumer ไม่ใช่ vendor clone |
| หลาย capability ใน repository เดียว | slice-first modular structure | public contract และ import direction |
| ต้อง scale/isolate/release แยกจริง | พิจารณา separate deployable | distributed cost และ ownership พร้อม |
Learning Domain-Driven Design เชื่อม business logic patterns กับลักษณะ Subdomain แต่การรวม security, team และ operations เป็น synthesis ที่ architect ต้องทำตามบริบท
บันทึกเหตุผลและเกณฑ์เปลี่ยนแนวทาง
Decision ที่ดีต้องบอกทั้งเหตุผลวันนี้และหลักฐานที่จะทำให้เปลี่ยน:
Module: Notification Preference
Business outcome: customer controls marketing channels
Forces: low rule complexity, medium compliance impact, one team
Decision: Transaction Script inside notification slice
Accepted cost: some SQL/application coupling
Guardrails: security alerts are a separate policy; tenant-scoped queries
Revisit when: three use cases duplicate the same rule, lifecycle branches,
or another channel needs the same invariant
Owner / date: Customer Platform / 2026-08-08
Exit criteria ป้องกัน 2 ด้าน: ไม่ over-engineer วันนี้ และไม่ปล่อย pattern เดิมต่อไป เพราะ sunk cost สัญญาณควรวัดได้ เช่น change เดียวแตะ 5 package, rule เดียวถูก copy 3 จุด, incident เกิดจาก vendor type รั่ว หรือทีมต้อง release ร่วมกันทุกครั้ง
เปรียบเทียบ 3 Module
การตั้งค่าการแจ้งเตือน
Rules น้อย transaction เดียว integration ไม่สำคัญต่อ decision path เริ่มจาก Transaction Script และ concrete store ได้ แต่แยก security notification จาก marketing เพราะความหมายต่างกัน
การอนุมัติ Refund
มี refundable amount, previous refunds, state transition, maker-checker, concurrency และ provider ambiguity เหมาะกับ Rich Domain Model สำหรับ invariant ร่วมกับ application port และ datastore concurrency controls
Adapter สำหรับ Payment Provider
Business rule ภายในอาจน้อย แต่ error/lifecycle/API version ผันผวน จึงลงทุนที่ translation boundary, contract tests, idempotency และ observability มากกว่า Entity hierarchy
Risk ไม่ได้สั่งให้ใช้ pattern ใด pattern หนึ่ง
เมื่อเงินหรือสิทธิ์ได้รับผลกระทบ สิ่งที่ต้องมีคือ authoritative decision path, concurrency control, audit, deny-by-default และ owner ของ policy Rich Domain Model อาจช่วยแสดง invariant แต่ไม่ทดแทน database/security controls
แบบฝึกปฏิบัติ: Architecture Decision Matrix
ให้เลือก 4 module: notification, refund, provideradapter, portalquery
แล้วกรอกคะแนน Low/Medium/High/Unknown พร้อม evidence ต่อทั้ง 8 force จากนั้นเขียน:
- starting domain logic pattern
- integration/persistence seams ที่จำเป็น
- structure วันนี้
- สิ่งที่ตั้งใจไม่ทำ
- three reevaluation signals
- owner และ review date
คะแนนแบบฝึกหัด:
- ผ่าน เมื่อ decision อ้าง force มากกว่า Subdomain type
- ดี เมื่อ module เดียวเลือก pattern ต่างระดับกันและมี exit criteria วัดได้
- ควรทำใหม่ เมื่อ matrix รวมคะแนนเป็นสูตรหรือเลือก architecture จากความนิยม
รายการตรวจสอบ
- ประเมิน complexity, change, consistency, integration และ risk แยกกัน
- Subdomain type เป็น input ไม่ใช่ selector เดียว
- Domain logic, application, structure และ deployment เป็นคนละ decision
- Decision ระบุ accepted cost และสิ่งที่ไม่ทำ
- Exit criteria สังเกตได้จาก code/change/incident
- Policy ที่กระทบเงินหรือสิทธิ์มี owner และวันที่
สรุปบทนี้
Architecture ที่พอดีเริ่มจาก forces แล้วเลือก pattern ทีละระดับ Heuristic ช่วยตั้งคำถาม แต่ไม่ควบคุมคำตอบ เอกสาร decision พร้อม exit criteria ทำให้เริ่มเรียบง่ายได้โดยไม่หลง ติดอยู่กับโครงสร้างเดิมเมื่อ domain เปลี่ยน
อ่านเพิ่มเติม
- Learning Domain-Driven Design — business logic และ architecture patterns
- Domain Logic and SQL — trade-off ของ Transaction Script และ Domain Model
- Fundamentals of Software Architecture — architecture characteristics และ trade-off analysis