บทที่ 5 · Part 1 — Architecture Starts with the Domain
Enterprise Fintech Landscape
สร้างแผนที่สมมติฐานของ Wallet, Checkout, Billing, Payment Integration และ Web Portal อย่างมีหลักฐาน
Enterprise Fintech Landscape
ในบริษัทเดียวกันคำว่า Product, Domain, Service และ Repository มักถูกใช้แทนกัน Wallet เป็นชื่อในหน้า website, เป็นทีม, เป็น Git repository และเป็น runtime service พร้อมกัน เมื่อวาด architecture diagram ทุกกล่องจึงดูเหมือนเป็น boundary ที่พิสูจน์แล้ว ทั้งที่บางกล่องเป็นเพียงโครงสร้างองค์กรหรือช่องทางผู้ใช้
จบบทนี้คุณจะ
แยก Product, Subdomain, Bounded Context, module, service และ datastore สร้าง landscape ของ Wallet, Checkout, Billing, Payment Integration และ Web Portal จำแนก candidate Core, Supporting และ Generic Subdomains พร้อมติดป้าย assumption/evidence และ trace journey เพื่อค้นหาคำถามแทนการประกาศ microservices ล่วงหน้า
[!IMPORTANT] วิธีอ่านหลักฐานในบทนี้ คำที่เป็น Definition/Pattern มีแหล่งต้นทางอยู่ใกล้ claim ส่วน decision table, starting structure, checklist และ lab เป็น Heuristic/Trade-off หรือ Course Convention สำหรับฝึกตัดสินใจ ไม่ใช่มาตรฐานสากล เว้นแต่บทจะระบุแหล่งและขอบเขตไว้ชัดเจน
แยกแกนการตัดสินใจ
คำ 6 คำนี้ตอบคนละคำถาม:
| Term | คำถามที่ตอบ | ตัวอย่างที่ยังเป็น hypothesis |
|---|---|---|
| Product | ลูกค้าซื้อหรือใช้อะไร | Wallet, Checkout, Billing |
| Subdomain | ธุรกิจต้องแก้ปัญหาส่วนใด | pricing, ledger, invoicing |
| Bounded Context | model/language ใดสอดคล้องภายใน | Wallet Accounting |
| Module | code ส่วนใดมี public boundary | invoicing package |
| Service | อะไร deploy/operate แยก | billing-api process |
| Datastore | state ถูกเก็บที่ไหน | MySQL schema, Redis cache |
ความสัมพันธ์ไม่ใช่หนึ่งต่อหนึ่ง Product หนึ่งมีหลาย Subdomain/Context ได้ Context หนึ่ง อาจมีหลาย module และช่วงแรกหลาย Context อาจ deploy ใน service เดียวได้ Repository หนึ่งก็อาจ build หลาย binary โดยยังรักษา module boundary
หาก diagram มีเส้น Wallet → Billing ให้ถามต่อว่าเป็น command, event, query,
shared table หรือแค่ทีมคุยกัน ลูกศรที่ไม่บอก semantic contract ยังใช้ตัดสิน ownership ไม่ได้
ภาพรวม Product ที่ยังเป็นสมมติฐาน
เริ่มด้วย 5 พื้นที่ที่ผู้ใช้กำหนด แต่ติดป้ายว่าเป็น discovery containers:
Wallet
อาจมี account lifecycle, ledger, balance availability, transfer และ top-up posting คำถามหลักคือ Wallet รับรองข้อเท็จจริงอะไรและใครมีสิทธิ์เปลี่ยน financial state
Checkout
อาจเป็นเจ้าของ checkout session, payment attempt, customer choice และสถานะที่ merchant เห็น แต่ไม่ควรคัดลอก provider lifecycle มาเป็น domain model โดยตรง
Billing
อาจประกอบด้วย subscription, meter usage, invoicing, credit note, receivable, receipt, tax และ seller/customer snapshots รายชื่อเหล่านี้ยังไม่บอกว่าควรเป็น context/module ใด
Payment Integration
อาจเป็นเพียง adapters ต่อ provider หรือเป็น capability ด้าน routing, credential, reconciliation และ provider operations ถ้ามี policy ของตัวเองอาจมี model boundary มากกว่า utility package แต่ไม่ควรเป็นเจ้าของทุก business payment rule
Web Portal
เริ่มจาก hypothesis ว่าเป็น channel/BFF/read composition ซึ่งเรียก use cases ของ context เจ้าของ หากภายหลังพบ workflow, language และ invariant เฉพาะ Portal จึงค่อยประเมินว่า มี domain capability ของตัวเองหรือไม่
ตัวอย่าง Subdomain Portfolio
ในศัพท์ของ DDD การจำแนก Core, Supporting และ Generic ใช้กับ Subdomain ไม่ใช่ Product, service หรือ repository ทั้งก้อน ส่วนคำว่า Core Domain ใช้พูดถึงพื้นที่เชิงกลยุทธ์ ที่องค์กรต้องให้ความสำคัญ การเขียนว่า “Wallet คือ Core Domain” จึงยังไม่พอสำหรับตัดสิน code architecture เพราะ Wallet product อาจรวม Subdomain ทั้ง 3 ชนิดไว้ด้วยกัน
ลองสมมติบริษัท Fintech ชื่อ AcmePay ซึ่งประกาศ strategy ที่ตรวจสอบได้ 3 ข้อ:
- เพิ่ม payment acceptance ด้วยการเลือก provider และ recovery ที่ดีกว่าคู่แข่ง
- ทำให้ merchant ใช้และเคลื่อนย้ายยอดเงินได้อย่างถูกต้องและรวดเร็ว
- รองรับ usage-based pricing ที่ปรับตามสัญญาของลูกค้าองค์กรได้เร็ว
ภายใต้ strategy นี้ ทีมอาจตั้ง candidate classification รอบแรกดังนี้:
ภาพนี้เป็น portfolio view ตาม strategy ปัจจุบันของ AcmePay ไม่ใช่ dependency diagram กล่องภายในแต่ละกลุ่มจึงไม่ได้แสดงลำดับหรือ dependency ส่วนตารางด้านล่างเก็บเหตุผล และเงื่อนไขทบทวนของแต่ละ candidate ไว้ให้ตรวจสอบได้
| Product area | Candidate Subdomain | Candidate type | เหตุผลและเงื่อนไขทบทวน |
|---|---|---|---|
| Wallet | Available Balance and Reservation Policy | Core | เป็นคำมั่นที่กระทบวิธีใช้เงินและประสบการณ์ merchant; ทบทวนถ้าบริษัทใช้ ledger product ซึ่งกำหนด policy ให้ทั้งหมด |
| Wallet | Wallet Account Lifecycle | Supporting | ต้องมีกฎเฉพาะองค์กรเพื่อเปิด ระงับ และปิดบัญชี แต่ยังไม่มีหลักฐานว่าใช้สร้างความต่าง |
| Wallet | Notification Delivery | Generic | ตลาดมีบริการส่ง email/SMS/push; template และ consent อาจยังเป็น Supporting ตามข้อกำหนดขององค์กร |
| Checkout | Payment Attempt and Routing Policy | Core | เชื่อมโดยตรงกับ acceptance, cost และ recovery strategy ที่บริษัทเลือกแข่งขัน |
| Checkout | Checkout Session Lifecycle | Supporting | จำเป็นต่อ product และมี state เฉพาะ แต่ตัวอย่างนี้ยังไม่มี evidence ว่า session model สร้างความได้เปรียบ |
| Payment Integration | Provider Connectivity | Supporting | ต้องแปล request, response, webhook และ failure ของ provider ให้เป็นภาษาภายใน แต่การเรียก API อย่างเดียวไม่ใช่จุดแข่งขัน |
| Billing | Usage Rating and Contract Pricing | Core | รองรับ pricing เฉพาะลูกค้าองค์กรและเปลี่ยนตาม commercial strategy |
| Billing | Invoicing, Receivables and Credit Notes | Supporting | จำเป็นและมีกฎเฉพาะบริษัท แต่ strategy สมมติแข่งขันที่ rating/pricing ไม่ใช่รูปแบบเอกสาร |
| Billing | Tax Calculation | Generic candidate | เริ่มจากประเมิน product ภายนอกก่อน; เปลี่ยนเป็น Supporting ได้หากตลาด/สัญญาทำให้ต้องมี model เฉพาะ |
| Web Portal | Operations Case Management | Supporting candidate | ถ้ามี workflow ตรวจเอกสาร อนุมัติ และแก้ exception จริง มันคือ business capability ไม่ใช่แค่หน้าจอ |
| Cross-product | Identity and Authentication | Generic candidate | ใช้มาตรฐานและ identity provider ได้ แต่ tenant mapping และ use-case authorization ยังต้องอยู่กับ context เจ้าของกฎ |
คำว่า candidate สำคัญ เพราะตารางนี้เกิดจาก strategy สมมติ ไม่ใช่ taxonomy สากลของ
Fintech ตัวอย่างเช่น Reconciliation มี operational criticality สูงมาก แต่อาจเป็น Supporting
ถ้าไม่ได้สร้างความแตกต่าง ขณะเดียวกัน routing algorithm ที่มีโค้ดไม่มากอาจเป็น Core
เพราะมันเปลี่ยน conversion และ cost ที่บริษัทแข่งขันโดยตรง
Generic ไม่ได้แปลว่าความเสี่ยงต่ำ
Identity, tax และ notification อาจเป็น Generic ในเชิง strategic differentiation แต่ยัง ต้องมี owner, security control, availability target, data classification และ compliance evidence ที่เหมาะสม ข้อกำหนดเหล่านี้ต้องมาจากเจ้าของที่รับผิดชอบ พร้อมชื่อและวันที่
Product เดิมเมื่อกลยุทธ์เปลี่ยน
หาก AcmePay เปลี่ยนจาก payment platform เป็น Billing SaaS ที่แข่งขันด้วยการออก invoice, credit note และ revenue workflow ข้ามประเทศ classification ก็เปลี่ยนได้:
| Strategy | Core candidates | Supporting candidates | Generic candidates |
|---|---|---|---|
| Wallet-led Fintech | balance availability, transfer policy | checkout session, provider connectivity | notification, identity integration |
| Payment Orchestrator | provider routing, recovery policy | checkout session, reconciliation operations | notification, document rendering |
| Billing SaaS | contract pricing, invoicing workflow, revenue operations | payment collection, portal case management | identity integration, commodity delivery channels |
ดังนั้น landscape review ต้องถามว่า “เราแข่งขันด้วย capability ใดในช่วงเวลานี้” ก่อนถาม ว่า “ระบบนี้ชื่ออะไร” และบันทึก evidence, business owner, วันที่ และ trigger สำหรับทบทวน เสมอ การเปลี่ยน classification ไม่ได้บังคับให้ย้าย service ทันที แต่เป็นสัญญาณให้ทบทวน ระดับการลงทุนใน domain discovery, modelling, testing และความรู้ที่ต้องรักษาไว้ภายใน
ใส่หลักฐานก่อนวาดกล่อง
ให้ทุกกล่องมี evidence label:
- Observed — มี scenario, language, owner หรือ runtime fact ยืนยัน
- Proposed — boundary ที่ทีมเสนอและมีเหตุผล แต่ยังไม่ได้ทดสอบครบ
- Unknown — คำถามสำคัญยังไม่มี owner/ข้อมูล
- Legacy — เป็น constraint ปัจจุบัน ไม่ถือเป็น target domain boundary
ตัวอย่าง billing-service ที่มี invoice, tax, email และ portal query ใน process เดียว
เป็น observed deployment fact แต่ไม่ได้ยืนยันว่าเป็น Bounded Context เดียว
แผนที่รอบแรกอาจเป็น:
ลูกศรทุกเส้นในรอบแรกต้องมี question log เช่น Billing ขอเก็บเงินด้วย CollectInvoice
หรือสร้าง Checkout Session? Wallet รับ CreditWallet command หรือ subscribe
PaymentConfirmed? Portal query ผ่าน API เจ้าของหรือ join database?
ไล่ 2 เส้นทางธุรกิจ
Journey ทำให้ ownership gap ปรากฏชัดกว่า context diagram
Wallet top-up
- Customer เริ่ม top-up ผ่าน channel
- Checkout สร้าง attempt และ Payment Integration เรียก provider
- Provider outcome ถูกแปลเป็น application language
- Wallet บันทึก credit โดย deduplicate business reference
- Reconciliation หา ambiguous/incomplete operation
Billing collection
- Metering สรุป usage ตาม contract
- Invoicing ออก Invoice และอาจออก Credit Note ภายหลัง
- Receivables ติดตามยอดค้างและขอ collect
- Payment capability ดำเนินการเรียกเก็บ
- Receipt ถูกออกจากหลักฐานการรับชำระ
- Portal แสดง composed view โดยไม่เป็นเจ้าของสถานะเหล่านั้น
Display journey ไม่ใช่ decision journey
Portal หรือ Redis read model อาจรวมข้อมูลหลาย context สำหรับหน้าจอ แต่การอนุมัติ Refund, Transfer หรือ Collection ต้องส่ง command ไปยัง context เจ้าของกฎ และให้ context นั้นอ่าน authoritative state ของตน อย่านำค่าที่ compose เพื่อ display ไปตัดสินเงิน
แบบฝึกปฏิบัติ: แผนที่พร้อมข้อสมมติ
สร้างแผนที่ 2 ชั้น ชั้นแรกเป็น Product/Channel ชั้นที่ 2 เป็น candidate contexts จากนั้นแนบ registry ต่อกล่อง:
Name and level: Product | Context candidate | Module | Service | Datastore
Status: Observed | Proposed | Unknown | Legacy
Candidate subdomain type: Core | Supporting | Generic | Unknown | Not applicable
Strategic evidence and review date:
Owned language and decisions:
Business and engineering owner:
Public commands/events/queries:
Authoritative data:
Dependencies and translations:
Evidence:
Questions and review date:
ห้ามวาด database ก่อนระบุ owner ของ data และห้ามใช้ repository name เป็น evidence ให้ trace อย่างน้อย 1 happy path กับ 1 ambiguous failure ผ่านทุกกล่อง
คะแนนแบบฝึกหัด:
- ผ่าน เมื่อทุกกล่องระบุ level และ status
- ดี เมื่อแตก Product เป็น candidate Subdomains, classification มี strategic evidence และ journey เปิดเผย contract/ownership gap พร้อม owner
- ควรทำใหม่ เมื่อ Product = Context = Service = Database แบบหนึ่งต่อหนึ่งทั้งหมด
รายการตรวจสอบ
- Product, Context, module, service และ datastore ถูกแยกเป็นคนละระดับ
- Core/Supporting/Generic ถูกใช้กับ Subdomain ไม่ใช่ติดป้าย Product ทั้งก้อน
- Candidate classification อ้าง strategy, owner, วันที่ และเงื่อนไขทบทวน
- Core classification ถูกแยกจาก operational criticality และ compliance impact
- Wallet/Checkout/Billing เป็น hypothesis ไม่ใช่ universal Fintech map
- Payment Integration ระบุว่าเป็น adapter capability หรือมี policy ใดจริง
- Portal เริ่มเป็น channel และไม่ยึด ownership จาก context อื่น
- ลูกศรมี semantic contract หรือ question log
- Journey มี ambiguous failure และ reconciliation owner
- Display state ถูกแยกจาก authoritative decision state
สรุปบทนี้
Enterprise landscape ที่ดีไม่ได้ดูสวยเพราะมีกล่องเยอะ แต่ทำให้รู้ว่าแต่ละกล่องอยู่ ระดับใด Subdomain ใดเป็น Core, Supporting หรือ Generic ตาม strategy มีหลักฐานอะไร และคำถามใดยังเปิดอยู่ แผนที่นี้เป็น input ให้เลือก code architecture ราย module ใน Part ถัดไป ไม่ใช่คำสั่งให้สร้าง microservice ตามรูป
อ่านเพิ่มเติม
- DDD Reference — Subdomain, Bounded Context และ Context Map
- Learning Domain-Driven Design, Chapter 1 — Core, Supporting และ Generic Subdomains
- Learning Domain-Driven Design — strategic design และ physical boundaries
- Monolith First — evolutionary boundary strategy และข้อจำกัดของหลักฐาน
- Microservice Trade-Offs — ต้นทุนที่ตามมาจาก deployment distribution