Domain-Driven Design: การออกแบบและพัฒนาระบบอย่างมีเสถียรภาพ
Domain-Driven Design (DDD) เป็นแนวคิดหรือเซตของหลักการในการออกแบบและพัฒนาซอฟต์แวร์หนึ่งในหลายๆ Principle ที่เน้นการพิจารณาและแก้ไขปัญหาความซับซ้อนของธุรกิจอย่างมีเสถียรภาพ ตามหลักการของ DDD นั้นเน้นที่จะสร้างโมเดลซอฟต์แวร์ที่เข้าใจง่ายและสอดคล้องกับธรรมชาติขององค์กรหรือธุรกิจที่กำลังจะพัฒนา โดยใช้ภาษาและคอนเซปต์ที่เข้าใจง่ายสำหรับทุกคนที่เกี่ยวข้องกับโครงการ
หลักการหลักของ DDD จะแบ่งแยกโครงสร้างและหน้าที่ของ Software Model และ Bussiness Model ออกเป็นแบบที่สอดคล้องกับการดำเนินธุรกิจนั้นๆ เพื่อแสดงความหมายและความสำคัญขององค์ประกอบในแต่ละ Domain นอกจากนี้ยังเน้นการสร้าง แยกแยะ จำแนก และแบ่งระบบออกเป็นส่วนต่าง ๆ ที่อยู่ในขอบเขตของการทำงานที่แตกต่างกันและให้ความสำคัญกับการสื่อสารและความเข้าใจร่วมกันระหว่างทีมพัฒนาและผู้เกี่ยวข้องที่เกี่ยวข้อง
การพัฒนา Domain ใน DDD :
1.การแบ่งแยกโดเมน (Domain Decomposition): การแบ่ง Domain ออกเป็นส่วนย่อยที่มีความสอดคล้องกับกระบวนการธุรกิจและฟังก์ชันของระบบ เพื่อให้แต่ละส่วนมีความชัดเจน ยืดหยุ่นและสามารถพัฒนาแยกออกจากกันได้
การแบ่งแยกโดเมน (Domain Decomposition) เป็นกระบวนการที่สำคัญในการออกแบบและพัฒนาซอฟต์แวร์โดยใช้แนวคิดของ Domain-Driven Design (DDD) ซึ่งมุ่งเน้นให้โมเดลซอฟต์แวร์สอดคล้องและแก้ไขปัญหาในโดเมนองค์กรหรือธุรกิจที่เกี่ยวข้องอย่างมีเสถียรภาพ
กระบวนการแบ่งแยกโดเมนนั้นมีลักษณะการแบ่งโดเมนออกเป็นส่วนย่อยๆ ที่มีความสอดคล้องกับ Couture และกระบวนการดำเนินธุรกิจขององค์กร โดยใช้หลักการต่อไปนี้:
- การจำแนกการทำงานของธุรกิจเป็นโดเมน (Functional Decomposition หรือ Domain): เป็นการแยกแยะและระบุการทำงานหรือฟังก์ชันหลักของแต่ละธุรกิจออกมา โดยพิจารณาฟังก์ชันที่สำคัญและเป็นส่วนหลักในการดำเนินงานของธุรกิจนั้นๆ และแยกออกเป็นส่วนย่อยๆ ที่สามารถพัฒนาแยกกันได้ โดยจะแบ่งแยกได้ สามแบบคือ:
- Core Domain คือ Domain หลักของธุรกิจนั้นๆ ทำให้ธุรกิจนั้นๆ แตกต่างและมี Values เป็นส่วนที่สำคัญที่สุดและเราควรมีทีมที่เก่งที่สุดขององค์กรเพื่อพัฒนา
- Supporting Domain คือ Domain ที่ช่วย Support Core Domain ทำให้ทำงานได้อย่างมีประสิทธิภาพมากขึ้น อาจจะไม่สามารถจัดซื้อบริการแบบทั่วไปได้เนื่องจากมีฟังก์ชั่นบางอย่างที่จำเพราะที่เราต้องการใช้ หรืออาจจะจ่ายเงิน custom software ไปเลยก็ได้
- Generic Domain คือ Domain ที่เราสามารถจัดซื้อ จัดจ้าง หรือใช้บริการจากที่อื่นได้ ตัวอย่างตามข้อแรก เช่น ห้องยา ที่เราสามารถเขียนใบสั่งยาแล้วให้ลูกค้าไปซื้อยาตามร้านยาได้ - การแบ่ง Domain ตามขอบเขตบริบททางธุรกิจ (Bounded Contexts): โดยแต่ละ Bounded Context เป็นเครื่องมือที่ช่วยในการแบ่งแยกขอบเขตบริบทของโดเมน แบ่งกลุ่มคอนเซ็ปต์ที่สอดคล้องกัน คล้ายกัน ใกล้เคียงกันและแตกต่างกัน โดยจะสามารถสื่อสารกัน เรียกใช้ข้อมูลของแต่ละโดเมนได้ โดยกระบวนการนี้เราจะได้ Aggregate ซึ่งเทียบเท่าคำที่เราคุ้นเคยและพยายามหาคำตอบกันคือ Micro Service นั่นเอง
- ศัพท์เฉพาะในแต่ละโดเมน (Ubiquitous Language): คือ ศัพท์เฉพาะที่ใช้คุยกันในแต่ละธุรกิจ รวมไปถึงการะัฒนา ที่ทีมเราเข้าใจ เช่นเมื่อเราจะพัฒนาระบบโรพพยาบาล หมอหรือพยาบาลเขาก็จะมีศัพท์เฉพาะที่นักพัฒนาจะไม่เข้าใจ ดังนั้นการนิยามศัพท์เฉพาะเพื่อให้ทุกระดับคุยกันรู้เรื่องจึงอยู่ในส่วนนี้ รวมไปถึงการนำมาตั้งชื่อตัวแปร ,Service , API , Function,Topic เพื่อไม่ให้เป็นภาระลูกหลานต่อไป
การแบ่งแยกโดเมนให้เหมาะสมและชัดเจนนี้ช่วยให้ทีมพัฒนาสามารถทำงานแยกกันได้อย่างมีประสิทธิภาพ และใช้คอนเซ็ปต์และภาษาที่เข้าใจง่ายในแต่ละโดเมน เพื่อสร้างระบบซอฟต์แวร์ที่มีความสอดคล้องกับโดเมนและธุรกิจองค์กรอย่างเสถียรและมีประสิทธิภาพ รองรับการขยายตัวและการดูแลได้ง่ายขึ้น
2. Context Mapping: ในส่วนนี้เราจะพูดถึงการเชื่อมโยง ความสัมพันธ์กันของ Domain ต่างๆ และกำหนดรูป Context โดยจะมีหลายรูปแบบ เช่น
- Separate ways : รูปแบบดั้งเดิมที่เราคุ้นเคยกันคือการแยก Context ออกจากกัน ข้อดีของ Separate ways คือความคล่องตัวของระบบเพราะมันจบในตัวเอง แต่ข้อเสียคืออาจเกิดปัญหาการซ้ำซ้อนของข้อมูล และเมื่อต้องการจะแชร์ข้อมูลกันก็ เราก็จะการสร้างตัวกลางขึ้นมาเพื่อใช้ในการส่งข้อมูลกัน
- Shared kernel : ตัวอย่างที่เราเห็นกันบ่อยคือใช้ Database ร่วมกัน query ตรง เปิด Connection ไปเลย
- Customer Provider : ผู้เรียกใช้งานเราจะเรียกว่า Upstream context ส่วนผู้ที่ให้บริการเรียกว่า Downstream context ทั้งสองฝั่งจะทำงานเป็นอิสระออกจากกันแต่ถ้าจะยกตัวอย่างให้เห็นภาพก็เช่น API ที่ทำขึ้นมาให้สำหรับ Customer หนึ่งแบบจำเพาะเจาะจงใช้เพียงเท่านั้นและเราจะ Implement ตามที่ Customer นั้นต้องการ
- Conformist : อันนี้จะคล้ายๆกับ Customer Provider แต่จะต่างกันที่มันจะถูกสร้างขึ้นมาไว้สำหรับหลายๆ Customer ทำไว้เป็นตัวกลางที่ทุก Customer ตกลงกันว่าจะมีหน้าตาข้อมูลเป็นอย่างไร
อันนี้คือ Context Mapping ที่ผมยกตัวอย่างมานะครับ แต่ก็ยังมีอีกหลายตัว เช่น Anticorruption Layer (ACL) , Open Host Service ,Published Language เป็นต้น
3. Design : ในส่วนนี้จะเป็นการออกแบบหน้าจอรวมไปถึงในส่วน Service และ Database ที่เราต้องการในระบบเลย มีกี่ pages แต่ละ page มี input ,label ,button , table อะไรบ้าง จะช่วยให้เห็นภาพข้อมูลของระบบและจะช่วยให้เราออกแบบ Table ใน Database ชื่อตัวแปร ชื่อ Services และอื่นๆได้ด้วย และ Ubiquitous Language มักจะมีบทบาทในส่วนนี้แหละเพราะเมื่อเราเห็นหน้า UI เราจะเรียกว่าหน้าอะไร Service ชื่ออะไรโดยจะมีการนิยามศัพท์เฉพาะที่คุยกันตั้งแต่ฝั่ง User ไปจนถึง Developer เลย เมื่อเราพูดถึง Function/Method ชื่อว่าอะไรก็จะล้อไปกับชื่อ pages,API,Topic,tables เลย และเมื่อเราทำมาจนถึง Tables ใน Database แล้ว และเราก็ได้ทำการรวม Tables ที่จำเป็นต่อการทำงานให้จบ Flow ได้นั้นเราก็จะได้ Aggregate ซึ่งก็คือ 1 Micro-Service นั้นเอง และเมื่อเราแยก Aggregate ออกเป็นชิ้นๆ เราก็จะเห็นการทำงานของแต่ละส่วนที่เล็กพอจะทำงานในเรื่องๆ หนึ่งจบได้ด้วยตัวเอง หรือที่เราจะเรียกว่า Aggregate นั่นเอง และ 1 Aggregate ก็เท่ากับ 1 Micro-Service และ 1 Micro-Service ก็เท่ากับ 1 Container นั่นเองและจากการแบ่ง Aggregate นี่เองทำให้เราได้คำตอบของสิ่งที่เราตามหามานานว่า Micro-Service คืออะไร
4. Command, Event, Result : ส่วนนี้จะเกี่ยวเนื่องกับเรื่อง UI Design ในหัวข้อที่แล้ว เพราะผมจะพูดถึง Command, Event, Result ในจังหวะการ Design Services ในตอนนี้เองเราจะเกิดสิ่งเหตุการณ์ดังต่อไปนี้ในการทำงานของ Services
- Command คือ การสั่ง Create (บันทึกข้อมูล)
- Event คือ Created (บันทึกข้อมูลเสร็จแล้ว)
- Result คือ Response (ฮ่าาาๆๆๆ )
เมื่อเราแยกแยะ จำแนก Command, Event, Result ของแต่ละ service ได้ครอบคลุมแล้วนั้นเราก็จะเห็นการทำงานของระบบในเชิง event ได้อย่างชัดเจน ยิ่งเราสามารถจำแนกได้ละเอียดเท่าไหร่เราก็จะเห็นการทำงานได้ชัดเจนมากขึ้นเท่านั้น และจะเป็นประโยชน์ในการเลือก Technology มา Implement การทำงานของแต่ละ service ได้ว่าเราจะกำหนดการสื่อสารระหว่าง services ด้วย API , Batch Files ,Kafka … ก็ว่ากันไป
Domain-Driven Design (DDD) บทสรุปที่แสนคลุมเคลือ Abstract สุดๆ:
ในการพัฒาระบบด้วย Domain-Driven Design (DDD) เรามักจะได้ยินคำนี้ CAP Theorem คู่กันไปด้วย ในเรื่องนี้เขาจะพูดถึงแนวคิดในการ Trade-off คุณสมบัติของ Distributed System ที่เราออกแบบ 3 ตัว คือ [C]onsistency, [A]vailability, [P]artition Tolerant โดย Theorem นี้จะบอกว่าเราไม่มีทางที่จะออกแบบระบบโดยมีคุณสมบัติ 3 ข้อนี้ และเมื่อพูดถึง “CAP” ผมก็อดนึกถึงพี่คนนึงที่มักยกตัวอย่างเรื่องของ “บริษัทสุธีการจำ” ไม่ได้ และนอกจากนี้ยังมีอีกหลายส่วนที่เราต้องนึกถึง เช่น
- เข้าใจโดเมนอย่างละเอียด: DDD ช่วยให้ทีมพัฒนาเข้าใจและมีความเข้าใจที่ลึกซึ้งเกี่ยวกับโดเมนองค์กร การใช้คอนเซ็ปต์ที่สร้างสรรค์และภาษาที่เข้าใจง่ายทำให้ทุกคนในทีมสามารถสื่อสารและทำงานร่วมกันได้สะดวกยิ่งขึ้น
- การทำงานที่มีเสถียรภาพ: DDD ช่วยให้เกิดโมเดลซอฟต์แวร์ที่มีเสถียรภาพและรักษาความสอดคล้องกับโดเมนในระยะยาว โดยการแยกแยะและแบ่งระบบออกเป็นโมดูลที่มีความเกี่ยวข้องกัน และใช้คอนเซ็ปต์ในโดเมนเป็นแนวทางในการออกแบบ
- ความยืดหยุ่นในการพัฒนา: DDD ช่วยให้สามารถพัฒนาโมเดลซอฟต์แวร์ในระดับย่อย (subdomain) แยกกันได้โดยอิสระ ทำให้การพัฒนาและปรับปรุงระบบเป็นไปอย่างรวดเร็วและมีความยืดหยุ่น
- ทำให้ระบบง่ายต่อการทดสอบ: โดยใช้คอนเซ็ปต์และโมเดลที่มีความเข้าใจง่าย ทำให้การทดสอบโมเดลซอฟต์แวร์เป็นไปอย่างมีประสิทธิภาพ และทีมพัฒนาสามารถพัฒนาและทดสอบโมเดลซอฟต์แวร์ได้อย่างมีประสิทธิภาพ
ส่งท้าย :
ในการพัฒนาระบบนั้นเมื่อเราทำงานกับหลายคน หลายที่มา หลายตำแหน่ง ผมว่าจำเป็นอย่างมากที่ทีมต้องสื่อสารไปในทางเดียวกัน และ Domain-Driven Design (DDD) เป็นแนวคิดและเซตของหลักการในการออกแบบและพัฒนาโครงการซอฟต์แวร์ที่มีเสถียรภาพ โดยให้ความสำคัญกับการเข้าใจและแก้ไขปัญหาในแต่โดเมนธุรกิจ การแยกแยะและแบ่งโดเมนออกเป็นส่วนย่อยที่มีความสอดคล้องกับกันในกระบวนการดพเนินธุรกิจ การใช้คอนเซ็ปต์และภาษาที่เข้าใจง่ายในแต่ละโดเมน การกำหนดขอบเขตและบริบทของแต่ละโดเมนธุรกิจ การออกแบบโมเดลซอฟต์แวร์ เพื่อให้ยืดหยุ่นในการพัฒนาในอนาคต ทำให้ DDD เป็นอีกเครื่องมือที่มีประสิทธิภาพในการพัฒนาซอฟต์แวร์อย่างมีหลักการ ทำให้ทีมพูดคุยไปในทางเดียวกัน เข้าใจตรงกัน เจรจาต่อรองง่ายเพราะเราพูดไปในทางเดียวกัน