บทที่ 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 lifecycle
  • customer — Customer model ใน context นั้น

ใช้ capability/process name เมื่อขอบเขตคือการทำงานต่อเนื่องหรือชุด policies:

  • invoicing — capability ออก/ปรับ/เผยแพร่ invoice documents
  • metering — ingest, deduplicate, aggregate usage
  • billing — 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 hub
  • utils — functions ไม่มี concept/owner
  • manager — ไม่บอกว่า orchestrate, decide หรือ persist
  • core — อาจหมายถึง shared kernel, important code, framework หรือ Core Subdomain
  • service — ใช้ได้เป็น 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 ยังไม่ชัด ถาม:

  1. Outcome ใดที่ module รับผิดชอบ
  2. ประโยคธุรกิจใดใช้ชื่อมันเป็น subject
  3. State/lifecycle อะไรอยู่ภายใน
  4. สิ่งใดตั้งใจไม่เป็นเจ้าของ
  5. ใครตอบคำถามเมื่อ 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

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