Active Record to Domain Model
เมื่อเวลาผ่านไป ธุรกิจมีการเติบโตขึ้นและ business rules มีความซับซ้อนมากขึ้น คุณเริ่มสังเกตเห็น duplications code เริ่มได้กลิ่นของ big ball of mud จาก dependency ที่ import ข้ามกันไปมา อยากลองเปลี่ยนวิธี Implementing Domain Logic บทควมนี้อาจช่วยคุณได้
Domain Model
คือการอธิบายความรู้และความเข้าใจของ domain experts ที่มีต่อธรุกิจ อาจจะอยู่ในรูปแบบ diagrams, documentation หรือ post-it
ในโลกของ software development เปรียบได้กับการสร้าง class model เพื่อจำลองธุรกิจ โดยที่ model นี้จะให้ความสำคัญกับ business logic มาเป็นอันดับแรก ใน model จะมี variables ที่บอกว่าสนใจข้อมูลอะไรและมี functions ไว้บอกถึง behaviors ของ domain ถ้าธุรกิจมีความซับซ้อนและเปลี่ยนแปลงตลอดเวลา มีขั้นตอนตรวจสอบความถูกต้องของข้อมูลที่แตกต่างไปจากธุรกิจอื่นๆ หรือมีการใช้ข้อมูลจากหลาย ๆ ตารางเพื่อคำนวณค่าบางอย่าง การใช้ Domain Model อาจจะเหมาะกับธุรกิจคุณ
แต่ถ้าระบบมีแค่ตรวจสอบค่า Null และรวบรวมข้อมูลเพื่อ CRUD ลงฐานข้อมูล Transaction Script หรือ Active Record อาจเป็นทางเลือกที่ดีกว่า
Domain Storytelling
เป็นเทคนิคที่ให้ domain experts มาเล่าเรื่องราวจาก domain ที่ตัวเองดูแล เพื่อช่วยให้เราเข้าใจเกี่ยวกับธุรกิจ พฤติกรรมที่เขาทำ ภาษาที่เขาใช้สื่อสารระหว่าง domain A ถึง domain B อาจเป็นคำที่เราไม่คุ้นหู แต่ดูเป็นธรรมชาติเมื่อพวกเขาอธิบายมันออกมา บางครั้งเราใช้ช่วงเวลานี้สร้าง common language ที่เรากับ domain experts เข้าใจร่วมกัน ในโลกของ Domain-driven Design เราเรียกสิ่งนี้ว่า Ubiquitous Language
Implementation
Disclaimer : ผู้เขียนไม่ใช่ผู้เชี่ยวชาญด้านลงทุน ตัวอย่างที่ยกมาเป็นเพียงความเข้าใจของตนเองเท่านั่น
ระบบซื้อขาย Single Stock Futures สำหรับลูกค้า (VIP)
Actors: investor โทรหา Marketing ต้องการเปิด Long Futures AOTZ23 ที่ราคา 50 บาท จำนวน 1000 สัญญา (1 Contract มีค่าเท่ากับ 1000 หุ้น) Marketing ต้องคำนวณค่า Commission และจดบันทึกเอาไว้ใน Excel โดยสูตรที่ใช้คำนวณ Commission คือ
[((Price x (Contract * Contract Size) x Commission) + (Contract x TradingFee)] x VAT 7%
Ref: เปรียบเทียบ Commission ระหว่าง ซื้อขายหุ้น กับ ซื้อขาย SSF Block Trade
ตัวอย่างโค้ด Active Record
class OrderService {
async placeOrder(request: IPlaceOrder): Promise<Order> {
const { series, contract, price, side } = request;
const { contractSize } = await this.getSingleStockFuturesBySeries(series);
const commissionRate = 0.001;
const tradingFee = 0.51;
const vat = 1.07;
const order = new Order();
order.series = series;
order.contract = contract;
order.side = side;
order.price = price;
order.commission =
(price * (contract * contractSize) * commissionRate +
contract * tradingFee) *
vat;
const response = await order.save();
return response;
}
}
ตัวอย่างโค้ด Domain Model
class Marketing {
private readonly commissionRate = 0.001;
private readonly tradingFee = 0.51;
private readonly vat = 1.07;
calculateCommission(price: number, contract: number, contractSize: number) {
return (
(price _ (contract _ contractSize) _ this.commissionRate +
contract _ this.tradingFee) \*
this.vat
);
}
Domain Model จะหลีกเลี่ยง dependency เช่น การเชื่อมต่อ database, การใช้ data types จาก library, การเรียกไปยัง external components หรือการอ่าน config จาก .env สิ่งเหล่านี้ไม่ควรอยู่ในนั่น ใน model ควรจะมีแค่ business logic จริงๆ ของธุรกิจและมีความยืดหยุ่นที่สามารถปรับเปลี่ยนตามธุรกิจได้แบบที่ไม่ต้องสนใจ database connect ไม่ได้, env ไม่มีค่า หรือ library update อีกหนึ่งเรื่องที่ “ควร” ให้ความสำคัญ คือ testing การไม่มี dependency ทำให้เขียนและปรับเปลี่ยนเทสได้ง่าย ถ้า domain experts เปลี่ยน Ubiquitous Language สิ่งที่ผมไม่อยากทำคือการแก้ชื่อ functions ใน jest.spyon
สำหรับธุรกิจที่ซับซ้อน domain model จะมีทั้ง inheritance, strategies, behaviors และโครงสร้างข้อมูลที่แตกต่างไปจาก fields ฐานข้อมูลจึงเป็นเรื่องยากที่จะใช้งานร่วมกับ active record Martin Fowler แนะนำไว้ใน Patterns of Enterprise Application Architecture ว่า If you’re using Domain Model, my first choice for database interaction is Data Mapper เพื่อแยก domain model ออกจาก database schema และยังแนะนำให้ศึกษาเรื่อง Service Layer ในกรณีที่อยากแยก domain model ออกจาก api handles