บทที่ 14 · Part 3 — Organizing the Codebase
Large Multi-Module Codebase: Billing
แบ่ง Billing ด้วย capability, lifecycle, invariant และ ownership พร้อม contracts และคำถาม boundary ที่ยังไม่ปิด
Large Multi-Module Codebase: Billing
Billing repository โตจนมี Invoice, Receipt, Credit Note, Subscription, Customer, Meter, Seller และ Tax ทีมพยายามแบ่ง module ตาม table 1 table ต่อ package ผลคือ use case Issue Invoice ต้อง import 7 packages และไม่มีใครแน่ใจว่าใครเป็นเจ้าของ amount due การแบ่งที่ดีต้องเริ่มจาก capability, lifecycle, invariant และ ownership
จบบทนี้คุณจะ
สร้าง Billing module map แยก subscriptions, metering, invoicing, receivables, receipts และ tax ด้วยหลักฐาน ตัดสินความสัมพันธ์ Invoice/Credit Note และบทบาท Customer/Seller พร้อม public contracts กับ unresolved questions โดยอนุญาตแต่ละ module ใช้ architecture ต่างกัน
[!IMPORTANT] วิธีอ่านหลักฐานในบทนี้ คำที่เป็น Definition/Pattern มีแหล่งต้นทางอยู่ใกล้ claim ส่วน decision table, starting structure, checklist และ lab เป็น Heuristic/Trade-off หรือ Course Convention สำหรับฝึกตัดสินใจ ไม่ใช่มาตรฐานสากล เว้นแต่บทจะระบุแหล่งและขอบเขตไว้ชัดเจน
เริ่มจาก Capability และเส้นทางธุรกิจ
Candidate modules มีคำถามเจ้าของต่างกัน:
| Candidate | Outcome / decision ที่อาจเป็นเจ้าของ |
|---|---|
| Subscriptions | contract lifecycle, plan change, billing cadence |
| Metering | ingest usage, deduplicate, aggregate measurement |
| Invoicing | issue immutable commercial document, line calculation, lifecycle |
| Receivables | outstanding obligation, collection, allocation, ageing |
| Receipts | evidence/record of payment received ตาม policy |
| Tax | tax determination/result integration หรือ snapshot |
ชื่อเหล่านี้ยังเป็น hypotheses ให้ trace journey Usage → Invoice → Collection → Receipt
แล้วดูว่า language, event และ policy เปลี่ยนที่ใด
Invoice และ Credit Note
Credit Note เกี่ยวข้องกับ Invoice แต่คำถามคือ relation แบบใด:
- หาก Credit Note เป็น correction document ใน lifecycle เดียว ใช้ numbering/audit policy ร่วม และ Invoicing owner รับผิดชอบ อาจอยู่ module เดียว
- หาก correction มี workflow/approval/regulatory model แยกและทีมแยก อาจเป็น module ย่อยหรือ boundary อื่น
- ไม่ควร mutate issued Invoice ย้อนหลังเพียงเพราะ database update ง่ายกว่า หาก business policy กำหนด correction document
เอกสารทางบัญชีและภาษีต้องอ้าง policy จริง
คอร์สไม่กำหนดว่า Invoice ต้อง immutable, numbering แบบใด หรือ Credit Note ใช้เมื่อใด ระบบจริงต้องให้ Accounting/Tax/Legal owner ระบุ jurisdiction, เอกสาร, effective date และ audit requirements ก่อนออกแบบ invariant
ใน lab ให้ใช้ invariant สมมติแบบมีป้าย เช่น “เอกสารที่ ISSUED เปลี่ยน line ไม่ได้;
correction สร้าง linked adjustment document” แล้วระบุว่ายังต้อง validate กับ owner
Customer กับ Seller ขึ้นกับ Context
Billing ไม่จำเป็นต้อง own enterprise Customer/Seller master ทั้งหมด สิ่งที่ Invoice ต้องใช้ อาจเป็น immutable billing-party snapshot ณ เวลาออกเอกสาร:
type BillingPartySnapshot struct {
ExternalPartyID string
LegalName string
TaxID string
Address PostalAddress
}
snapshot มี purpose เพื่อ historical document ไม่ใช่ cache สำหรับ authorization Customer master owner ส่ง Published Language หรือ Billing query แล้วเก็บ version ที่ใช้ Seller อาจเป็น legal entity configuration ของ Billing หาก policy/lifecycle อยู่ที่นี่จริง
ถามเสมอ:
- ใครแก้ legal identity และใครรับ correction
- เอกสารเก่าต้องเปลี่ยนตาม master data หรือคง snapshot
- field ใดใช้ display field ใดใช้ decision
- data retention/access owner คือใคร
แผนที่ Module เบื้องต้น
แต่ละเส้นต้องตัดสิน command/event/query และ idempotency ตัวอย่าง InvoiceIssued
ควรมี invoice ID, version/schema, monetary totals/currency และ occurred time ตาม contract
แต่ไม่ควรเผย persistence row ทั้งก้อน
ต่าง Module ต่าง Architecture
- Meter ingestion อาจใช้ Transaction Script + idempotent store เพราะ flow หนักที่ throughput
- Invoicing อาจใช้ Rich Domain Model สำหรับ document lifecycle/line invariants
- Tax อาจเป็น Ports & Adapters รอบ external engine และ snapshot result
- Receivables อาจใช้ application workflow + domain policies
- Portal query ใช้ read model ไม่ต้อง hydrate Aggregates
ความสม่ำเสมอที่ต้องรักษาคือ boundary contract, observability, security และ engineering standards ไม่ใช่บังคับ tactical pattern เดียวทุก module
Contract ที่ Module เปิดให้ใช้ และ Data Ownership
สร้าง registry:
Module: Invoicing
Owns: invoice lifecycle, line/totals policy, document identifiers
Consumes: BillingSchedule, UsageFact, TaxResult, BillingPartySnapshot
Publishes: InvoiceIssued, InvoiceAdjusted
Authoritative tables: billing.invoices, billing.invoice_lines
Does not own: collection attempt, provider payment, customer master
Architecture: rich model + application services + MySQL repository
Open questions: correction rules and tax owner approval
ห้าม module อื่นเขียน table ของ Invoicing โดยตรง แม้ใช้ MySQL instance เดียวกัน enforce ผ่าน DB grants/schema, package visibility และ tests ตามความเสี่ยง
แบบฝึกปฏิบัติ: หา Billing Boundary
ทำ module cards สำหรับ candidates ทั้ง 6 แต่ละ card มี language, commands, events, invariants, owner, authoritative data, architecture และ open questions จากนั้น trace:
- late usage มาหลัง Invoice issued
- partial payment 2 ครั้ง
- customer เปลี่ยน legal name
- collection succeeded แต่ receipt creation ล้มเหลว
หากคำตอบต้องแก้ table ข้าม module ให้ทบทวน contract/ownership อย่ารีบ merge module เพราะ workflow ข้าม boundary เป็นเรื่องปกติ
รายการตรวจสอบ
- Module แบ่งตาม capability/lifecycle/invariant ไม่ใช่ table
- Invoice/Credit Note relation มี policy evidence หรือ open question
- Customer/Seller ระบุ owner และ snapshot semantics
- แต่ละ contract บอก fact/intent และ versioning responsibility
- Module ใช้ architecture ต่างกันได้พร้อมเหตุผล
- Direct table writes ข้าม owner ถูกห้าม
- Accounting/tax assumptions มี owner/date ไม่ถูกทำเป็น universal rule
สรุปบทนี้
Billing ใหญ่ควรถูกแบ่งด้วยภาษา กฎ lifecycle และ ownership รายชื่อ noun เป็นเพียง candidate map Invoice, Receipt และ Credit Note อาจสัมพันธ์กันแต่ไม่จำเป็นต้องเป็น model เดียว Customer/Seller มักต้องแยก master ownership กับ Billing snapshot และแต่ละ module เลือก code architecture ตาม force ของตัวเอง
อ่านเพิ่มเติม
- DDD Reference — Bounded Context, Context Map และ Published Language
- Analysis Patterns — domain modelling vocabulary รวม accounting concepts
- Accounting Patterns draft — จุดเริ่มศัพท์ Account/Entry/Transaction ไม่ใช่ข้อกำหนดบัญชีแทน owner
- Enterprise Integration Patterns — messaging contracts และ translation