Domain-Driven Design

Concepts & Strategic Design

Chatthana Janethanakarn
LifeatRentSpree
9 min readAug 18, 2021

--

สวัสดีครับ วันนี้ทาง DevSpree จะมานำเสนอ pattern หนึ่งในโลกของ software architecture อย่าง Domain-Driven Design ครับ ซึ่งจริงๆ แล้วเป็น pattern ที่มีมานานพอสมควร แต่กลับมาถูกพูดถึงมากขึ้นเมื่อ microservices ได้รับความนิยมในช่วงที่ผ่านมา โดยการนำ Domain-Driven Design มาใช้จะช่วยให้เราสามารถสร้าง distributed system ที่ดูสมเหตุสมผลและมีประสิทธิภาพสูงได้เป็นอย่างดี

ก่อนอื่นจะขอพูดถึงวิธีการสุด classic ในการพัฒนา software ที่คุ้นเคยกันเป็นอย่างดีก่อน โดยวิธีที่ว่านี้คือการมอง business requirement ผ่านโครงสร้างของข้อมูลต่างๆ ซึ่งในขั้นตอนการพัฒนา engineer ก็จะสร้าง table หรือ collection ที่มี field ต่างๆ ตาม requirement โดยจะมอง data เหล่านี้เป็น source of truth ของระบบ จากนั้นจึงทำการสร้าง service หรือ API ต่างๆ มาเรียกใช้ data เหล่านี้อีกต่อหนึ่ง ซึ่งรูปแบบของ data ก็จะแตกต่างกันตามแต่ละ requirement หรือ UI ในหน้าต่างนั้นๆ

ซึ่งข้อดีของการพัฒนาในลักษณะนี้คือมีความเร็วในการพัฒนาในช่วงเริ่มต้นที่สูงมาก เพราะโครงสร้างข้อมูลยังไม่ได้มีความซับซ้อน และ use case ภายในระบบก็ยังไม่ได้เยอะทำให้การเรียกใช้ data ต่างๆ เพื่อนำมาแสดงผลใน UI นั้นสามารถทำได้ง่าย

Small & Simple System

แต่เมื่อถึงจุดหนึ่ง เมื่อระบบใหญ่ขึ้น มี use case มากขึ้น จำนวน data และโครงสร้างข้อมูลมากขึ้น ระบบก็จะเริ่มมีความซับซ้อนขึ้นมา จนบางครั้งมันกลายเป็นความยุ่งเหยิง

เรื่องของความยุ่งเหยิง (Complexity)

เรื่องที่เป็นสัจธรรมของธุรกิจที่ขับเคลื่อนด้วยเทคโนโลยีเกือบทุกที่ คือ ความเรียบง่ายไม่ได้คงอยู่ตลอดไป ธุรกิจที่อยู่รอดคือธุรกิจที่เติบโต และการที่มันเติบโตนั้นแปลว่ามันจะต้องมี feature ที่เพิ่มเติมเข้ามาเพื่อรองรับการแข่งขันที่มากขึ้น

feature เหล่านี้ นับวันยิ่งเพิ่มมากขึ้น และทุกๆ ครั้งที่ feature ต่างๆ ถูกเพิ่มเข้ามา มันก็จะมีส่วนที่เชื่อมต่อกันมากขึ้นเรื่อยๆ จนถึงจุดหนึ่ง ระบบของเราก็จะดูยุ่งเหยิงไปหมดจนการทำความเข้าใจนั้น มันยากมากๆ โดยเฉพาะกับ engineer ที่เข้ามาใหม่ โดยระบบที่ยุ่งเหยิงและมีส่วนที่เชื่อมต่อกันวุ่นวายไปหมดจนจับต้นสายปลายทางไม่ถูกแบบนี้เราจะรู้จักในชื่อของ “Big Ball of Mud” ซึ่งหน้าตาของระบบก็จะมีความคล้ายกับรูปด้านล่างนี้

Big Ball of Mud

จะสังเกตได้ว่าระบบมีการพูดคุยข้ามไปข้ามมาระหว่าง module ต่างๆ เต็มไปหมด สาเหตุที่เป็นแบบนี้ก็เพราะว่าระบบต่างๆ จำเป็นต้องมีการเรียกใช้ data ที่อยู่ใน module อื่นๆ เช่น Module A ต้องใช้ข้อมูลที่อยู่ใน Module B และตัว Module B ก็อาจจะมีข้อมูลที่ต้องใช้ซึ่งอยู่ใน Module C อีกต่อนึง โดยวิธีการทั่วไปเมื่อมีความต้องการเรียกใช้ข้อมูลก็คือการทำให้ module ต้นทางที่เป็นเจ้าของข้อมูล expose method หรือ API ออกมาให้เรียกใช้

ปัญหาของเรื่องนี้คือหากเราออกแบบระบบได้ไม่ดีพอ ระบบของเราจะมี hard dependency ระหว่างกันในลักษณะ point-to-point สูงมากเพราะ data ที่มีอยู่กระจัดกระจายไปหมด ยิ่งถ้าการออกแบบระบบในลักษณะ distributed system อย่างเช่น microservices จะยิ่งพบปัญหาในลักษณะนี้ได้ง่ายมาก เพราะ business logic กับ data ที่ใช้อยู่กันคนละที่

ซึ่งหากไม่มีการแก้ไข หรือ ออกแบบระบบให้มีความสมเหตุสมผลมากขึ้น ณ จุดหนึ่ง เราจะพบว่า “หาก module หนึ่งมีปัญหาขึ้นมาจะทำให้ module อื่นๆ ที่มาเรียกใช้มันพังตามกันไปทันที” ดังที่เห็นในรูปด้านล่าง

มากไปกว่านั้นเราไม่สามารถคาดเดาอย่างแม่นยำได้เลยว่าจะเกิด side effect อะไรบ้างถ้ามีการแก้ไข module ใด module หนึ่ง ซึ่งถ้าระบบไหน unit test ไม่ cover ก็อาจจะไม่มีทางรู้ได้เลยว่าระบบไหนได้รับผลกระทบบ้าง

นอกจากเรื่องของระบบแล้ว การสื่อสารระหว่างคนที่ทำงานอยู่ใน project ก็เริ่มพบว่าการเพิ่ม feature หรือมี business concept ใหม่ๆ การพูดคุยกันยิ่งทำได้ยาก และนี่ถือว่าเป็นสิ่งที่เป็นปัญหาอันดับต้นๆ ในวงการ tech เลยก็ว่าได้ โดยเฉพาะเรื่องการตั้งชื่อ และเรียกชื่อสิ่งต่างๆ นั่นเอง

สิ่งที่เรามักพบบ่อยๆ คือเวลาที่ประชุมเพื่อเก็บ requirement ในฝั่งของ business ก็จะพยายามอธิบาย high-level concept ออกมา ซึ่งพอเอามาคุยกับ engineer ปุ๊บ engineer ก็มักจะนึกภาพของ data structure ที่อยู่ใน database ขึ้นมาเพื่อให้มันตอบโจทย์ requirement นั้นๆ แต่ปัญหาของเรื่องนี้ที่เกิดขึ้นอยู่บ่อยครั้ง คือการตีความที่ไม่ตรงกัน ซึ่งนำไปสู่ความเข้าใจที่ผิดพลาดระหว่างคนที่ทำงานร่วมกันอยู่เรื่อยๆ

และด้วยความยุ่งเหยิงแบบนี้ ไม่ว่าจะเป็นเรื่องการสื่อสาร ความซับซ้อน ทำให้ process ต่างๆ ที่เคยใช้ได้ดีในตอนที่ระบบยังไม่ซับซ้อนนั้นมันเริ่มจะไม่ตอบโจทย์การทำงานแล้ว ถ้าสงสัยว่าจะรู้ได้ยังไงว่าสิ่งที่ทำอยู่มันเริ่มไม่เข้าที่เข้าทาง ลองสังเกตอาการเหล่านี้

  1. การพัฒนา feature ใหม่ๆ ทำได้ช้า และช้าลงเรื่อยๆ
  2. Engineer ที่เข้ามาใหม่ (หรือแม้แต่คนเดิมๆ) เริ่มจะใช้เวลาในการทำความเข้าใจระบบมากขึ้น
  3. การทำความเข้าใจระบบเดิมที่มีอยู่ยากขึ้น
  4. แก้นิดแก้หน่อยพังหมด แก้ระบบหนึ่ง อีกอันหนึ่งพังเฉย
  5. จะเข้าหน้านึง query ซับซ้อนมาก join data กันเยอะ เพียงเพื่อจะ render view หน้าหนึ่งใน frontend

นี่คือความท้าทายของบริษัทที่ขับเคลื่อนด้วยเทคโนโลยีต้องเผชิญ โดยเฉพาะในช่วงหัวเลี้ยวหัวต่อของการเติบโต เพราะถ้าไม่จัดการกับความยุ่งเหยิงในการทำความเข้าใจตัวระบบแล้ว การเติบโตในอนาคตจะยิ่งยากและช้าลงเพราะข้อจำกัดด้านเวลาและ resource ที่ต้องมาเสียเวลากับความยุ่งเหยิงที่เกิดขึ้น

และ ณ จุดนี้เองที่ Domain-Driven Design จะเข้ามาช่วยเราได้ แต่ก่อนอื่นเราไปทำความรู้จักกับมันสักนิดก่อน

What is Domain-Driven Design?

Domain-Driven Design หรือ DDD เป็นปรัชญาและวิธีการในการพัฒนา software ที่ถูกคิดค้นขึ้นในปี 2003 โดย Eric Evans ซึ่งเป็น Software Architect ที่มีชื่อเสียงคนหนึ่งที่แม้แต่ลุง Martin Fowler เองก็พูดถึงอยู่บ่อยครั้งในช่วงที่แกอธิบายเรื่อง Event Driven Architecture

ซึ่ง Evans เองก็เขียนหนังสือที่แทบจะเรียกว่าเป็น bible ของ clean code ในยุคหลังๆเลย หนังสือเล่มนั้นมีชื่อว่า “Domain-Driven Design : Tackling complexity at the heart of software”

อย่างที่ชื่อหนังสือบอก ความซับซ้อนคือสิ่งที่ Domain-Driven Design จะเข้ามาช่วย แต่เดี๋ยวก่อน มันไม่ได้ช่วยให้เรากำจัดความยุ่งเหยิงและความซับซ้อนออกไป เพียงแต่เขามองว่าเราสามารถรับมือกับความยุ่งเหยิงเหล่านี้ได้ เพราะความซับซ้อนของระบบย่อมมีมากขึ้นเมื่อธุรกิจเติบโต มี user มากขึ้น feature มากขึ้น ยังไงก็หนีไม่พ้น เพียงแต่ Domain-Driven Design จะช่วยให้เราสามารถจัดการกับความยุ่งเหยิงเหล่านี้ได้อย่างเป็นระบบ

โดยแนวคิดหลักคือ แทนที่จะไปโฟกัสว่าจะใช้ technology อะไรมาตอบโจทย์ business ก็ให้ focus ไปที่ตัว business concept หรือที่เราเรียกว่า domain concept เลย แล้วแยก implementation ซึ่งเป็นเรื่อง technical เช่น database, frameworks, หรือ library ต่างๆ โดยจะมองส่วนของ data เป็น side-effect ที่เกิดขึ้นจาก event ต่างๆ ที่เกิดขึ้นในระบบ และมอง event ต่างๆ เป็น source of truth แทน

ซึ่งแบบนี้ วิธีการพัฒนาก็จะเปลี่ยนไปโดยสิ้นเชิง ลองนึกภาพถึง 3-tier architecture ซึ่งน่าจะเป็น architecture ที่ใช้แทบจะแพร่หลายที่สุดแล้ว ซึ่งจะเป็นการเอา business logic ทั้งหมดไปไว้ใน service layer แล้วการเรียกใช้ data ก็จะเรียกใน layer นี้รวมถึง validation logic ต่างๆ ด้วย แบบนี้ business logic จะผูกติดกับ technology โดยตรง ลองคิดว่าสมมุติอยากเปลี่ยน database technology จาก SQL เป็น NoSQL จะต้องแก้เยอะขนาดไหน

ในแนวคิดของ Domain-Driven Design คือ business concept หรือ domain concept ไม่ควรจะยึดติดกับ technology หรือ framework ซึ่งในปัจจุบันก็มี pattern ต่างๆ ที่ช่วยให้เราแยก business logic ออกจาก technology หรือ framework ที่เราใช้ได้ เช่น Clean Architecture หรือ Onion Architecture ซึ่ง concept ของ Domain-Driven Design นั้นยังถูกเอาไปใช้กับ event driven architecture อย่างเช่น การทำ CQRS หรือ Event Sourcing อีกด้วย

ซึ่งก่อนจะลงลึกไปมากกว่านั้น ต้องบอกก่อนว่า Domain-Driven Design ถูกแบ่งออกเป็นขั้นตอนหลักๆ 2 ขั้นตอน ได้แก่ Strategic Design กับ Tactical Design ซึ่ง Strategic Design นั้นจะโฟกัสกับการวิเคราะห์และทำความเข้าใจ business หรือ domain ของเราเพื่อแยก domain ออกมาเป็นสิ่งที่เรียกว่า bounded context กับ context map เพื่อเอาไป refactor code ซึ่งขั้นตอนการ refactor code นั้นจะอยู่ในขั้นตอนของ Tactical Design

ซึ่งในวันนี้เราจะมาพูดถึงส่วนของ Strategic Design กันครับ

Strategic Domain-Driven Design

ในเรื่องของการทำ Strategic Design นั้น เราจะโฟกัสที่การวิเคราะห์ high-level domain concept โดยจะมีบุคคลที่เกี่ยวข้อง 2 role หลักๆ ได้แก่

  • Domain Expert — ผู้ที่มีความรู้ความเข้าใจในตัวธุรกิจและอุตสาหกรรมที่บริษัทดำเนินการอยู่ รวมถึงมีความเข้าใจว่า high level business concept ของบริษัทเป็นอย่างไร (Flow ต่างๆ ในระบบ) ซึ่ง Domain Expert อาจเป็น Product Designer หรือคนที่มาจากฝั่ง Business ก็ได้ ขอเพียงมีความเข้าใจใน business model และ flow ต่างๆ ของระบบ
  • Technical Expert — ตรงจุดนี้อาจจะเป็น software engineer หรือ architect ซึ่งเป็นคนที่จะนำเอา domain concept ไปพัฒนาเป็น solution

ซึ่งในขั้นตอนนี้จะเป็นการรวบรวมเอา domain expert กับ technical expert มาพูดคุยกันเพื่อทำการวิเคราะห์และทำความเข้าใจ เรียบเรียง concept ต่างๆ ของธุรกิจ เพื่อกำหนดขอบเขตของของปัญหาที่จะต้องถูกแก้ไข(domain) รวมถึงจัดลำดับความสำคัญของส่วนต่างๆ เหล่านั้น เพื่อที่จะแบ่ง resource ไปอยู่ในส่วนงานที่เหมาะสมได้อย่างมีประสิทธิภาพ

การพูดคุยกันระหว่าง Domain Expert กับ Technical Expert ส่วนใหญ่มักจะจัดเป็น Workshop โดยรูปแบบหนึ่งที่นิมยมใช้กันมากในปัจจุบันคือ Event Storming สามารถอ่านเพิ่มเติมได้ที่ https://www.eventstorming.com/

โดยขึ้นตอนหลักๆ ของ Strategic Design มีดังนี้

  1. ทำความเข้าใจ domain ซึ่งเป็นปัญหาของลูกค้าที่ระบบของเราจะเข้าไปแก้ ในส่วนนี้เราอาจจะแยก domain ออกเป็น domain ย่อยๆ ที่เรียกว่า subdomain เพื่อโฟกัสกับปัญหาที่เล็กลงมาได้
  2. domain experts กับ technical experts พูดคุยถึง business concept และทำข้อตกลงว่าจะอธิบายในแต่ละส่วนงานอย่างไร และเรียกสิ่งต่างๆ ในระบบเหล่านี้ว่าอะไร เพื่อให้ได้ ubiquitous language ซึ่งเป็นภาษากลางที่ใช้ในขั้นตอนการพัฒนา
  3. แบ่ง bounded context หรือระบบงานย่อยๆ โดยแต่ละส่วนงานต้องมี concept ที่ชัดเจน ไม่ซ้ำซ้อนกับระบบงานอื่น
  4. สร้าง context map ซึ่งแสดงความสัมพันธ์ของ bounded context ทั้งหมด

เป้าหมายของ Strategic Design นี้คือการสร้าง bounded context ต่างๆ ซึ่งเปรียบเสมือนขอบเขตของระบบย่อยๆ ซึ่งเดี๋ยวเราจะกลับมาพูดถึงมันในหัวข้อถัดไปโดยละเอียดอีกครั้ง

ก่อนอื่น จะขออธิบายในเรื่องของ domain และ subdomain อีกทีก่อนนะครับ

Domain and Subdomain

จริงๆ แล้ว ถ้าเป็นคนที่ทำงานในวงการ tech จะได้ยินคำว่า domain หรือ subdomain นี้ค่อนข้างบ่อย ซึ่งถ้าไปเปิด dictionary จะอธิบาย domain ไว้แบบนี้

“A sphere of knowledge, influence, or activity”

อธิบายง่ายๆ คือ domain ก็คือปัญหาที่ธุรกิจของเราจะเข้าไปแก้ ไม่ว่าจะด้วยการออกแบบ software หรือวิธีการอื่นๆ ก็ตาม เป็นสิ่งที่สะท้อนปัญหาของลูกค้ากลุ่มเป้าหมายของเรา ยกตัวอย่างเช่น RentSpree เป็น platform ที่ช่วยอำนวยความสะดวกในการเช่าอสังหาริมทรัพย์ domain ของเราก็คือ rental management หรือ rental process facilitation หรือในระบบจัดการบัญชีออนไลน์ อาจจะมี domain เป็น accounting management เป็นต้น

ปัญหาที่เห็นได้เลยจากเรื่องนี้คือการออกแบบระบบโดยมองภาพใหญ่ที่มีเพียง model ชุดเดียวที่อธิบายธุรกิจได้ทั้งหมดแบบนี้ก็มักจะเป็นเรื่องยากมากๆ เพราะในความเป็นจริงแล้ว ธุรกิจหนึ่งก็มักจะมีรายละเอียดหรือฟังก์ชั่นแยกย่อยลงไปอีก ยกตัวอย่างเช่น บริษัทที่ทำธุรกิจเกี่ยวกับการเงิน อาจจะมีการแยกส่วนการทำงานลงไปเป็น การเงินส่วนบุคคล (personal finance) หรือ การเงินระดับองค์กร (corporate finance) หรืออาจจะมีการทำระบบบัญชี (accounting) ได้อีก

ดังนั้น โดยทั่วๆ ไปแล้ว เวลาจะออกแบบระบบ มักจะมีการแยกย่อย domain ออกเป็น subdomain ต่างๆ เป็นปัญหาย่อยๆ เช่น ระบบ e-commerce อาจจะมี subdomain ที่เรียกว่า storefront ที่เป็นระบบหน้าร้าน กับ sales promotion ที่จะจัดการระบบส่งเสริมการขายนั่นเอง ซึ่งจะเห็นได้ว่า พอเราแบ่ง domain ออกมาเป็น subdomain ย่อยๆ แล้ว การตีความมันง่ายขึ้นเยอะและที่สำคัญคือมันช่วยให้เราสามารถออกแบบระบบโดยโฟกัสปัญหาที่เล็กลงมาได้

ในบทความนี้ผมจะใช้คำว่า domain กับ subdomain สลับไปมาอยู่บ่อยครั้ง ซึ่งแท้จริงแล้วสองสิ่งนี้คือสิ่งเดียวกันครับ เพียงแต่ domain ที่เป็น subdomain จะต้องเป็นส่วนย่อยของ domain ที่ใหญ่กว่าตัวมันเองเสมอ

ซึ่งการแบ่ง domain จริงๆ อาจจะไม่ได้ง่ายแบบนี้ เพราะธุรกิจต่างๆ มีความแตกต่างกัน การจะแยก subdomain ให้มีประสิทธิภาพนั้นจะต้องมีความเข้าใจธุรกิจที่สูงในระดับหนึ่ง ไม่ว่าจะเป็นในเรื่องของโครงสร้างองค์กรหรือตัวระบบเอง หากเราตีความระบบผิดพลาดก็อาจจะสร้างมุมมองที่ผิดไปมากได้เลย ซึ่งนั่นส่งผลถึงการพัฒนา software ที่เมื่อสร้างมาแล้วมันไม่ตอบโจทย์ผู้ใช้นั่นเอง หากจะให้กล่าวง่ายๆ ก็เหมือนการมองปัญหาไม่ตรงจุด เมื่อมองไม่ตรงจุดก็ไม่สามารถแก้ปัญหาให้ตรงจุดได้

ซึ่งก่อนที่เราจะไปทำการหา subdomain เรามาทำความรู้จักถึงประเภทต่างๆ ของ domain กันก่อนดีกว่า เพราะนอกจากจะช่วยให้เราเห็นจุดที่เราจะใช้แยก domain ออกจากกันแล้ว มันยังช่วยให้เราสามารถจัดลำดับความสำคัญของแต่ละ domain ได้อีกด้วย

  • Core (sub)domain — เป็นส่วนที่สำคัญที่สุด เพราะเป็นส่วนที่เป็นฟังก์ชั่นหลักของธุรกิจ หากกล่าวในมุมมองของ business model ส่วนนี้คือกิจกรรมหลักขององค์กร และเป็นสิ่งที่เป็น competitive advantage ซึ่งองค์กรจะต้องทำให้ดีที่สุด เช่น ในระบบ e-commerce อาจจะมี catalog เป็น core domain เพราะถ้าไม่มี domain นี้บริษัทก็ไม่สามารถขายสินค้าได้ ซึ่งส่วนนี้จะเป็นส่วนที่บริษัทต้องทุ่มทรัพยากรลงไปให้มากที่สุด เช่นอาจจะมีการ enforce best practice ในหลายๆส่วนของการพัฒนา เช่น ทีมที่รับผิดชอบจะต้องนำเอา concept extreme programming มาใช้เพื่อให้ได้ code ที่มีคุณภาพ
  • Supporting (sub)domain — เป็นส่วนที่จะสนับสนุนการทำงานของ core domain ในมุมมองทางการตลาด สิ่งนี้จะทำให้เกิด augmented attribute ของ product นั้นๆ กล่าวคือจะทำให้ core domain ทำงานได้ดีมากขึ้น เช่นใน e-commerce อาจมี recommendation subdomain เพื่อแนะนำสินค้าให้ตรงกลุ่มเป้าหมาย ซึ่งจะช่วยให้ core domain อย่าง catalog ทำงานได้ดีขึ้นไป อย่างไรก็ตาม ส่วนนี้อาจจะไม่ได้เป็นส่วนที่สร้างรายได้หลักหรือเป็นกิจกรรมหลักให้กับองค์กร ซึ่งอาจจะไม่ได้จำเป็นต้อง enforce ในเรื่องของ best practice มากนัก โดยอาจจะพิจารณาจ้าง outsource มาทำได้เลย
  • Generic (sub)domain — ส่วนนี้สามารถไปหาซื้อ software หรือ service สำเร็จรูปที่มีในท้องตลาดมาปรับใช้ได้เลย ที่เห็นได้ชัดๆ ก็คือพวกระบบ notification อาจจะพิจารณาเอา SendGrid มาใช้ หรือถ้าอยากได้ระบบจัดการ user ก็อาจจะมอง solution อย่าง Auth0 หรือ Okta มาปรับใช้ได้เลย

ทั้งนี้เราควรจะพูดคุย ทำความเข้าใจ วิเคราะห์ปัญหาสำหรับ core domain ให้มากเพราะเป็นกิจกรรมหลักขององค์กร เป็นส่วนที่สร้างรายได้ส่วนใหญ่ ในส่วนอื่นๆ ก็อาจจะให้ความสำคัญรองลงมาได้ เพราะอย่างที่ทราบกันดีว่าเราไม่สามารถจัดสรร resource ที่มีอยู่จำกัดโดยให้ความสำคัญกับทุกๆ อย่างเท่ากันได้

Problem Space & Solution Space

อย่างที่เราทราบกันดีว่า domain นั้นก็คือปัญหาของลูกค้าหรือผู้ใช้ที่ software หรือระบบของเราจะเข้าไปแก้ปัญหา ซึ่งส่วนใหญ่ก็จะมีการแยก domain ออกมาเป็น domain ย่อยๆ อย่างที่กล่าวไปในหัวข้อก่อนหน้านี้

ในยุคหลังๆ มา software architect อย่าง Vaughn Vernon ที่นำแนวคิด Domain-Driven Design ไปต่อยอด ซึ่งได้แบ่งวิธีการตีความ domain ออกเป็น problem space กับ solution space โดยจำกัดขอบเขตให้ problem space โฟกัสไปที่การทำความเข้าใจปัญหาของลูกค้าเป็นหลัก

ในขณะเดียวกันก็แยกการมอง solution space ออกมาโดยโฟกัสไปที่การสร้างชุดของการแก้ปัญหา (solution) ที่อยู่ใน problem space หรือ domain ต่างๆ ที่อยู่ใน problem space อีกที ซึ่งในส่วนนี้จะเป็น process ในการนำปัญหาที่เราทำการวิเคราะห์ และทำความเข้าใจมาสร้างเป็น product หรือ software นั่นเอง โดย solution ที่ได้มานั้นก็จะอยู่ในรูปแบบของ bounded contexts ที่ประกอบไปด้วยระบบงานย่อยเล็กๆ หลายส่วน

ซึ่งแนวคิดนี้จะมีความคล้ายกับ process ใน design thinking เป็นอย่างมาก และสามารถนำมาประยุกต์ใช้ได้เป็นอย่างดี เพียงแต่ Domain-Driven Design จะโฟกัสไปที่การสร้าง software มากกว่า

Ubiquitous Language

อย่างที่เกริ่นไปตั้งแต่แรกว่าปัญหาที่พบมากใน enterprise หรือองค์กรที่เริ่มมีระบบที่ใหญ่ขึ้นมาในระดับหนึ่งแล้ว จะพบว่าจะมีส่วนงานหลายๆ ส่วนที่ทับซ้อนกัน domain บาง domain อาจถูกตีความออกเป็น concept ต่างๆ ได้หลายแบบมากๆ ซึ่งนี่คือปัญหาที่ใหญ่ในระดับองค์กรเลยทีเดียว

ตัวอย่างที่ง่ายที่สุดที่พอจะยกขึ้นมาอธิบายเรื่องนี้ได้ เช่น ในบริษัทที่ทำเกี่ยวกับธุรกิจการเงิน เราจะพบ concept ของคำว่า account ซึ่งลองนึกตามดีๆ หากเราไปพูดถึงคำว่า account นี้โดยเดินไปถามคนทุกคนในแต่ละแผนก พวกเขาจะตีความหมายเหมือนกันหรือเปล่า?

ถ้าเราไปถามคนที่ทำงานแผนกบัญชี เขาอาจจะมอง account นี้เป็น บัญชีประเภทต่างๆ ก็ได้ หรือถ้าไปถามคนที่ทำงานในแผนก customer support เขาอาจจะมองว่านี่หมายถึง user account ของลูกค้าก็เป็นไปได้

หรืออีกตัวอย่างที่ชัดเจนและพบบ่อยเวลา developer คุยกับ product designer หรือคนที่มาจากฝั่ง business ก็คือการใช้ technical term กับ business term ที่มักจะไม่ค่อยตรงกันเท่าไหร่ อย่างที่เกริ่นไปในช่วงแรก developer อาจจะมีคำที่ใช้เรียกสิ่งต่างๆ ซึ่งส่วนมากก็มาจากชื่อ table หรือ collection ใน database นั่นแหละ ส่วนคนที่มาจากฝั่ง business ก็จะเป็นอีกภาษาหนึ่ง เรียกสิ่งเดียวกันเป็นอีกอย่างหนึ่ง

เรื่องนี้ไม่ได้เป็นความผิดของใครทั้งนั้น การตีความหมายเป็นเรื่องที่ปัจเจกมากๆ แต่บ่อยครั้งเราก็ต้องมาพบ barrier ในเรื่องของภาษาที่ใช้เรียกสิ่งเหล่านั้นเวลาทำงานร่วมกัน

ดังนั้น ในโลกของ Domain-Driven Design จึงมี Ubiquitous Language และถือเป็น concept หลักที่จะช่วยให้เราสามารถอธิบาย domain concept ต่างๆ ได้ โดยยึดตัว Ubiquitous Language เป็นภาษากลางที่ใช้อธิบาย domain นั้นๆ และใช้เรียกในการทำงานร่วมกันของทุกคนที่เกี่ยวข้องใน domain นั้นๆ ด้วย ซึ่งจะครอบคลุมไปถึงคำต่างๆ หรือ business policy ที่ใช้งานด้วยเช่นกัน

กล่าวโดยสรุป ลักษณะโดยทั่วไปของ Ubiquitous Language มีดังนี้

  • สามารถเปลี่ยนแปลงได้ตามกาลเวลา ตามบริบทของธุรกิจและการตีความที่เปลี่ยนไป ไม่ได้ถูกคิดขึ้นมาครั้งเดียวแล้วใช้ไปตลอด
  • ต้องถูกใช้โดยทุกคนที่ทำงานในส่วนงานที่เกี่ยวข้อง เช่น engineer หรือ product designer
  • ถูกใช้ในการเขียน code ในส่วนที่เป็น domain model
  • ต้องไม่เป็น concept ที่ถูกคิดโดย engineer หรือ domain expert เพียงฝ่ายเดียว
  • ไม่จำเป็นต้องเป็นคำที่ใช้จริงในอุตสาหกรรมนั้นๆ ขอให้เป็นคำที่เข้าใจระหว่างผู้ที่ร่วมงานกันใน domain นั้นๆ เป็นอันใช้ได้
  • Concept หรือคำต่างๆ ที่ไม่เกี่ยวข้องควรถูกตัดทิ้ง

Bounded Contexts

อย่างที่กล่าวไปก่อนหน้านี้ว่า domain นั้นมีความหมายครอบคลุมและกว้างมาก เราไม่สามารถ(และไม่ควร) ใช้เพียง model ใหญ่ๆ เพียง model เดียวที่พยายามอธิบายทุกสิ่งทุกอย่างในธุรกิจของเรา เพราะมันคงวุ่นวายมากๆ ลองนึกภาพถ้าเราทำงานในองค์กรมหาชนขนาดใหญ่ที่มีพนักงานจากหลายแผนกร่วมหมื่นคน การทำความเข้าใจกับคนจากแต่ละส่วนงานเพื่อสร้าง Ubiquitous Language คงแทบเป็นไปไม่ได้ และต่อให้ทำก็คงไม่มีประสิทธิภาพเท่าไหร่ด้วย

ซึ่งในโลกของ Domain-Driven Design นั้น แทนที่เราจะไปหาทางที่จะอธิบายทุกสิ่งทุกอย่างใน domain ด้วย model เดียว เราก็แยก domain ต่างๆ นั้นออกมาเป็นส่วนเล็กๆที่เรียกว่า bounded context ซึ่งเจ้า bounded context จะเป็นขอบเขตของระบบหนึ่งที่แยกย่อยออกมาจาก domain โดยในแต่ละ bounded context นั้นจะมี domain model และ Ubiquitous Language ที่เป็นเอกลักษณ์เฉพาะที่ชัดเจน

ในส่วนนี้เองบางคนอาจจะสับสนว่า เอ๊ะ! แล้ว subdomain กับ bounded context นี่มันคือสิ่งเดียวกันหรือเปล่า?

ในส่วนนี้ให้มองว่า subdomain เป็นชุดของปัญหาที่อยู่ใน problem space แต่ bounded context เป็นชุดของการแก้ปัญหาอยู่ใน solution space ซึ่งตรงนี้หากเรามองแบบธรรมชาติแล้ว subdomain กับ bounded context อาจจะสามารถเชื่อมโยงกันแบบหนึ่งต่อหนึ่งได้

เรามี problem แล้วก็สร้าง solution ขึ้นมา แต่ในบางครั้งเราอาจจะพบว่าในบาง bounded context พอแยกออกมาจาก domain แล้วพบว่า domain concept ของมันไปทับซ้อนกับหลายๆ subdomain เช่น ในระบบ e-commerce มี subdomain product catalog กับ shopping cart ซึ่งอยู่ใน problem space แต่เรามี purchasing เป็น bounded context ที่เป็น solution ให้กับ subdomain ทั้งสองนี้ก็ได้ หรือในบางครั้งเราอาจจะมี bounded context มากกว่าหนึ่ง bounded context ภายใต้ subdomain เดียวกันในกรณีที่ subdomain นั้นมันกว้างจนไม่สามารถตีความรวมเป็นก้อนเดียวได้

ตัวอย่างในระบบ e-commerce

ซึ่งไม่ได้มีกฏตายตัวสำหรับเรื่องนี้ เนื่องจากการมอง domain และ bounded context นั้นเป็นเรื่องเฉพาะเจาะจงตามแต่ละธุรกิจจริงๆ โดยปกติแล้วเราจะพยายามให้ model ที่เกี่ยวข้องกัน หรือมี domain concept ไปในทิศทางเดียวกันอยู่ใน bounded context เดียวกันเพื่อลด depedency ระหว่างแต่ละ context

ในบางครั้งเวลาเราออกแบบ model เราอาจจะพบว่ามี model ที่ถูกแชร์หรือใช้ร่วมกันระหว่าง bounded context ต่างๆ

จากตัวอย่างด้านบนหากเรามองลึกลงไปถึงระดับ model เราจะพบว่ามี concept ของ product อยู่ในทั้ง purchasing และ personal sales bounded contexts ดังรูปด้านล่าง

ถึงแม้ว่าจะมี product model เหมือนกันใน 2 bounded context แต่การตีความในแต่ละ context นั้นแตกต่างกันโดยสิ้นเชิง ถ้าเรามองในมุมมองของ Personal Sales Context เราอาจจะสนใจแค่ข้อมูลของ product ที่เกี่ยวข้องกับการขายรายบุคคล เช่น สเป็คของสินค้าที่ Sales จะเอาไปขายให้กับลูกค้าในแต่ละภูมิภาค ในขณะที่ Purchasing Context อาจจะสนใจแค่ รหัสสินค้า(SKU) ราคา หรือจำนวนสินค้าที่ลูกค้าใส่ในรถเข็นเพื่อนำไปสร้าง order

จะสังเกตได้ว่าจริงๆ แล้ว bounded context ก็คือระบบๆ หนึ่งที่มี business / domain concept ของตัวเอง สามารถทำงานด้วยตัวเองได้ ในปัจจุบันหลายๆ บริษัทที่มีการประยุกต์ใช้ microservices ก็จะมอง bounded context เป็น microservices แต่ละ service เลย

ทั้งนี้ทั้งนั้น bounded context อาจจะเป็นเพียง module หนึ่งในระบบที่เป็น monolith ก็ได้เช่นกัน สิ่งที่ต้องพึงระลึกไว้เสมอคือ bounded context แต่ละตัวจะต้องมี ubiquitous language และ domain concept ที่ชัดเจน และต้องมี concept ที่ไม่ไปทับซ้อน หรือมีความหมายซ้ำซ้อนกับ bounded context อื่นหรือมีคำที่สื่อความหมายไปได้หลายอย่างปะปนอยู่ภายใน bounded context เดียวกัน

ซึ่งในความเป็นจริงแล้ว bounded context ต่างๆ อาจจะมีความสัมพันธ์ระหว่างกันได้ ซึ่งในโลกของ Domain-Driven Design ก็ได้มีการพูดถึงรูปแบบความสัมพันธ์ต่างๆของ bounded context เอาไว้ โดยความสัมพันธ์ของ bounded context นั้นจะมีดังนี้

  • Open-Host Service
  • Published Language
  • Customer / Supplier
  • Conformist
  • Anti-Corruption Layer
  • Separate Ways
  • Shared Kernel

โดยความสัมพันธ์ส่วนใหญ่จะอยู่ในรูปแบบของ upstream-downstream ซึ่ง upstream context คือ context หรือ service ที่ถูกเรียกใช้จาก downstream context

เนื่องจากรูปแบบความสัมพันธ์ของ bounded context นั้นมีรายละเอียดค่อนข้างมาก เลยจะขอแยกเป็นอีกหนึ่งบทความเพื่อพูดถึงเรื่องนี้โดยเฉพาะครับ

ซึ่งเมื่อเราสามารถกำหนดได้แล้วว่าแต่ละ bounded context มีความสัมพันธ์กันอย่างไร เราก็สามารถสร้างแผนภาพของระบบเราที่เรียกว่า Context Map ได้แล้ว

Context Map

Context Map

Context Map เป็นแผนภาพที่ใช้แสดงภาพรวมของระบบโดยจะแสดงความสัมพันธ์ระหว่างแต่ละ bounded context อย่างชัดเจน

ข้อดีของการมี context map คือมันทำให้เราเห็นภาพรวมของระบบรวมถึงตัวธุรกิจมากขึ้น เห็นส่วนต่างๆ ในระบบเป็น bounded context ต่างๆ รวมถึงยังช่วยให้เรามองเห็นปัญหาในภาพใหญ่อีกด้วย เช่น เราอาจจะเห็นว่ามีระบบหนึ่งซึ่งทำหน้าที่แชร์ข้อมูลผ่านทาง web service แต่ปรากฏว่าพอมาดูบน context map แล้วระบบที่เรียกใช้ตัวมันนั้นดันสร้าง anti-corruption layer มาใช้แทบจะทุกตัว แบบนี้เราก็จะรู้ทันทีว่าเราอาจจะต้องหาทางทำให้ระบบนี้เป็น open host service โดยการตกลงกับทีมที่เป็นฝ่ายเรียกใช้เพื่อทำความเข้าใจ requirement และสร้างชุดของ API ที่สามารถตอบโจทย์ context อื่นๆ ได้อย่างมีมาตรฐาน

เมื่อไหร่ที่เราควรนำ Domain-Driven Design มาปรับใช้

จริงๆแล้วการจะเลือกเอา architecture อะไรขึ้นมาใช้ภายในองค์กรนั้นมีหลายเรื่องที่ต้องนำมาพิจารณา เช่น โครงสร้าง วิธีการสื่อสารระหว่างทีมต่างๆ ภายในองค์กรหรือแม้แต่ culture ก็ยังมีส่วน

ซึ่งถ้าถามว่าแล้วเมื่อไหร่ที่ทีมควรจะเอา Domain-Driven Design มาปรับใช้ ก็อาจจะต้องย้อนกลับมามองดูว่าบริษัทของเราประสบปัญหาที่เวลาพัฒนา product หรือ feature ออกมาแล้วไม่ตรงตามความต้องการลูกค้าหรือไม่? over-engineer เกินไปหรือเปล่า? ซึ่งถ้าเจอปัญหา ณ จุดนั้น Domain-Driven Design ก็จะเข้ามาช่วยแก้ปัญหาตรงนี้ได้ เพราะตามที่ได้อธิบายไปว่าแนวคิดของ Domain-Driven Design นั้นคือการพัฒนา software โดยนำเอา business concept มาเป็นตัวตั้งต้น ทำให้ขั้นตอนการพัฒนาจะ align ไปกับ business โดยปริยาย

อย่างไรก็ตาม การทำแบบนี้ก็ต้องการความร่วมมือจากหลายส่วนในองค์กร หรืออย่างน้อยที่สุดก็ในระดับทีมหรือแผนกเพื่อร่วมกันทำความเข้าใจตัวธุรกิจเพื่อสร้างระบบที่ตอบโจทย์ธุรกิจให้มากที่สุด ซึ่งจะช่วยให้เราแยกปัญหาออกเป็นส่วนๆ เพื่อสร้าง solution ในแต่ละส่วนให้ดีที่สุดเท่าที่จะเป็นได้ แทนที่จะพยายามแก้ปัญหาโดยมองภาพใหญ่ๆ เพียงภาพเดียว ซึ่งเราสามารถนำเอา concept อื่นๆ มาร่วมใช้งานด้วยก็ได้ เช่น การนำเอาแนวคิด design thinking หรือ value proposition canvas มาช่วยในการทำความเข้าใจปัญหาของผู้ใช้เพื่อจะได้แบ่ง domain ออกเป็นส่วนๆ ได้อย่างมีประสิทธิภาพ

ดังนั้นจะเห็นได้ว่า Domain-Driven Design เหมาะกับธุรกิจหรือระบบที่มีความซับซ้อนในระดับหนึ่ง เช่นเริ่มมี business policy ที่ซับซ้อนและต้องอาศัยการทำความเข้าใจที่ลึกซึ้งมากยิ่งขึ้น ซึ่งการพัฒนาแบบ CRUD ที่เป็นการทำงานกับ data collection ตรงๆ เริ่มไม่ตอบโจทย์แล้ว ซึ่งแน่นอนว่าการจะทำ Domain-Driven Design นั้นจะค่อนข้างมีขั้นตอนที่หลากหลายและต้องอาศัยความเข้าใจทางธุรกิจที่มากพอสมควร

ซึ่งในส่วนนี้หากระบบของเราไม่ได้ซับซ้อนมาก การพัฒนาแบบทั่วๆ ไป เช่น CRUD นั้นก็ตอบโจทย์แล้วอาจจะไม่จำเป็นต้องนำ Domain-Driven Design มาใช้ก็ได้ แต่เมื่อไหร่ก็ตามที่เริ่มรู้สึกว่าการพัฒนานั้นเริ่มช้าลง การทำความเข้าใจระบบทำได้ยากขึ้น หรือไม่ว่าจะเพิ่มคนเข้ามาในทีมเท่าไหร่การพัฒนาก็ไม่ได้เร็วขึ้นอย่างที่คิดหรือถ้าเราต้องการสร้างระบบที่มี performance สูงๆ เช่นการนำเอา event driven architecture หรือ microservices มาใช้ การนำเอาแนวคิด Domain-Driven Design มาใช้ก็จะช่วยให้เราเข้าใจระบบและธุรกิจของเราในภาพรวม แยกปัญหาเป็นส่วนย่อยๆ และจัดสรร resource ในการพัฒนาระบบได้มีประสิทธิภาพมากยิ่งขึ้น

สรุป

การทำ Domain-Driven Design ในส่วนของ Strategic Design เป็นเหมือนจุดเริ่มต้นก่อนที่เราจะไป implement ในส่วนของการเขียนโค้ด ซึ่งตรงจุดนี้มีความสำคัญมาก เพราะถ้าเรามองโจทย์ หรือ domain ผิดพลาดไป ก็จะทำให้เราไม่สามารถสร้าง solution ที่ตอบโจทย์ได้ ดังนั้นในส่วนนี้จึงต้องใช้ความร่วมมือจากหลายส่วนในองค์กร ทั้งคนที่อยู่ในฝั่ง business และ technical เพื่อทำความเข้าใจปัญหาก่อน จากนั้นก็แยกปัญหาออกเป็นส่วนๆ แล้วค่อยหา solution ต่อไป

การทำ Domain-Driven Design นั้นไม่ได้เป็นเพียงเรื่อง technology เพียงอย่างเดียว แต่จริงๆ แล้วเป็นเรื่องที่แทบจะครอบคลุมทุกส่วนของบริษัทๆ หนึ่งเลย เพราะการที่เราสร้าง software ขึ้นมานั้นก็เพื่อแก้ปัญหาทางธุรกิจให้กับลูกค้าของเรา

แต่ในหลายๆ ครั้งเรากลับพบว่าเราพยายามเขียน software โดยเน้นไปที่ technical implementation มากกว่าที่จะโฟกัสไปที่ business concept ทำให้บางครั้งเราก็จะเจอปัญหาที่ว่า ทำได้ไม่ตรง requirement หรือ over-engineer เกินไปจนเกิดส่วนที่ทำมาแล้วไม่ได้ใช้ ซึ่ง Domain-Driven Design ก็คือ software architecture ที่จะมาอุดช่องโหว่ตรงนี้ให้เรา โดยให้เราโฟกัสไปที่การแก้ปัญหามากกว่าการเลือกใช้ technology

มากไปกว่านั้น Domain-Driven Design สามารถนำมาปรับใช้กับ pattern อย่าง microservices ที่นิยมใช้กันแพร่หลายในปัจจุบันได้ดีมากๆ ซึ่งมันจะช่วยให้เราแยก (decompose) microservices ได้อย่างมีประสิทธิภาพ มีขอบเขตการทำงานที่ชัดเจนและลดความซับซ้อนในภาพรวมได้อย่างมีนัยยะ ซึ่งในบางระบบที่ต้องการ performance สูงๆ ก็ยังสามารถต่อยอดไปสู่ event driven architecture อย่างพวก cqrs หรือ event sourcing โดยใช้ event ที่เกิดขึ้นในระบบเป็น source of truth ได้

และที่ RentSpree เรามอง Domain-Driven Design เป็น iterative process ซึ่งเราสามารถปรับ domain และปรับ bounded context รวมถึง context map ได้ตลอดตาม business strategy ที่อาจเปลี่ยนไปได้ตามกาลเวลาโดยเราจะเอา process เหล่านี้เข้ามาเป็นส่วนหนึ่งของ scrum ทั้งในส่วนของ strategic design และ tactical design ฃเพื่อให้เราสามารถทดลอง และเรียนรู้ รวมถึงปรับเปลี่ยนวิธีการทำงานของเราได้อย่างต่อเนื่อง

ซึ่งเวลาที่เรามี project หรือ epic ใหญ่ๆ ที่มี business rules ที่ซับซ้อน เราก็ทำในส่วนของ strategic design โดยจะมีการจัด Event Storming workshop เพื่อทำความเข้าใจ domain ก่อนที่จะเริ่มทำการ design ระบบทุกครั้ง

ในบทความต่อไป เราจะพูดถึง Domain-Driven Design ในมุมของ RentSpree ว่าเรามี process ในการทำ Domain-Driven Design อย่างไรบ้าง มีการประยุกต์ใช้ concept ต่างๆ ที่กล่าวมาใน process การทำงานของเราอย่างไร

สุดท้ายนี้ จะบอกว่าตอนนี้ RentSpree กำลังหาสมาชิกเพิ่มอยู่นะคร้าบ ถ้าอยากลองทำงานใน environment แบบ international หรืออยากเป็นส่วนหนึ่งของการสร้าง product ระดับโลกที่มีฐานลูกค้าอยู่ใน US ล่ะก็ เชิญที่ link นี้เลยครับ

(สามารถติดตามบทความที่น่าสนใจเกี่ยวกับ technology ได้ที่ Blog ของทาง RentSpree ได้นะครับ)

อ่านเพิ่มเติมเกี่ยวกับ Domain-Driven Design

--

--