DDD چیست؟
Domain Driven Design یک رویکرد در طراحی و توسعه نرم افزار است. تمرکز این رویکرد بر مدل سازی فضای مساله است. معمولا از این رویکرد برای طراحی و توسعه نرم افزار های استفاده می شود که فضای مساله آنها شامل قوانین بیزینسی پیچیده است و اصلا مناسب بیزینس با قوانین ساده نیست، به فرض مثال برای طراحی نرم افزار معاملات بورسی استفاده از DDD یکی از بهترین گزینه هاست اما برای توسعه نرم افزار بلاگ یه کلینیک پزشکی احتمالا گزینه مناسبی نیست.
مفاهیمی که DDD به آنها مکررا اشاره کرده
Domain: هر نرم افزاری ساخته می شود تا در حوزه خاصی خدمت رسانی کند و مشکلی را حل کند، این مشکل یا حوزه خاص، فضای مساله یا Domain نامیده می شود. Domain هر نرم افزاری دلیل وجود آن نرم افزار است.
اجزای تشکیل دهنده Domian شامل قوانین، اصطلاحات، فرآیندها و رفتارهای مربوط به حوزه ای است که نرم افزار برای حل یک مشکل خاص در آن حوزه ساخته شده است. به فرض مثال Domain یک نرم افزار فروشگاه اینترنتی شامل: فرآیند سفارش محصول، مشتری، ثبت نام مشتری، نحوه پرداخت، نحوه ارسال محصول و ... است.
Core Domain: فضای مساله معمولا آن قدر بزرگ است که داخل آن می توان مسائل مختلف را پیدا کرد اما در هر فضای مساله ای یک مساله مهمترین مساله موجود است، مساله ای که یک شرکت راه حلی برای آن مساله مهم دارد که مزیت رقابتی آن شرکت با رقبا است اصلا دلیل وجود پروژه نرم افزاری است که، شکل گرفته یا می خواهد شکل بگیرد. به این اصلی ترین مساله اصطلاحا Core Domain گفته می شود. به توصیه DDD باید بیشترین هزینه ای که در پروژه نرم افزاری می شود برای Core Domain باشد.
Model: یک Model تجسمی از واقعیت است اما نه تمام واقعیت. در واقع هر Model بخشی از دنیای واقعی را در بر می گیرد که مرتبط به یک مساله خاص است. خاصیت برجسته یک Model ساده سازی آن از یک مساله پیچیده است یعنی وقتی یک مساله ای را از دنیای واقعی به شکل Model تعریف می کنید بخش هایی از واقعیت را داخل Model درگیر می کنید که ضمن حل مشکل، آن مشکل را خیلی ساده داخل پروژه نرم افزاری تعریف می کند.
Eric Evans فرآیند Model سازی را به فیلم سازی تشبیه می کند. او می گوید وقتی یک کارگردان یک فیلمی را میسازد فقط بخش هایی را در فیلم نمایش می دهد که مربوط به موضوع فیلم است و از نمایش دادن تمام جزییات اضافی پرهیز می کند. برای درک بهتر این تشبیه، فیلم سینمایی را تصور کنید که یک اتفاق سه روزه را روایت می کند آیا مدت زمان آن فیلم هم 3 روز است؟ خیر. آن فیلم نهایت 2 ساعت است زیرا از نمایش تمام جزییات اتفاق پرهیز کرده. Model سازی هم به همین شکل است اگر بخواهید تمام جزییات مربوط به یک مساله را Model سازی کنید احتمالا هیچ گاه موفق نشوید اگر هم موفق بشوید آن Model احتمالا آن قدر درگیر جزییات شده که نمی تواند مساله اصلی را به خوبی به نمایش بگذارد. پس فقط قسمت هایی از مساله اصلی را داخل Model درگیر کنید که به حل مساله کمک می کند.
برای Model سازی بهتر می توانید از ابزاری مثل دیاگرام ها استفاده کنید. انواع دیاگرام های استاندارد مثل UML دیاگرام ها و حتی دیاگرام های غیر استاندارد مثل یک دیاگرام از ارتباط Modelهای مختلف که در جلسه تحلیل تیمی روی تخته White Board کشیده شده است می تواند در فرآیند Model سازی به شما کمک کند.
Domain Model: همانطور که در قسمت های قبلی گفته شد، Domain همان فضای مساله است و داخل این مساله چندین مساله مرتبط به یکدیگر وجود دارند و شما نیاز دارید که تمام این مسائل را تبدیل به Model هایی کنید تا بتوانید نرم افزار مورد نیاز را شکل دهید. به تمام اجزایی که داخل Domain هستند و برای آنها Model سازی می شود Domain Model گفت می شود.
Ubiquitous Language
یک زبان مشترک بین متخصصان تکنولوژی و متخصصان بیزینس. یکی از اهداف DDD در هر پروژه نرم افزاری ایجاد یک زبان مشترک بین ذینفعان پروژه (برنامه نویسان، تحلیگران، سهام داران، مشتریان و ...) است. مزیت ایجاد این زبان مشترک کاهش ابهام ها و سوءتفاهم ها در پروژه نرم افزاری، تسریع در روند توسعه پروژه در طولانی مدت، ارتباط موثرتر بین ذینفعان پروژه است. نکته ی مهمی که DDD در مورد زبان مشترک عنوان می کند این است که به وجود آمدن و تکامل آن زمان زیادی نیاز دارد.
توصیه های DDD برای پرورش و توسعه زبان مشترک:
- از Model طراحی شده به عنوان ستون فقرات ساخت زبان مشترک استفاده کنید.
- هر تغییر در زبان مشترک به معنی تغییر در Model و کد برنامه نویس می باشد.
- سعی کنید ارتباط بین افراد فنی و بیزینسی را بیشتر کنید.
- سعی کنید برای هر لغتی که در تعاملات تیم فنی و تیم بیزینسی استفاده می کنید تعریف مشخصی داشته باشید.
- برای یک مفهوم از دو لغت متفاوت استفاده نکنید.
- برای پیدا کردن بهترین لغت و بهترین تعاریف سعی کنید از لغات و تعاریف جایگزین هم استفاده کنید سپس بهترین لغت و بهترین تعریف برای پرورش زبان مشترک به کار بگیرید.
- سعی کنید از زبان مشترک در هر مکالمه ای که مربوط به پروژه است استفاده کنید.
- از زبان مشترک در کد استفاده کنید، برای انتخاب نام متغییر ها، کلاس ها، متد ها سعی کنید از لغاتی که در زبان مشترک هستند استفاده کنید.
- در تمام داکیومنت های مربوط به پروژه اعم از داکیومنت های فنی و بیزینسی از زبان مشترک استفاده کنید.
Ubiquitous Language و Model و Code سه ضلع یک مثلث در DDD هستند که همیشه باید حواستان باشد در طول عمر پروژه این سه ضلع کنار هم حفظ شوند. اگر به هر دلیلی هر کدام از این سه ضلع دچار تغییر شوند نیاز به بازبینی دو ضلع دیگر برای اعمال تغییر در صورت نیاز است.
به یاد داشته باشید که برنامه نویسان و افراد فنی پروژه یک سری دایره لغات مخصوص به تخصص خود دارند و افراد متخصص کسب و کار هم یک سری دایره لغات مخصوص به کسب و کار دارند که هیچ ارتباطی به پروژه ندارد نکته مهم این است که این دایره لغات تخصصی فنی و بیزینسی هیچ جایی در زبان مشترک ندارند و تنها لغات، اصطلاحات، تعاریف و مفاهیمی که افراد بیزینسی و فنی هر دو باهم نیاز دارند بدانند تا بتوانند پروژه را توسعه دهند باید در زبان مشترک به کار گرفته شود.
Communication and Collaboration
ارتباط و تعامل درون تیمی تاثیر بسزایی در موفقیت یک پروژه نرم افزاری دارد، از همین رو در کتاب DDD به این موضوع به دفعات اشاره شده است. فرآیند شکل گیری یک پروژه نرم افزاری بدین شکل است که یک نیازمندی از سمت صاحب کسب و کار به تیم بیزینس اعلام میشود سپس تحلیل گران و متخصصان کسب و کار آن نیازمندی را بررسی و تحلیل می کنند و یک سری داکیومنت آماده می کنند سپس آن داکیومنت در اختیار تیم توسعه قرار می گیرد و نرم افزار مورد نظر شکل میگیرد. اما چالش این فرآیند کجاست؟ چالش ها:
- عدم قطعیت صاحب کسب و کار از چیزی که درخواست داده است
- عدم انتقال مناسب خواسته ها بین افراد
- تعامل کم بین افراد تیم
- مقاومت افراد نسبت به تغییرات پروژه
- عدم آگاهی تمام اعضای تیم نسبت به فضای مساله
- علاقه اعضای فنی تیم به کارهای فنی و تکنولوژی و عدم علاقه آنها به بیزینس و تحلیل
در بیشتر مواقع درخواستی که از سمت صاحبان کسب و کار اعلام نیاز می شود به اندازه کافی پخته و کامل نیست و خیلی طبیعی است که بعدها این خواسته دچار تغییر و تحول شود. فارغ از بحث بالغ بودن درخواست صاحبان کسب و کار، قدرت درک افراد و قدرت انتقال آنها از درخواست بسیار مهم است. فرض کنید یک درخواستی از سمت صاحب کسب و کار رسیده که در حال حاضر 60 درصد این نیازمندی پخته و کامل است و هنوز 40 درصد باقی شفاف نیست، حال اگر تحلیلگر تنها 40 درصد از آن 60 را درک کند و باقی آن را متوجه نشود و هنگام توضیح این درخواست به تیم توسعه برای پیاده سازی نتواند به صورت کامل آن چیزی را که متوجه شده توضیح بدهد در نهایت نتیجه این است که تیم فنی ممکن است به اندازه 10 درصد از نیازمندی صاحب کسب و کار را درک کرده باشد پس در نتیجه چیزی که در نهایت توسط تیم توسعه پیاده سازی می شود با آنچه که صاحب کسب و کار درخواست داده تفاوت بسیار زیادی دارد. بنابراین تحلیلگری که نیازمندی صاحب کسب و کار را بررسی می کند باید به خوبی و کامل نیازمندی اعلام شده را درک کند و تمام جوانب آن را بسنجد، اگر در جایی تناقصی دید با صاحب کسب و کار صحبت کند و اگر توانست به پخته تر شدن درخواست اعلامی کمک کند سپس این درخواست را به تیم فنی منتقل کند.
اما حتی اگر تحلیل باعث پخته شدن درخواست شود و نیازمندی که به تیم فنی اعلام می کند کامل تر باشد باز هم بسیار محتمل است که این درخواست تغییر کند و تغییر در نیازمندی ها یعنی تغییر در کد پیاده سازی شده یعنی کار بیشتر برای افراد فنی تیم پس طبیعی است که افراد فنی در برابر تغییراتی که از سمت صاحبان کسب و کار اعلام نیازمندی می شود مقاومت داشته باشند. برای کمتر کردن این مقاومت، تحلیگر کسب و کار باید نیازمندی هایی که از طرف صاحبان کسب و کار اعلام می شود را به خوبی بررسی کند و اگر می تواند به کامل تر شدن درخواست کمک کند تا بعدا این نیازمندی کمتر دچار تغییر و تحول شود.
یکی از مواردی که تغییر را سخت می کند، تغییر در چیزی است که آگاهی کمی نسبت به آن داریم. این موضوع در مورد پروژه های نرم افزاری هم صدق می کند یعنی یک توسعه دهنده، نرم افزاری را که نسبت به فضای مساله آن آگاه تر است راحت تر می تواند تغییر دهد. بنابراین هر چقدر آگاهی اعضای تیم نسبت به فضای مساله بیشتر باشد، تغییرات در زمان کمتر و با آگاهی بیشتر صورت می پذیرد. همچنین تغییر در یک قسمت نرم افزار باقی قسمت های نرم افزار را هم تحت تاثیر قرار می دهد و توسعه دهنده ای که تغییر را اعمال می کند اگر نداند این تغییر چه قسمت هایی را تحت تاثیر قرار می دهد ممکن است باعث به وجود آمدن یک فاجعه شود پس هر چقدر آگاهی اعضای تیم نسبت به فضای مساله پروژه نرم افزاری بیشتر باشد تغییرات کم هزینه تر خواهند بود.
معمولا اعضای فنی تیم علاقه زیادی به تکنولوژی و کدنویسی دارند و علاقه کمی نسبت به مسائل مربوط به کسب و کار دارند. از همین رو معمولا توسعه دهندگان علاقه دارند کدهایی که بیشتر درگیر تکنولوژی هستند را توسعه دهند و کارهای مربوط به تحلیل فضای مساله را به تحلیل گران واگذار کنند. همین موضوع باعث فاصله گرفتن توسعه دهندگان از فضای مساله و فضای کسب و کار می شود که نتیجه آن این است که نرم افزار پیاده سازی شده با نیازمندی های اعلام شده از سمت صاحبان کسب و کار تفاوت های زیادی دارد و نرم افزار پیاده سازی شده آن چیزی نیست که صاحبان کسب و کار درخواست داده اند.
راه حل تمام این چالش ها ارتباط و تعامل مداوم بین اعضای تیم فنی و تیم تحلیل گران و صاحبان کسب و کار است. هر چقدر این تعاملات بیشتر باشد نیازمندی های اعلام شده بیشتر بررسی می شوند و قوانین کسب و کار بین افراد بیشتر مورد گفتگو قرار میگیرند و همه این ها باعث آگاهی بیشتر تمام افراد تیم نسبت به فضای مساله و فضای کسب و کار می شود و هرچقدر این آگاهی بیشتر باشد نرم افزاری که توسط تیم فنی پیاده سازی می شود نزدیک به خواسته ی صاحب کسب و کار است و همین موضوع باعث موفق شدن یک پروژه نرم افزاری می شود.
Iterative Development and Refactoring
Refactoring به معنی باز طراحی نرم افزار یا بخشی از نرم افزار به طوری که عملکرد آن تغییر نکند است. هر تغییر، بدون تغییر در عملکرد موجود، طراحی را انعطاف پذیرتر یا قابل فهم تر می کند.
بیشتر اوقات نمی توان برای شروع یک پروژه نرم افزاری نسبت به تمام فضای مساله آگاه بود و بعد شروع به پیاده سازی نرم افزار کرد. شما در ابتدای پروژه نرم افزاری چیزهای کمی در مورد Domain می دانید و سطح پایینی از دانش مربوط به فضای مساله دارید و باید با همین دانش کم پروژه نرم افزاری را شروع به پیاده سازی کنید.
اگر قرار است با سطح دانش کم پروژه نرم افزاری را شروع کنید پس چگونه نرم افزار تکمیل می شود؟
پیاده سازی نرم افزار یک چرخه است که از نقطه اعلام نیازمندی از سمت صاحب کسب و کار شروع می شود و در نقطه نرم افزار پیاده سازی شده پایان می یابد، اما نقطه پایان به معنی اتمام کار نیست چون این یک چرخه است، نقطه پایان برای این است که بدانید شما یک دور از توسعه نرم افزار را تمام کرده اید و نوبت شروع دور بعد است و آنقدر این چرخه را تکرار می کنید تا نرم افزار به بلوغ برسد و نزدیک ترین نسخه به آن چیزی که صاحب کسب و کار درخواست داده باشد.
آیا هرباری که دانش جدیدی نسبت به فضای مساله به دست می آوریم باید شروع به Refactoring کنیم؟ خیر
وقتی یک نیازمندی از سمت صاحب کسب و کار اعلام می شود تیم تحلیل آن را تحلیل می کنند و دانش اولیه را پرورش می دهند سپس با کمک تیم فنی دانش به دست آمده را Model سازی می کنند سپس تیم فنی Model به دست آمده را به صورت کد در پروژه پیاده سازی می کند. پس کد نمایشی از Model و دانش مربوط به فضای مساله است. شما در دو شرایط باید پروژه را Refactor کنید:
- شرایطی که می توانید طوری کد را Refactor کنید که نمایش بهتری از Model باشد.
- شرایطی که بینش عمیق تری نسبت به فضای مساله به دست آورده اید و می توانید Model را طوری تغییر دهید که Domain را کامل تر توصیف کند.
پس هربار که دانش جدیدی نسبت به فضای مساله به دست می آوریم فقط در صورتی که یکی از دو شرط بالا برقرار باشد می توانیم پروژه را Refactor کنیم.
تعامل مداوم اعضای تیم باعث به وجود آمدن بینش عمیق نسبت به Domain در اعضای تیم می شود و بینش عمیق باعث تغییر Model در راستای توصیف کامل تر Domain می شود و برای نمایش بهتر Model در کد فرایند Refactoring شروع می شود.
موارد Implicit و Explicit
یکی از مواردی که از مانع بینش عمیق افراد تیم نسبت به Domain می شود موارد غیرصریح (Implicit) در پروژه است. یکی از مواردی که باید تلاش زیادی برای آن صورت بگیرد تبدیل موارد غیر صریح به موارد صریح (Explicit) در پروژه است.
موارد غیرصریح (Implicit) مواردی هستند که از وجود آنها باخبر هستیم اما به صورت شفاف در کد و Model قرار ندارند. این موارد باید شناسایی شوند و به صورت مستقل در زبان مشترک، کد و Model به کار گرفته شوند.
مثال: فرض کنید در پروژه یک فروشگاه اینترنتی میخواهیم قانونی داشته باشیم که اگر هر مشتری سبد خریدی با بیش از مبلغ 100 دلار داشت کل سبد خرید شامل 10 درصد تخفیف شود. این مثال را با دو مدل Implicit و Explicit میخواهیم پیاده سازی کنیم:
اگر Implicit باشد:
public decimal CalculateCartPrice(List<Item> items){
//some code
//some code
//some code
var sumPrice = items.sum(p => p.Price);
if(sumPrice >= 100)
return sumPrice - sumPrice/10;
else
return sumPrice;
}
اگر Explicit باشد:
public decimal CalculateCartPrice(List<Item> items){
//some code
//some code
//some code
var sumPrice = items.sum(p => p.Price);
var canApplyDiscount = CanApplyDiscount(sumPrice);
if(canApplyDiscount == true)
return ApplyDiscount(sumPrice);
else
return sumPrice;
}
public bool CanApplyDiscount(decimal price){
if(price >= 100)
return true;
else
return false;
}
public decimal ApplyDiscount(decimal price)
{
return price - price/10;
}
همان طور که مشاهده می کنید در هر دو نمونه قانون تخفیف 10 درصدی اعمال شده و به درستی کار می کند، اما نمونه Explicit این قانون را بسیار شفاف تر به نمایش گذاشته است.
معایب Implicit بودن مفاهیم:
سخت شدن یادگیری Domain و Model
سخت شدن نگهداری کد
برنامه نویسان جدیدی که به تیم ملحق می شوند ممکن است از وجود چنین مفاهیمی ناآگاه باشند و به دلیل شفاف نبودن این موارد تا مدت ها از وجود این قوانین ناآگاه بمانند
پنهان بودن پیچیدگی های Domain در این موارد که در طولانی مدت باعث عدم توسعه پذیری پروژه میشوند
با تبدیل این موارد به مفاهیم Explicit می توان این معایب را در پروژه از بین برد.
آیا باید تمام موارد Implicit را تبدیل به Explicit کرد؟ خیر
همان طور که در دو نمونه بالا مشاهده کردید اگرچه مورد Explicit خواناتر بود اما باعث شد که کد بیشتری نوشته شود. پس اگر بخواهیم تمام موارد Implicit را تبدیل به Explicit کنیم بیش از حد به جزییات پرداخته ایم و تمام موارد غیر مهم را نیز به پروژه اضافه کرده ایم. بنابراین هر آن چیزی که مربوط به Core Domain است یا جزو قوانین کسب و کار محسوب می شود باید به صورت Explicit در کد و Model پیاده سازی شود.
خب برای شروع تا حدودی با DDD آشنا شدید. از اینجا به بعد جزییات DDD را به صورت تخصصی بررسی میکنیم.