Skip to content

Additional resource for describing how to mix clean and tactical DDD

Notifications You must be signed in to change notification settings

chrisza4/clean-ddd-mix

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Clean architecture mix with tactical DDD

จากที่พูดเรื่อง Clean Architecture กับ Domain Driven Design มา บ่อยครั้งจะมีคนถามว่า มันสามารถใช้ด้วยกันได้มั้ย

โค้ดเบสนี้เป็นตัวอย่างของการใช้ร่วมกัน

บริบท

เรากำลังทำระบบอนุมัติงานซื้อ โดยผู้ใช้สามารถส่ง Purchase Request ให้หัวหน้างาน หัวหน้างานสามารถกด Approve หรือ Reject ได้

ในการ Approve จะมีลำดับขั้นและเงื่อนไขเล็กน้อยว่า ถ้าใบขออนุมัตินี้รวมแล้วมีมูลค่าเกิน 100,000 บาท ต้องให้ CEO เท่านั้นที่จะอนุมัติได้

การผสมระหว่างสอง Concept

หากพูดถึงเนื้อแท้ของ Clean Architecture แล้ว มันคือ Dependency rules หมายถึงว่า Domain ต้องเป็นอิสระจาก Infrastructure และ Tech stack ทั้งหมด ส่วน Infrastructure อาจจะขึ้นอยู่กับ Domain ได้

  • โค้ดเบสนี้ถ้าเราดูใน Package domain เราจะพบว่า มันไม่มีการ Import package อื่นที่อยู่ข้างนอก Domain เลย ดังนั้น Domain เป็นอิสระจากทุกสิ่งแน่นอน
  • แล้วยังงั้น Domain จะเข้าถึงฐานข้อมูลได้ยังไง คำตอบคือให้ส่ง Repository เข้ามาทาง Constructor ของ PurchaseRequestService
  • ใน Domain ผมได้จงใจแยก Package ให้ชัดเจนระหว่าง Entities, Value object และ Service เพื่อให้เห็นภาพชัดว่าสิ่งไหนเป็นอะไรได้บ้าง นี่คือการทำตาม Tactical DDD

ดังนั้นโค้ดนี้จึงตรงเงื่อนไขของ Clean Architecture ที่ว่าในขอบเขตของ Domain ไม่พึ่งพาใครเลย และใช้เทคนิค Tactical DDD ในการเขียนออกแบบโค้ดใน Package Domain

ตรงนี้น่าจะเห็นภาพชัดว่าขอบเขตของ Clean คือตรงไหน และ Domain คือตรงไหน

จุดสังเกตที่น่าสนใจมากคือ แม้แต่ตัว Repository Interface นั้นถูกประกาศไว้ใน Package domain และ Implement ที่ Persistance ซึ่งเป็นการสื่อว่า Domain ควบคุมความต้องการ และทีมที่เขียนตัวต่อ Database ต้องล้อตามความต้องการของทีมที่เขียน Domain ที่ประกาศไว้ว่าฉันต้องการอะไบ้าง

จุดนี้เป็นการผลักดันหลักการของ Clean Architecture ไปอย่างที่สุด ในโลกอุดมคติของ Clean Architecture นั้นบอกว่าทีมโดเมนต้องเป็นใหญ่ กำหนดทิศทางของทุกสิ่ง แต่ในโลกจริงก็ต้องอาศัยการร่วมมือกันทำงาน บางครั้ง Database ที่ใช้มี Limitation เยอะมากจนทำให้ทีม Database ควรจะเป็นฝ่ายกำหนด Interface ใน Package ของตัวเองมากกว่า ก็ว่ากันไป แต่ผมอยากให้เห็นว่าในเชิงอุดมคติและ Clean Architecture อยากให้เป็นอะไรกันแน่

(จริงๆ ผมแอบมีการใช้งานส่วนที่ไม่ควรใช้ใน Domain เหมือนกันอย่างการเอา Exception ของ Framework มาใช้ ถ้าเป็น Clean แบบเพียวสุดๆ ผมต้องสร้าง Exception ขึ้นมาเองด้วย)

เราควรใช้มั้ย?

ผมเองไม่ค่อยชอบสอนสองคอนเซปต์นี้เข้าด้วยกันเพราะผมพบว่ามันจะทำให้สับสนได้ง่าย

การใช้สองคอนเซปต์นี้ด้วยกันอย่างเต็มที่ตามตัวอย่างนี้มันมีหลายๆ สิ่งที่ผมจะไม่ทำใน Project ทั่วไป

  1. ถ้าเชื่อใน Clean จริงๆ แม้แต่การใช้ Spring Bean เพื่อแก้ Dependency injection ใน Service ก็ทำไม่ได้ เพราะจะทำให้ Domain ไม่เป็นอิสระจาก Framework ทำให้ผมต้องเขียน DependencyResolver มาตัวนึง ในโลกความจริง ผมคงเลือกใช้ Spring Bean กับ Autowired ในการทำ Dependency Injection ซึ่งสำนัก Clean แบบสุดโต่งก็อาจจะบอกว่าทำแบบนี้ไม่ได้มันทำให้โดเมนขึ้นกับเฟรมเวิร์ค ย้ายไปใช้ในเฟรมเวิร์คอื่นไม่ได้นอกจาก Spring ซึ่งก็ถูก ผมก็ต้องให้ผู้อ่านนึกเอาเองหน้างานว่าโอกาสย้ายเฟรมเวิร์คมีเยอะขนาดไหนล่ะ การเขียน Dependency Resolving framework เองก็ต้องเขียนโค้ดเอง เขียนเทสเอง เขียน Documentation กำกับอีกมากมายเพื่อให้คนอื่นใช้เป็นอีก (ถ้าไปอ่าน Spring, MVC Dependency injection documentation จะเห็นว่าเอกสารหนาขนาดไหน) ความสามารถในการย้ายเฟรมเวิร์คคุ้มกับงานที่เพิ่มมั้ย ก็ว่ากันไปนะครับ
  2. การประกาศ Interface ใน Domain ผมพบว่ามันไม่ค่อย Practical ส่วนมาก Interface ของ ตัวต่อฐานข้อมูล ตัวต่อ Infra ต่างๆ มักจะถูก Drive ด้วย Limitation ของระบบ จึงทำให้ทีมที่กำหนด Interface มักไม่ใช่ทีมที่ทำ Domain
  3. ถ้าเราใช้ ORM อย่าง Hibernate เราจะใช้มันได้เฉพาะใน Persistance เท่านั้น (เพราะ Clean บอกว่าห้ามเอาพวกนี้เข้ามาไว้ในโดเมน) แต่ Entity บางทีหน้าตามันแทบจะเหมือนกับ ORM Object เลย เช่น ถ้าผมเอา ORM มาใช้กับโค้ดเบสนี้ Prodcut ORM ก็แทบจะหน้าตาเหมือนกับ Prodct Domain Object ทุกประการ ดังนั้นบางทีผมก็จะรวมมันเข้าด้วยกันไปเลย มาร์ก @Entity ไว้ใน Domain Object ซึ่งก็คือผมเบรคกฎของ Clean แต่ผมพบว่ามันทำงานง่ายกว่าในหลายๆ ครั้ง เพราะ Domain Object กับ ORM Object มักจะล้อไปด้วยกัน เช่น ฟิลด์ก็ต้องเพิ่มไปด้วยกัน เปลี่ยนชื่อก็เปลี่ยนชื่อไปด้วยกัน แต่อย่างที่บอกไว้แหละครับว่า Clean Architecture มันเกิดขึ้นในสมัยที่ Database กับ Third party อาจจะเป็นอะไรแปลกๆ อย่าง AS/400 หรือ SAP หรือ FoxPro ที่การเข้าถึงข้อมูลได้มันไม่ง่ายเท่า ORM+SQL ในปัจจุบัน ถ้าเคสแบบนั้นการแยก Infra ออกจาก Domain ให้ขาดมันก็เวิร์คจริง แต่ปัจจุบันมันเวิร์คมั้ยก็แล้วแต่ว่าคุณเข้าถึงฐานข้อมูลแบบไหน

ดังนั้นระหว่างที่ผมสอน Clean Arch หรือ DDD ผมจะไม่พยายามรวม เพราะพอรวมกันมันมี Dilemma ในการเรียนรู้

  • ถ้าสอนแล้วทำแบบไม่เพียวจริงแต่เหมาะกับงานส่วนมาก ก็จะงงว่าอ้าว ตกลง Clean Architecture คืออะไรกันแน่ ทำไมสอนเองแล้วไม่ทำตามกฎซะเอง
  • ถ้าสอนแล้วทำแบบเพียว ก็จะงงว่าทำไมมันต้องซับซ้อนขนาดนี้
  • ถ้าสอนแล้วทำแบบเพียวแล้วคนเรียนดันศรัทธาผมสอนไปเลยขึ้นมา ไม่งงไม่ถาม ก็อาจจะเข้าใจผิดว่ามันเหมาะกับงานทั่วไปนะ ซึ่งไม่ใช่ ผสมแบบเพียวนี้เหมาะกับงานที่ Specific มากๆ

ซึ่งด้วยสาเหตุนี้ ผมเลยคิดว่าการสอนวิธีผสมสองคอนเซปต์นี้เข้าด้วยกันให้กับมือใหม่พึ่งหัด มัน Do more harm than good แต่ผมก็ไม่แปลกใจที่คนเรียนเป็นจำนวนมากมักจะสงสัย ก็เลยขอทำเดโมนี้ไว้ตอบคำถามทีเดียว

คำถามที่ว่า เราจะใช้ Clean + DDD ได้มั้ย ผมตอบว่าได้ นี่คือตัวอย่าง

แต่ถ้าถามว่าดีมั้ย ก็ไม่ใข่ว่าของดีผสมของดีเท่ากับดีคูณสอง ผมพบว่าาถ้าเราไปสุดทางกับทั้งสองอย่าง แล้วันผสมกันเพียวขนาดที่ผมเขียนในโค้ดอันนี้ให้ดู มันจะเหมาะกับเคสที่แบบเราต้องการย้าย Framework เราต่อกับ Legacy project มากมาย ถึงต้องแบ่ง Boundary ดีๆ ซึ่งผมพบว่างานแบบนี้ไม่ใช่งานที่เจอได้บ่อยในการทำงานจริง

สำหรับงานที่เจอได้บ่อยอย่างสร้าง App นึง มี SQL ใช้ ORM ได้ นานๆ ใช้ 3rd-party ที อาจจะต้องมีการเบรค Heuristic บางอย่างบ้างไม่ผสมกันเพียวขนาดนี้ครับ (เช่น เอา ORM เข้าไปไว้ใน Domain object หรือยอมใช้ DI ของ Spring ก็เลือกตามความเหมาะสมครับ)

About

Additional resource for describing how to mix clean and tactical DDD

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages