บทที่ 15 · Part 3 — Organizing the Codebase
Naming Modules with Ubiquitous Language
เลือก invoice หรือ invoicing จากความหมายและ ownership ไม่ใช่กฎ tense พร้อมกำจัดชื่อกำกวมอย่าง utils และ core
Naming Modules with Ubiquitous Language
ทีมถามว่าชื่อ module ต้องเป็น noun หรือ gerund ทำไมมีทั้ง invoice และ invoicing
บางคนจึง rename ทุก package ให้ tense เดียวกัน แม้ความหมายเปลี่ยน ผลคือ billing,
invoicing, invoice ถูกใช้แทนกันและไม่มีใครบอกได้ว่าแต่ละ module เป็นเจ้าของอะไร
DDD ไม่มีกฎ grammatical tense กลาง สิ่งสำคัญคือ Ubiquitous Language และ abstraction level
จบบทนี้คุณจะ
เลือกชื่อ concept หรือ capability ตาม ownership เปรียบเทียบ invoice กับ invoicing
รักษาชื่อให้อยู่ abstraction level เดียว และ refactor common, utils, manager,
core ให้กลายเป็น modules ที่มี purpose ตรวจสอบได้
[!IMPORTANT] วิธีอ่านหลักฐานในบทนี้ คำที่เป็น Definition/Pattern มีแหล่งต้นทางอยู่ใกล้ claim ส่วน decision table, starting structure, checklist และ lab เป็น Heuristic/Trade-off หรือ Course Convention สำหรับฝึกตัดสินใจ ไม่ใช่มาตรฐานสากล เว้นแต่บทจะระบุแหล่งและขอบเขตไว้ชัดเจน
ตั้งชื่อตาม Concept หรือตาม Capability
ใช้ concept noun เมื่อ package เป็นเจ้าของ model/lifecycle ที่มีศูนย์กลางชัด:
charge— Charge state และ operations ที่เกี่ยวข้องrefund— Refund lifecyclecustomer— Customer model ใน context นั้น
ใช้ capability/process name เมื่อขอบเขตคือการทำงานต่อเนื่องหรือชุด policies:
invoicing— capability ออก/ปรับ/เผยแพร่ invoice documentsmetering— ingest, deduplicate, aggregate usagebilling— broader capability ที่อาจเป็น umbrella/discovery container
invoice ไม่ผิด และ invoicing ไม่ดีกว่าโดยธรรมชาติ หาก module เป็น Aggregate/package
เล็กที่ centered on Invoice, invoice อ่านง่าย หาก module รวม issuance workflow, numbering,
adjustment และ publication, invoicing อาจสื่อ capability กว่า
Go package naming แนะนำชื่อสั้น ชัด และไม่ซ้ำกับ exported identifier แบบ stutter ให้ดูจาก consumer code:
invoice.Issue(...)
invoicing.NewService(...)
metering.RecordUsage(...)
เลือกแบบที่ประโยคอ่านตาม language ของทีม
รักษาระดับ Abstraction ให้เท่ากัน
tree นี้ผสมระดับ:
internal/
billing/
invoice/
mysql/
stripe/
customer_manager/
utils/
billing/invoice เป็น business terms, mysql/stripe เป็น technologies,
manager เป็น role กำกวม และ utils ไม่มี owner หากอยู่ sibling level เดียวกัน reader
ไม่รู้ว่าเป็น modules, adapters หรือ platform
ปรับเป็น:
internal/
modules/
invoicing/
metering/
receivables/
adapters/
paymentgateway/stripe/
platform/
clock/
observability/
path นี้เป็น course convention ไม่ใช่มาตรฐาน Go สิ่งที่สำคัญคือระดับและ dependency
ชื่อกว้างที่กำกวม
common— ทุกทีม import แล้วกลายเป็น coupling hubutils— functions ไม่มี concept/ownermanager— ไม่บอกว่า orchestrate, decide หรือ persistcore— อาจหมายถึง shared kernel, important code, framework หรือ Core Subdomainservice— ใช้ได้เป็น role แต่ชื่อ package top-level กำกวมmodel— ไม่บอกว่า domain, persistence, API หรือ read model
rename จาก responsibility เช่น money, tenant, idempotency, paymentgateway
หรือเก็บ helper unexported ใกล้ consumer หากไม่มี reuse จริง
อย่ารีบสร้าง shared/money เพราะ 2 contexts ใช้ amount/currency เหมือนกัน Money semantics
อาจต่างด้าน rounding, allowed currencies หรือ precision หากต้อง share ให้กำหนด Published
Language/Shared Kernel ownership และ change policy
การตั้งชื่อคือบททดสอบ Model
ถ้าทีมตั้งชื่อ module ไม่ได้ อาจเป็นเพราะ boundary ยังไม่ชัด ถาม:
- Outcome ใดที่ module รับผิดชอบ
- ประโยคธุรกิจใดใช้ชื่อมันเป็น subject
- State/lifecycle อะไรอยู่ภายใน
- สิ่งใดตั้งใจไม่เป็นเจ้าของ
- ใครตอบคำถามเมื่อ behavior ผิด
ตัวอย่าง payment อาจหมายถึง payment method, attempt, provider integration หรือ merchant
settlement ให้เปลี่ยนเป็น checkoutpayment, paymentgateway, collections ตาม context
แทนการยอมรับคำกว้าง
แบบฝึกปฏิบัติ: ตั้งชื่อ Folder Tree ใหม่
ให้ tree:
core/
invoice_manager/
customer/
services/
payment/
common/
model/
utils/
integrations/
stripe/
สร้าง glossary statements และ rename ทุก node พร้อมตาราง:
Old name:
New name:
Level: capability | concept | adapter | platform
Owned outcome:
Explicitly does not own:
Example consumer sentence:
Migration/import impact:
ไม่มีเฉลยชื่อเดียว แต่คำอธิบายต้องไม่ใช้คำว่า “standard” อย่างเดียว และ modules siblings ควรอยู่ระดับเดียวกัน
รายการตรวจสอบ
- ชื่อมาจาก glossary และ ownership
- Noun/gerund ถูกเลือกจาก meaning ไม่ใช่ tense rule
- Sibling packages อยู่ abstraction level เดียวกัน
- Technology adapters ไม่ปะปนกับ business modules
- Generic names ถูกแทนด้วย concept หรือย้ายใกล้ consumer
- Shared types มี semantic/change ownership ชัด
- Consumer code อ่านเป็นประโยคที่ไม่ stutter
สรุปบทนี้
ชื่อ module ไม่มี tense บังคับ invoice เหมาะกับ concept-centered boundary ส่วน
invoicing เหมาะกับ capability/process เมื่อภาษาธุรกิจใช้แบบนั้น Naming เป็น modelling
test: ถ้าบอก owner/outcome ไม่ได้ ปัญหาอยู่ที่ boundary มากกว่า grammar
อ่านเพิ่มเติม
- Package names — official Go naming guidance
- DDD Reference — Ubiquitous Language และ Model-Driven Design
- Go Code Review Comments — idiomatic naming และ package/API concerns