codebase-design
การออกแบบ Codebase
ออกแบบ deep module: พฤติกรรมจำนวนมากอยู่หลัง interface เล็ก ๆ วางไว้ที่ seam (จุดต่อที่เราสลับพฤติกรรมได้โดยไม่ต้องแก้ code ตรงนั้น) ที่สะอาด และ test ได้ผ่าน interface นั้น ใช้ภาษาและหลักการชุดนี้ทุกที่ที่มีการออกแบบหรือปรับโครงสร้าง code เป้าหมายคือ leverage ให้ฝั่ง caller, locality ให้คนดูแล code และความ testable ให้ทุกคน
Glossary
ใช้คำเหล่านี้แบบเป๊ะ ๆ — อย่าเปลี่ยนไปใช้ "component," "service," "API," หรือ "boundary" แทน ภาษาที่สม่ำเสมอคือหัวใจของเรื่องนี้
Module — อะไรก็ตามที่มี interface กับ implementation ตั้งใจให้ไม่ผูกกับขนาด: จะเป็น function, class, package หรือ slice ที่พาดผ่านหลาย tier ก็ได้ เลี่ยง: unit, component, service
Interface — ทุกอย่างที่ caller ต้องรู้เพื่อใช้ module ได้ถูกต้อง: ไม่ใช่แค่ type signature แต่รวมถึง invariant, ข้อบังคับเรื่องลำดับการเรียก, รูปแบบ error, config ที่ต้องมี และคุณสมบัติด้าน performance ด้วย เลี่ยง: API, signature (แคบเกินไป — สองคำนี้พูดถึงแค่ผิวระดับ type เท่านั้น)
Implementation — สิ่งที่อยู่ข้างใน module ตัวเนื้อ code ของมัน ต่างจาก Adapter: ของชิ้นหนึ่งอาจเป็น adapter เล็กที่มี implementation ใหญ่ (Postgres repo) หรือ adapter ใหญ่ที่มี implementation เล็ก (fake ใน memory) ก็ได้ ใช้คำว่า "adapter" เมื่อประเด็นอยู่ที่ seam นอกนั้นใช้ "implementation"
Depth — leverage ที่ตัว interface: ปริมาณพฤติกรรมที่ caller (หรือ test) ใช้งานได้ต่อหน่วยของ interface ที่ต้องเรียนรู้ module จะ deep เมื่อพฤติกรรมจำนวนมากอยู่หลัง interface เล็ก ๆ และ shallow เมื่อ interface ซับซ้อนแทบเท่า implementation
Seam (Michael Feathers) — จุดที่เราเปลี่ยนพฤติกรรมได้โดยไม่ต้องแก้ code ตรงจุดนั้น; คือตำแหน่งที่ interface ของ module อาศัยอยู่ จะวาง seam ตรงไหนเป็นการตัดสินใจออกแบบอีกเรื่องหนึ่ง แยกจากว่าอะไรจะอยู่ข้างหลังมัน เลี่ยง: boundary (ความหมายชนกับ bounded context ของ DDD)
Adapter — ของจริงชิ้นหนึ่งที่ทำตาม interface ณ seam หนึ่ง อธิบายบทบาท (เสียบเข้าช่องไหน) ไม่ใช่เนื้อใน (ข้างในมีอะไร)