บทที่ 24 · Part 5 — Enterprise Fintech Architecture

Testing and Architecture Fitness

วาง test ตาม seam ที่พิสูจน์ claim และ automate dependency, cycle, vendor containment กับ data ownership rules

Testing and Architecture Fitness

ทีมมี unit tests หลายพันรายการที่ mock ทุก repository/client และ assert ลำดับ method call แต่ production พังเพราะ MySQL constraint ผิด, Stripe error ใหม่ไม่ถูก map และ module หนึ่ง import vendor SDK เข้า domain Tests ต้องพิสูจน์ claim ที่ seam จริง ส่วน architecture fitness ต้องทำ boundary rules สำคัญให้ executable

จบบทนี้คุณจะ

สร้าง test matrix สำหรับ domain, application, MySQL, Redis, provider, HTTP และ workflows เลือก real/fake/mock ตาม claim และ automate dependency direction, cycles, vendor-type containment กับ cross-context data-access rules โดยไม่ทดสอบ convention ทุกอย่าง

[!IMPORTANT] วิธีอ่านหลักฐานในบทนี้ คำที่เป็น Definition/Pattern มีแหล่งต้นทางอยู่ใกล้ claim ส่วน decision table, starting structure, checklist และ lab เป็น Heuristic/Trade-off หรือ Course Convention สำหรับฝึกตัดสินใจ ไม่ใช่มาตรฐานสากล เว้นแต่บทจะระบุแหล่งและขอบเขตไว้ชัดเจน

เลือก Seam ให้ตรงกับสิ่งที่ต้องพิสูจน์

ClaimTest seamDependency
refund invariantpure domain testreal domain objects
authorization orchestrationapplication testfocused fakes for owner ports
SQL mapping/constraint/concurrencyrepository integrationreal MySQL version
Redis TTL/invalidation/stampedecache integration/behaviorreal Redis when behavior matters
Stripe translationadapter/contract testfixtures + official sandbox as applicable
HTTP decode/error/security headershandler testreal router + application stub
crash/retry/idempotency journeyworkflow/component testdurable stores + controlled failures
end-to-end money journeycritical journey testproduction-like boundaries

จำนวน tests แต่ละชั้นไม่ใช่ pyramid quota ให้ลงทุนตาม failure cost และ feedback speed

การทดสอบ Domain และ Application

Domain tests ใช้ real values และ table-driven scenarios:

func TestChargeApproveRefund(t *testing.T) {
	tests := []struct {
		name           string
		capturedSatang int64
		refundedSatang int64
		requestSatang  int64
		wantErr        error
	}{
		{name: "partial THB refund", capturedSatang: 10000, requestSatang: 2500},
		{name: "exceeds THB charge", capturedSatang: 10000, refundedSatang: 8000, requestSatang: 2501, wantErr: ErrRefundExceedsCharge},
	}
	// Construct THB Money values from satang, then assert the Aggregate outcome.
}

Application tests ใช้ fake เฉพาะ meaningful ports เช่น gateway/clock/store เพื่อจำลอง unknown outcome ไม่ assert internal call order เกิน contract หาก refactor จาก 2 calls เป็น transaction method tests ควรยังผ่านเมื่อ observable behavior เดิม

Integration Test สำหรับ Database และ Cache

Repository tests ต้องใช้ MySQL engine/version/schema จริงสำหรับ:

  • unique/idempotency/tenant constraints
  • transaction rollback
  • optimistic conflict หรือ locking behavior
  • nullable/precision/time mapping
  • migration compatibility
  • concurrent goroutines/transactions ที่จำลอง race

In-memory fake หรือ SQLite ไม่พิสูจน์ MySQL semantics ใช้ isolated database/schema และ deterministic fixtures cleanup โดยไม่ share mutable state ข้าม parallel tests

Redis tests ใช้ real Redis เมื่อ claim คือ TTL, atomic command, invalidation หรือ stampede แต่ application test ที่เพียงต้อง cache hit/miss ใช้ fake ได้ ห้ามทำ test suite ขึ้นกับ Redis เพียงเพราะ interface มี implementation

Financial correctness ต้องมี concurrent/integration evidence

Pure Aggregate test ไม่พิสูจน์ว่า two requests ไม่ over-refund ต้องมี MySQL integration test ที่แข่งขัน version/lock จริง และ critical journey ยืนยัน idempotent effect ปลายทาง

ทดสอบ Contract ที่ Provider และ System Boundary

Adapter unit tests cover translation table ทุก vendor status/error ที่ application ใช้ Contract/sandbox tests ยืนยัน assumptions ต่อ pinned API version, request fields, idempotency, webhook signature และ response schema

Pact เป็นตัวเลือก consumer-driven contract หนึ่ง แต่ external provider sandbox/official test suite อาจเหมาะกว่า อย่าอ้างว่า Pact พิสูจน์ financial lifecycle ทั้งหมด

HTTP tests พิสูจน์ decoding limits, authentication middleware mapping, tenant source, problem details และ no sensitive errors Webhook tests ใช้ raw payload/signature fixtures, duplicates, old events และ crash point

Architecture Fitness Functions

Building Evolutionary Architectures ใช้ fitness function เพื่อประเมิน architecture characteristic ต่อเนื่อง ใน repo นี้ตัวอย่าง:

deny imports github.com/stripe/* outside internal/adapters/paymentgateway/stripe
deny database/sql and net/http from selected domain packages
deny module import cycles
deny modules importing peer repository/mysql packages
require SQL ownership annotation or DB grant test for cross-schema access
require integration events to pass schema compatibility checks

ใช้ go list -deps, AST/static analysis, build tags หรือ custom tests ตามความคุ้มค่า Rule ต้องมี reason/owner และ exception expiry ห้าม test ว่าทุก module ต้องมี domain.go เพราะนั่นเป็น folder convention ไม่ใช่ architecture property

Test Matrix ที่ดูแลต่อเนื่อง

Risk / invariant:
Owner:
Test seam:
Real dependencies required:
Failure injection:
Evidence produced:
Runtime monitor/reconciliation:
Known gap and review date:

Testing ไม่จบที่ pre-deploy Unknown provider state และ reconciliation ต้องมี production metrics, alerts และ drill Test/monitor เป็น evidence คนละช่วงของ control เดียวกัน

แบบฝึกปฏิบัติ: Refund Confidence Map

สร้าง tests อย่างน้อย:

  1. domain cumulative-refund invariant
  2. application maker-checker/tenant tests
  3. MySQL concurrent version conflict
  4. Stripe translation unknown outcome
  5. webhook duplicate/out-of-order
  6. crash หลัง provider success ก่อน local save
  7. architecture rule กัน vendor import

ระบุต่อ test ว่าพิสูจน์อะไรและ ไม่พิสูจน์อะไร หาก 2 tests พิสูจน์ claim เดียวกันด้วย feedback ช้ากว่าให้ลด duplication

รายการตรวจสอบ

  • Test seam ตรงกับ claim/failure mode
  • Domain tests ใช้ real objects มากกว่า mock internals
  • MySQL/Redis behavior ทดสอบกับ engine จริงเมื่อ claim ต้องการ
  • Provider assumptions ผูกกับ pinned version/source
  • Concurrency/idempotency มี component evidence
  • Fitness rules วัด dependency/data/vendor boundary จริง
  • Rules มี owner/reason/exception expiry
  • Runtime monitoring/reconciliation เติมสิ่งที่ tests ไม่เห็น

สรุปบทนี้

Test architecture ที่ดีไม่ใช่ mock coverage สูง แต่คือ evidence ที่ seam เหมาะสม Domain, application, database, provider และ workflow ต้องใช้เครื่องมือต่างกัน Fitness functions ทำ boundary สำคัญให้ตรวจซ้ำได้โดยไม่เปลี่ยนทุก style preference เป็น governance

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