3 Technique ในการใช้แนวคิด Domain-driven design บน Legacy code
เมื่อวันก่อน ที่บริษัทมี lunch&learn เกี่ยวกับ Domain-driven design บน Legacy code ของคุณ Chris โดยส่วนตัวผมคิดว่ามันมี technique ที่เรียบง่ายและสามารถนำไปปรับใช้แล้วดีขึ้นได้ทันทีโดยไม่ต้องลงแรงมากมาย มาดูกัน
Domain-driven design มีประโยชน์อย่างไร แบบขอสั้น ๆ
ในการพัฒนา software หนุ่งในปัญหาระดับโลกคือการทำงานร่วมกัน (collaboration) ระหว่างฝั่ง business และ technical ที่ไม่ราบรื่น จึงเกิดแนวคิด Domain-driven design (DDD) โดยมีเป้าหมายคือทำให้ความรู้ความเข้าใจในระบบ (business domain) เป็นไปตามสิ่งที่ผู้เชี่ยวชาญ (domain expert หรือ subject-matter expert) ได้บอกกล่าว ซึ่งผู้เชี่ยวชาญนั้นก็คือฝั่ง business หรือคนที่ให้ requirement กับนักพัฒนานั่นเอง
https://medium.com/nick-tune-tech-strategy-blog/domain-driven-architecture-diagrams-139a75acb578
ในแง่ของ programming การใช้งาน DDD จะช่วยให้ class, method, variable ตรงกับ business domain ส่งผลให้
- การดูแลรักษาง่ายขึ้น เพราะ business และ technical ใช้ภาษาเดียวกัน (Ubiquitous language)
- Code สามารถต่อยอดตาม requirement ได้ง่าย เพราะ code ถูก model ตาม context ใน domain
ในบางครั้งนักพัฒนาก็ไม่สามารถหลีกเลี่ยงที่จะต้องแตะ legacy code ถึงแม้ว่าใครแตะแล้วก็จะเหม็นติดมือก็ตาม การทำตามแนวคิด DDD ก็อาจจะไม่ได้ตรงไปตรงมาเนื่องจาก code ไม่ได้ model ตาม domain context ตั้งแต่แรก ดังนั้นจึงต้องใช้ technique เข้ามาช่วย
Vocabulary extraction
แนวคิดคือเราจะต้องระบุ ubiquitous language จาก business ให้ได้ก่อน ซึ่งมันก็อยู่รอบ ๆ ตัวเราระหว่างทำงานทุกวันแบบไม่รู้ตัว เช่น
- User story
- เอกสารต่าง ๆ
- Discussion จากการประชุมต่าง ๆ
- บทสนทนาข้าวเที่ยง ?
-
เริ่มต้นง่าย ๆ ด้วยการระบุคำนาม (noun) และกริยา (verb) ยกตัวอย่างใน user story
-
ถ้ามี class/method อยู่แล้วแต่คนละชื่อ ตกลง ubiquitous language กับ business ให้เรียบร้อยว่าจะใช้อันไหน
-
เขียน code โดยตั้งชื่อ class ตาม noun และ method ตามชื่อ verb
ประโยชน์ที่ได้คือเราจะได้ ubiquitous language ทำให้เราสามารถต่ยอดแนวคิด DDD ได้ง่าย
Ubiquitous language bubble
แนวคิดคือเราจะสร้าง anti-corruption layer เช่น adapter มาแปลง legacy code เป็น domain code
-
แยก legacy code ออกจาก domain code แล้วจัดกลุ่มของใครของมัน เช่น แยก package ออกมาโดยเฉพาะเลย
├── legacy │ ├── Client │ ├── ... ├── other │ ├── ... │ ├── ...
-
ใช้ ubiquitous language ใน domain package
├── legacy │ ├── Client │ ├── ... ├── domain │ ├── Customer │ ├── ...
-
สร้าง adapter มาแปลง legacy code เป็น domain code
-
Retire legacy code จนเหลือแค่ domain code
ประโยชน์ที่ได้คือ ขณะที่เรากำลังแทน legacy ด้วย domain code ตัว business logic ก็ยังคงทำงานได้อยู่ และทำให้เราเห็น context map ระหว่าง legacy และ domain code ซึ่งช่วยเพิ่มความเข้าใจในระบบมากขึ้น
Technique นี้มีข้อควรระวังคืออย่าใช้มากเกินไปและอย่าเก็บ adapter ไว้นานเกินไป เพราะเราจะต้อง maintain code เพิ่ม ทำให้งงได้ง่ายขึ้น
Bounded context discovery
ในการพัฒนาระบบงานใน legacy code มีโอกาสสูงมากที่เราจะเจอ class ที่รวม logic ไว้เยอะมาก ซึ่งแน่นอนว่าจะมีหลาย variable และ method เราเรียกสั้น ๆ ว่า God class มีผลเสียคือ ทำความเข้าใจยาก เวลาเกิดปัญหาใช้เวลาค้นหานานขึ้น
ใน God class นั้นอาจจะประกอบไปด้วย context หลาย ๆ อัน ดังนั้นเราควรจะตีกรอบและแยก context ออกจากกัน (bounded context) เพราะการแก้ไข context ใด ๆ ไม่ควรส่งผลต่อ context อื่น ๆ ยกตัวอย่าง เช่น
จะสังเกตว่าจริง ๆ ทั้งคู่ก็คือร้านอาหารแหละ แต่มันอยู่คนละบริบทกันขึ้นอยู่กับผู้ใช้งานระบบเป็น manager ของร้านอาหาร หรือ admin
จากตรงนี้เราก็สามารถที่จะแยก method ที่ใช้โดย admin ออกจาก manager ได้ละ
ประโยชน์ที่ได้คือ code ดูแลรักษาง่ายขึ้น ลดความเสี่ยงในการที่ logic ส่วนอื่นได้รับผลกระทบเมื่อมีการแก้ไขส่วนใด ๆ
มาถึงตรงนี้น่าจะเห็นแล้วว่าการนำแนวคิด DDD มันช่วยให้เราพัฒนาระบบงานได้อย่างเรียบง่ายขึ้น ลดอาการปวดหัวระหว่างฝั่ง business และ technical โดยที่เราจะให้ domain modeling เป็นตัวขับเคลื่อน technical pattern นั่นเอง