چرا برخی پروژههای نرمافزاری، علیرغم بهرهگیری از فناوریهای پیشرفته، در تحقق اهداف خود ناکام میمانند؟ یکی از دلایل اصلی، نادیده گرفتن درک عمیق از دامنه مسئله است. «طراحی دامنهمحور» رویکردی است که با تمرکز بر مدلسازی دقیق منطق کسبوکار و همکاری مستمر میان توسعهدهندگان و ذینفعان، تلاش میکند این شکاف را پر کند.
این روش به جای تمرکز صرف بر پیادهسازی فنی، ابتدا به فهم درست نیاز کاربران و ساختارهای مفهومی میپردازد و سپس آنها را به زبان نرمافزار ترجمه میکند. در این مقاله با اصول طراحی دامنهمحور، اجزای کلیدی آن و تاثیر آن بر بهبود کیفیت نرمافزار آشنا خواهیم شد.
DDD یا Domain Driven Design چیست؟
طراحی دامنه محور یا Domain Driven Design، برای اولین بار در سال ۲۰۰۳ در کتابی به همین نام و توسط اریک ایوانز (Eric Evans) مطرح شد و توجه جامعه نرمافزاری را به خود جلب کرد. در اصل DDD یک نوع تفکر یا رویکردی برای تولید و توسعه نرمافزارهای بزرگ، با فرایندها و قوانین زیاد و پیچیده است که از مرحله تحلیل تا کدنویسی یک محصول همراه ما است و در دو قسمت استراتژیک و تکنیکال به ما ایده میدهد.
- دامنه (Domain): دامنه به موضوع یا مسئلهی اصلیای گفته میشود که نرمافزار قرار است آن را حل کند. مثلا در یک نرمافزار بانکی، دامنه شامل مفاهیمی مثل حساب بانکی، تراکنش، مشتری و قوانین بانکی میشود.
- محور بودن (Driven): واژهی «محور» یعنی طراحی سیستم بر اساس نیازهای واقعی دامنه انجام میشود، نه صرفا بر پایهی ملاحظات فنی.
در واقع، منطق و ویژگیهای دامنه، مسیر طراحی را تعیین میکنند. - طراحی (Design): طراحی به معنای برنامهریزی برای ساختار کلی سیستم نرمافزاری است؛ اینکه اجزای مختلف چطور با هم تعامل داشته باشند و سیستم چطور نیازهای کاربران را برآورده کند.
البته اریک ایوانز در کتاب خود بیشتر به مفاهیم استراتژیک میپردازد و تمرکز اصلی او بر بیزینس یا حوزه اصلی نرمافزاری که میخواهیم بنویسیم، یعنی Domain است. در واقع کار DDD از یک خواسته یا مشکل کسبوکاری (Problem Domain) شروع میشود. او Domain را این گونه شرح میدهد:
«محدودهای که کاربر برای برنامه اعمال میکند.»
طراحی دامنهمحور عمدتا برای محصولاتی مناسب است که از پیچیدگیهای بالای کسبوکار برخوردارند. بهکارگیری این رویکرد در پروژههای کوچک، ساده یا سیستمهایی که صرفا وظیفه ذخیره و بازیابی دادهها را دارند و فاقد منطق پیچیده کسبوکار هستند، نهتنها مزیت قابلتوجهی ایجاد نمیکند، بلکه ممکن است منجر به افزایش غیرضروری زمان و هزینه توسعه شود.
چرا طراحی دامنهمحور (DDD) اهمیت دارد؟
در پروژههای نرمافزاری پیچیده، تنها دانستن نحوه ذخیرهسازی اطلاعات کافی نیست. آنچه اهمیت دارد، درک عمیق از منطق کسبوکار و طراحی سیستمی است که دقیقا با آن منطق همراستا باشد. طراحی دامنهمحور بر همین اصل بنا شده است:
این رویکرد پیشنهاد میدهد که طراحی نرمافزار باید از شناخت دامنه شروع شود، نه از دیتابیس یا ساختارهای فنی. در DDD، مفاهیم واقعی کسبوکار (مثل «سفارش»، «پرداخت»، «مشتری») مستقیما به مدل نرمافزاری ترجمه میشوند. این یعنی منطق سیستم نه در دیتابیس، بلکه در «مدل دامنه» زندگی میکند.
نتیجه این رویکرد، سیستمی است که بهتر قابل درک، انعطافپذیرتر در برابر تغییرات، و قابل توسعه توسط تیمهایی است که به زبان مشترکی بین برنامهنویس و متخصص کسبوکار دست پیدا کردهاند. به همین دلیل است که طراحی دامنهمحور، بهویژه در سیستمهای پیچیده و بلندمدت، نقش کلیدی در موفقیت پروژه دارد.
کاربردهای DDD (طراحی دامنه محور)
DDD روی تعامل سازنده و بهینه میان برنامهنویسها و افراد متخصص دامنه یا Business Experts تاکید دارد. به همین دلیل ایجاد یک زبان مشترک به نام Ubiquitous Language در مورد مفاهیم دامنه، ضروری است. این زبان مشترک هم در مستندات تحلیل و هم در کد، دیده میشود. در اصل یکی از قدرتهای طراحی دامنه محور، استفاده از زبان مشترک است. برای مثال به دو بخش زیر دقت کنید، تصویر اول یک سناریو برای ارسال و تحویل پیتزا و بخش دوم پیادهسازی متد همین سناریو است. همانطور که میبینید، در هر دو تصویر سعی بر این است که از زبان مشترک استفاده شود.
مستند سناریو تحویل پیتزا:
پیادهسازی سناریو تحویل پیتزا:
1 2 3 4 5 6 7 8 9 10 |
public class Guaranteed30MinuteDeliveryOffer { public void After(PizzaDelivered delivery) { if (delivery.TimeTaken.Exceeded(thirtyMinutes)) { sendCouponTo(delivery.Customer); } } } |
در واقع DDD همه چیز را میشکند و به بخشهای کوچکتر تقسیم میکند تا برخورد با آنها سادهتر باشد؛ مثل Knowledge Crunching (شکستن دانش) یا شکستن دامنه به چند SubDomain یا ارائه راهکارهایی برای تقسیم نرمافزار به بخشهای جدا و مستقل از هم و تبیین ارتباط این بخشها با یکدیگر. این موضوع باعث میشود فرایند توسعه نرمافزار، بهصورت موازی بین چند تیم انجام شود و همچنین معماران سیستم را قادر میسازد تا از معماریها و تکنولوژیهای مختلف در بخشهای مختلف استفاده کنند.
اجزای اصلی مدل دامنه در DDD
رویکرد طراحی دامنهمحور، بهمنظور مدلسازی دقیقتر مسائل پیچیده در نرمافزار، بر مفاهیم مشخصی تکیه دارد که هر یک نقش ویژهای در سازماندهی منطق دامنه ایفا میکنند.این اجزا در واقع همان الگوهای تاکتیکی Domain-Driven Design هستند که برای طراحی دقیقتر منطق دامنه استفاده میشوند و عبارتاند از:
۱. موجودیت (Entity): موجودیتها اشیائی در دامنه هستند که هویت منحصربهفردی دارند و این هویت در طول زمان و حتی با تغییر ویژگیهای آنها، حفظ میشود. برای مثال، «کاربر» با شناسه یکتا (ID) یک موجودیت است، حتی اگر نام یا آدرس ایمیل او تغییر کند.
مثال: در یک سامانه فروش آنلاین، «کاربر» یک موجودیت محسوب میشود. هر کاربر دارای شناسه یکتایی (مثلا user_id) است که او را از سایر کاربران متمایز میکند، حتی اگر مشخصاتی مانند نام یا آدرس ایمیل وی تغییر کند.
۲. شیء ارزش (Value Object): اشیای ارزش، فاقد هویت مستقل هستند و تنها بر اساس ویژگیهایشان تعریف میشوند. این اشیا اغلب به صورت غیرقابل تغییر (immutable) طراحی میشوند. به عنوان نمونه، «موقعیت جغرافیایی» یا «مبلغ پولی» میتوانند Value Object باشند.
مثال: در همان سامانه فروش، «آدرس تحویل کالا» یک Value Object است. اگر دو آدرس دقیقا یکسان باشند، سیستم آنها را برابر در نظر میگیرد. در صورت نیاز به تغییر، نسخهای جدید از آدرس ایجاد میشود.
۳. ریشه تجمیع (Aggregate Root): تجمیعها گروهی از اشیا مرتبط (موجودیتها و اشیای ارزش) هستند که به عنوان یک واحد یکپارچه عمل میکنند. ریشه تجمیع، مسئول حفظ یکپارچگی و اعتبار (consistency) کل مجموعه است. تنها از طریق ریشه تجمیع میتوان به سایر اعضای آن دسترسی داشت.
مثال: در یک سیستم فروش، «سفارش» میتواند یک ریشه تجمیع باشد. این سفارش شامل اقلام سفارش، آدرس تحویل، وضعیت پرداخت و سایر اطلاعات مرتبط است. عملیاتهایی نظیر افزودن یا حذف یک کالا در سفارش، فقط باید از طریق موجودیت سفارش انجام شود، نه مستقیماً از طریق اقلام سفارش.
۴. مخزن (Repository): مخازن مسئول مدیریت دسترسی به دادهها هستند و مانند پل ارتباطی میان لایه دامنه و لایه زیرساخت عمل میکنند. هدف اصلی Repository، جداسازی منطق دامنه از جزئیات ذخیرهسازی داده است.
مثال: در سیستم مدیریت کاربران، یک UserRepository میتواند متدهایی مانند findByEmail(email) یا save(user) ارائه دهد تا بتوان کاربران را بدون نیاز به نوشتن کوئریهای مستقیم پایگاه داده مدیریت کرد.
۵. سرویس دامنه (Domain Service): هنگامیکه یک عملیات به منطق دامنه مربوط میشود ولی مستقیما به هیچ موجودیت یا Value Object خاصی تعلق ندارد، از سرویسهای دامنه استفاده میشود. این سرویسها به حفظ انسجام منطق دامنه کمک میکنند.
مثال: در یک سامانه پرداخت، سرویس «محاسبه تخفیف نهایی» ممکن است نیاز به بررسی چندین موجودیت (مانند نوع کاربر، مجموع سفارش و کدهای تخفیف) داشته باشد. از آنجا که این عملیات به یک موجودیت خاص مربوط نیست، در قالب یک Domain Service پیادهسازی میشود.
چالشهای اجرای DDD در پروژههای واقعی
اجرای Domain-Driven Design (DDD) در پروژههای واقعی، اگرچه مزایای فراوانی دارد، اما با چالشهایی نیز همراه است که باید به آنها توجه کرد:
- پیچیدگی بالای مدلسازی دامنه
در سیستمهای بزرگ، درک کامل دامنه و مدلسازی دقیق آن نیازمند زمان، همکاری نزدیک با کارشناسان کسبوکار و تجربه است که گاهی دشوار و زمانبر است.
- نیاز به تعامل مستمر با متخصصان دامنه
موفقیت DDD وابسته به همکاری نزدیک تیم فنی با کارشناسان حوزه کسبوکار است، که در سازمانهای بزرگ یا پراکنده، هماهنگی این تعامل میتواند چالشبرانگیز باشد.
- آموزش و فرهنگسازی در تیم
DDD مفهومی پیچیده است و نیاز به آموزش مناسب تیم توسعه دارد. عدم آشنایی کافی میتواند منجر به سوءتفاهم و پیادهسازی نادرست شود.
- هزینه و زمانبری بالاتر در مراحل اولیه
طراحی دقیق مدل دامنه و تعریف درست Aggregateها و Boundaries ممکن است در ابتدا زمان و هزینه بیشتری ببرد، که در پروژههای با زمانبندی فشرده مشکلساز شود.
- سختی در تطبیق با سیستمهای موجود
در پروژههای توسعه بر روی سیستمهای قدیمی، یکپارچهسازی DDD با معماریهای قبلی یا پایگاه دادههای موجود ممکن است پیچیده باشد.
الگوهای استراتژیک در طراحی دامنهمحور (DDD)
در طراحی دامنهمحور، الگوهای استراتژیک به ما کمک میکنند تا تصمیمگیریهایی در سطح کلان معماری انجام دهیم. این تصمیمات مشخص میکنند چگونه قسمتهای مختلف یک سیستم بزرگ با یکدیگر تعامل دارند و چگونه میتوان با حفظ استقلال تیمها، انسجام مدلهای مفهومی را حفظ کرد.
۱. تقسیم دامنه به زیردامنهها (Subdomains)
نقطه شروع طراحی دامنهمحور، شناخت دقیق دامنه کسبوکار و تفکیک آن به زیردامنههای مختلف است. هر زیردامنه نقش متفاوتی در تحقق اهداف محصول دارد و باید با توجه به اهمیت و تأثیرش در طراحی مد نظر قرار گیرد. سه نوع Subdomain داریم:
- Core Domain (دامنه اصلی): مهمترین بخش سیستم است که مزیت رقابتی محصول در آن نهفته است. باید بیشترین توجه طراحی و بهترین نیروهای تیم روی این بخش متمرکز شوند.
- Supporting Subdomain (دامنه پشتیبان): این بخشها به دامنه اصلی کمک میکنند ولی خودشان مزیت رقابتی محسوب نمیشوند. برای مثال، سیستم ارزیابی عملکرد یا اعطای امتیاز به کاربران.
- Generic Subdomain (دامنه عمومی): نیازهای عمومی هستند که میتوان آنها را از سرویسهای آماده یا کتابخانههای عمومی تامین کرد؛ مانند ارسال پیامک، ثبت گزارش خطا یا احراز هویت.
درک درست از این تقسیمبندی باعث میشود تیمها بهتر بدانند روی کدام بخشها سرمایهگذاری بیشتری لازم است و کدامها را میتوان سادهسازی یا برونسپاری کرد.
۲. مرزبندی با Bounded Context
یک مدل دامنه نمیتواند در همه جا کاربرد داشته باشد. به همین دلیل، DDD تاکید میکند که هر مدل باید در یک Bounded Context مشخص طراحی و استفاده شود.
به بیان ساده، Bounded Context مرزهایی است که درون آن یک مدل خاص معنا پیدا میکند.
مثال: فردی ممکن است در یک Bounded Context به عنوان «کارمند» با ویژگیهایی مانند شماره پرسنلی و دپارتمان تعریف شود، اما در Bounded Context بیمه بهعنوان «بیمهشده» با اطلاعات کاملا متفاوتی شناخته شود.
هر Context معماری مستقل خود را دارد، حتی میتواند پایگاهداده مجزا داشته باشد و توسط تیم جداگانهای توسعه و منتشر شود.
۳. نقشهبرداری از روابط بین Contextها (Context Mapping)
پس از تعریف Contextهای مستقل، باید مشخص کنیم که این Contextها چگونه با یکدیگر تعامل میکنند. برای این منظور، DDD چند الگوی رایج معرفی میکند که بسته به نوع رابطه، میتوان یکی از آنها را انتخاب کرد:
- رابطه Seprate Ways: در واقع این همان ایده دوری و دوستی است! یعنی گاهی اوقات به دلیل هزینه بالای ارتباط بین تیمها یا همان Bounded contextها، شرکت تصمیم میگیرد که هر تیم راه خود را برود و روی هدف خود تمرکز کند.
- رابطه Customer-Supplier: در این نوع ارتباط BC سرویسدهنده، به دنبال رفع نیازمندیهای BC سرویس گیرنده است و به نوعی آن را مشتری خود میبیند. به همین دلیل، نیازمندیهای BC سرویسگیرنده روی اولویتها و برنامهریزیهای تیم سرویسدهنده تاثیر میگذارد.
- رابطه Conformist: در این نوع ارتباط، نیازمندیهای تیم سرویسگیرنده دغدغهای برای تیم سرویسدهنده ایجاد نمیکند، در واقع تیم سرویسگیرنده باید خودش را با تیم سرویسدهنده وفق دهد.
- رابطه Partnership: در این نوع ارتباط، همکاری زیادی بین تیم سرویسدهنده و سرویسگیرنده وجود دارد و تفاوت آن با ارتباط Customer-Supplier در این است که تیم سرویسدهنده و سرویسگیرنده روی BCهایی کار میکنند که به دنبال یک هدف مشترک هستند؛ در حدی که حتی گاهی اوقات پابلیش آن دو باید با هماهنگی هم صورت بگیرد.
- رابطه Anticorruption Layer: لایه ضدخرابی در واقع یک لایه مترجم سمت سرویسگیرنده است که از رخنه کردن مفاهیم و زبان BC سرویسدهنده به داخل BC خود جلوگیری میکند و در اصل، به عنوان نوعی فیلتر برای BC مطرح میشود (به نوعی الگوی Adapter است).
- رابطه Shared Kernel: وقتی قسمتی از مدل بین BCها از نظر مفهوم و منطق یکسان باشد، آن را به عنوان یک بخش مشترک یا هسته مشترک در نظر میگیریم. این الگو حتما باید با وسواس و دقت خاصی مورد استفاده قرار بگیرد؛ چون باعث به وجود آمدن وابستگی میشود.
- رابطه Published language: در اصل BC ارائهدهنده سرویس، یک مستندی از سرویسهای خود را در اختیار سرویسگیرنده قرار دهد.
- رابطه Open Host Service: در این نوع ارتباط، سرویسدهنده سرویس مورد نظر خود را در جایی Host میکند و در قالب API به همراه یک مستند (Published language) در اختیار سرویسگیرندهها قرار میدهد. با توجه به مواردی که مطرح شد، وقتی از ارتباط بین BCها یک مدل تصویری غیر رسمی ایجاد میکنیم که نحوه ارتباط آنها را برای ما شفافتر کند، اصطلاحا Context Map ایجاد کردهایم.
انتخاب صحیح این الگوها باعث میشود ارتباطات بین تیمها و سیستمها شفاف و مدیریتپذیر باقی بماند، بدون اینکه استقلال Contextها به خطر بیفتد.
مفهوم Bounded Context در DDD
همان طور که اشاره کردیم، Domain Driven Design همه چیز را میشکند. برای درک بهتر موضوع، به مکانیزم مغز انسان دقت کنید. وقتی مغز انسان با یک مسئله پیچیده روبهرو میشود، آن قدر مسئله را به بخشهای کوچک تقسیم میکند تا راه حل آن قسمتهای کوچکتر را در حافظهاش پیدا کند. بعد همه قسمتها را با هم ادغام میکند تا به جواب مسئله اصلی برسد. مفهوم Bounded Context یا محدودیت زمینه پژوهش، به محدودیتهایی اشاره دارد که باید در DDD به آنها توجه کنیم.
به عنوان مثال جمع دو عدد ۱۰۰+۱۰۰ را در نظر بگیرید. حاصل این مقدار برای خیلی از انسانها بدون استفاده از ماشین حساب بدیهی است؛ چون به عنوان یک مسئله بدون پیچیدگی برای مغز ثبت شده است. حال حاصل جمع ۱۵۷۸+۱۳۷۶ را در نظر بگیرید. بسیاری از افراد این جمع را یک مسئله پیچیده میدانند و در نگاه اول نمیتوانند به آن پاسخ دهند. در این حالت، مغز انسان برای حل این مسئله آن را به مسئلههای کوچکتر خرد میکند و در نهایت با ادغام مسئلههای کوچک، پاسخ را مییابد.
تعریف مسئله پیچیده به زبان ساده
مسئله پیچیده به موضوعی گفته میشود که درک و فهم آن در فضای مسئله (Problem Space) برای ذهن دشوار باشد. طبق تئوری سیستمهای پیچیده، این نوع سیستمها از اجزای متعدد و بههمپیوسته تشکیل شدهاند که تعامل پیچیدهای با هم دارند.
در فضای مسئله، دامنه به چندین SubDomain تقسیم میشود که هرکدام بخش مشخصی از کل سیستم را پوشش میدهند. زمانی که این SubDomainها وارد فضای راهحل (Solution Space) میشوند و به کد تبدیل میشوند، معمولا به Bounded Context تبدیل میشوند.
رویکرد طراحی دامنهمحور با استفاده از الگوهای استراتژیک و اصل تقسیمبندی، تلاش میکند دامنههای پیچیده را به بخشهای کوچکتر و قابل مدیریت تقسیم کرده و وابستگیهای بین آنها را به شکل صحیحی کنترل کند. در حالت ایدئال، هر SubDomain متناظر با یک Bounded Context است، اما در عمل ممکن است این تطابق کامل نباشد.
ارتباط Bounded Context و SubDomainها
اریک ایوانز Bounded Context را به غشای سلول بدن تشبیه کرده است. همانطور که اگر غشای سلول از بین برود، دیگر سلولی وجود نخواهد داشت. او همچنین در کتاب خود، مثال فرش کردن یک واحد ساختمان را مطرح میکند.
اتاقها و سالن این ساختمان همان SubDomainها هستند و فرشی که برای آنها استفاده میشود همان Bounded Context است. در این مثال، میتوان برای هر اتاق یک فرش جداگانه در نظر گرفت (مورد ایدئال) یا از یک فرش برای دو اتاقی که روبهروی هم قرار گرفتهاند، استفاده کرد (یک Bounded Context برای دو SubDomain) و این کاملا به فضای Domain بستگی دارد.
یک موجودیت (Entity) در Bounded Contextهای مختلف میتواند معانی و نقشهای متفاوتی داشته باشد و بر اساس هر معنا، ویژگیها و رفتارهای مخصوص به آن حوزه را بپذیرد. مثلاً فرض کنید یک شرکت وجود دارد که از بیمه خاصی استفاده میکند. یک فرد در حوزه شرکت به عنوان «کارمند» شناخته میشود و ویژگیها و رفتارهای مرتبط با کارمندی را دارد؛ اما همین فرد در حوزه شرکت بیمه به عنوان «بیمهشده» تلقی میشود که ویژگیها و رفتارهای متفاوتی دارد.
هر Bounded Context معماری مستقل خود را دارد و حتی ممکن است پایگاه داده جداگانهای برای خود داشته باشد. بر اساس سیاست شرکت، توسعه و انتشار (deploy) هر Bounded Context میتواند به تیمهای مختلف واگذار شود تا به صورت مستقل مدیریت شود.
طراحی دامنهمحور در برابر طراحی دادهمحور: تفاوتها و تصمیمگیری درست
در طراحی نرمافزار دو رویکرد اصلی وجود دارد:
طراحی دامنهمحور (DDD) که تمرکز آن روی منطق کسبوکار و مدلسازی دقیق مفاهیم دامنه است. این روش مناسب سیستمهای پیچیده با قوانین تجاری زیاد است و همکاری نزدیک بین توسعهدهندگان و متخصصان حوزه کسبوکار را لازم دارد.
طراحی دادهمحور که تمرکز اصلی آن روی ساختار، جریان و کیفیت دادههاست و برای سیستمهای گزارشگیری، تحلیل و مدیریت دادهها مناسبتر است.
ویژگی | طراحی دامنهمحور (DDD) | طراحی دادهمحور |
---|---|---|
تمرکز اصلی | منطق و قواعد کسبوکار | ساختار و جریان دادهها |
مناسب برای | سیستمهای پیچیده و قانونمند | سیستمهای دادهمحور و تحلیلی |
روش طراحی اولیه | مدلسازی دامنه و زبان مشترک | مدلسازی پایگاه داده |
چگونه انتخاب کنیم؟
اگر پروژهتان قوانین پیچیده کسبوکار دارد و تغییرات مکرر در آن محتمل است، DDD انتخاب بهتری است. اما اگر هدفتان مدیریت و تحلیل دادههای حجیم است، طراحی دادهمحور مناسبتر خواهد بود.
جمعبندی
طراحی دامنهمحور (DDD) رویکردی است که با تمرکز بر فهم عمیق حوزه کسبوکار و مدلسازی دقیق مفاهیم آن، به توسعه سیستمهای نرمافزاری پیچیده کمک میکند. با تقسیم دامنه به بخشهای کوچکتر (SubDomain) و مدیریت آنها از طریق Bounded Contextها، این روش باعث کاهش پیچیدگی و تسهیل ارتباط بین تیم فنی و کارشناسان کسبوکار میشود.
منابع
دیدگاهتان را بنویسید