همیارمداتکام
همیارمداتکام

کالبدشکافی معماری و تحلیل دامنه (DDD)؛ چگونه مرز میان منطق کسب‌وکار و کدنویسی را از بین ببریم؟

یکی از مهم‌ترین مشکلات پروژه‌های نرم‌افزاری، فاصله میان منطق کسب‌وکار و کدی است که قرار است آن منطق را پیاده‌سازی کند.

کارشناسان کسب‌وکار معمولاً با مفاهیمی مانند مشتری، بیمه‌گذار، بیمه‌شده، قرارداد یا ملک صحبت می‌کنند؛ اما این مفاهیم زمانی که وارد فرایند توسعه نرم‌افزار می‌شوند، گاهی به چند جدول ساده، تعدادی API و مجموعه‌ای از کلاس‌های پراکنده تبدیل می‌شوند.

با افزایش قوانین، کاربران و فرایندهای کسب‌وکار، تغییر هر بخش از نرم‌افزار می‌تواند بخش‌های دیگر را نیز تحت‌تأثیر قرار دهد. این وضعیت معمولاً به شکل‌گیری کدهای درهم‌تنیده، وابستگی‌های پنهان و مدل‌های دامنه‌ای بی‌جان منجر می‌شود.

طراحی دامنه‌محور یا Domain-Driven Design که به اختصار DDD نامیده می‌شود، رویکردی برای تحلیل، طراحی و توسعه نرم‌افزار است که مرکز توجه را از دیتابیس و فریم‌ورک به منطق اصلی کسب‌وکار منتقل می‌کند.

در DDD، هدف فقط طراحی کلاس‌ها یا رسم نمودارهای UML نیست؛ بلکه تلاش می‌شود ساختار نرم‌افزار به‌گونه‌ای شکل بگیرد که مفاهیم واقعی کسب‌وکار را به‌درستی بازتاب دهد.

طراحی دامنه‌محور یا DDD چیست؟

طراحی دامنه‌محور روشی برای مدیریت پیچیدگی نرم‌افزارهایی است که منطق کسب‌وکار گسترده و تغییرپذیری دارند.

در این رویکرد، تیم توسعه پیش از انتخاب ساختار دیتابیس، فریم‌ورک یا روش پیاده‌سازی، تلاش می‌کند دامنه کسب‌وکار را به‌خوبی بشناسد. منظور از دامنه، حوزه‌ای است که نرم‌افزار برای حل مسائل آن طراحی می‌شود؛ برای مثال:

– دامنه بیمه
– دامنه بانکداری
– دامنه فروشگاه اینترنتی
– دامنه املاک
– دامنه حمل‌ونقل
– دامنه آموزش آنلاین
– دامنه سلامت و درمان

DDD به تیم کمک می‌کند مفاهیم مهم این حوزه‌ها را شناسایی، دسته‌بندی و به اجزای قابل‌مدیریت نرم‌افزار تبدیل کند.

نکته مهم این است که DDD برای هر پروژه‌ای ضروری نیست. اگر نرم‌افزار صرفاً مجموعه‌ای ساده از فرم‌ها و عملیات CRUD باشد، استفاده از تمام الگوهای آن باعث پیچیدگی و هزینه اضافی خواهد شد. این رویکرد بیشتر برای پروژه‌هایی ارزشمند است که قوانین کسب‌وکار پیچیده، تغییرات مستمر و اهمیت بالایی در سازمان دارند.

دو لایه اصلی در DDD

مفاهیم DDD را می‌توان در دو سطح اصلی بررسی کرد:

۱. طراحی استراتژیک:
در طراحی استراتژیک، ساختار کلان دامنه، مرز سیستم‌ها و ارتباط میان بخش‌های مختلف کسب‌وکار بررسی می‌شود. مفاهیمی مانند زبان فراگیر، بافت محدودشده و نقشه‌برداری بافت‌ها در این سطح قرار دارند.

۲. طراحی تاکتیکی:
در طراحی تاکتیکی، مفاهیم دامنه به اجزای قابل‌پیاده‌سازی در کد تبدیل می‌شوند. الگوهایی مانند Entity، Value Object، Aggregate، Repository، Domain Service و Domain Event از مهم‌ترین اجزای تاکتیکی هستند.

زبان فراگیر؛ نقطه آغاز طراحی دامنه‌محور

یکی از پایه‌ای‌ترین مفاهیم DDD، زبان فراگیر یا Ubiquitous Language است.

زبان فراگیر یعنی کارشناسان کسب‌وکار، تحلیلگران، معماران و توسعه‌دهندگان از واژگان مشترک و دقیق استفاده کنند. این زبان باید در جلسات، مستندات، نمودارها، نام‌گذاری کلاس‌ها، متدها و حتی پیام‌های سیستم حضور داشته باشد.

برای نمونه، در یک سامانه بیمه ممکن است واژه‌های «بیمه‌گذار» و «بیمه‌شده» به‌اشتباه یکسان در نظر گرفته شوند؛ در حالی که این دو مفهوم از نظر کسب‌وکار تفاوت دارند. بیمه‌گذار شخصی است که قرارداد بیمه را منعقد می‌کند یا حق‌بیمه را می‌پردازد؛ در حالی که بیمه‌شده شخصی است که پوشش بیمه‌ای به او تعلق دارد.

اگر این تفاوت در تحلیل مشخص نشود، احتمال دارد در طراحی دیتابیس و کد نیز این دو مفهوم با یکدیگر ترکیب شوند. نتیجه، سیستمی است که در آینده برای توسعه قوانین بیمه با بن‌بست مواجه خواهد شد. زبان فراگیر باید به یک قرارداد مشترک میان کسب‌وکار و تیم فنی تبدیل شود.

بافت محدودشده چیست؟

یکی از اشتباهات رایج در طراحی سیستم‌های سازمانی، تلاش برای ایجاد یک مدل واحد برای تمام بخش‌های سازمان است.

فرض کنید در یک سازمان واژه «مشتری» در چند بخش استفاده می‌شود:
– در بخش بازاریابی، مشتری ممکن است یک سرنخ یا Lead باشد.
– در بخش فروش، مشتری فردی است که قصد خرید دارد.
– در بخش مالی، مشتری دارای حساب و سوابق پرداخت است.
– در بخش خدمات، مشتری فردی است که درخواست پشتیبانی ثبت کرده است.

هر چهار بخش از واژه مشتری استفاده می‌کنند؛ اما معنای آن در هر بخش یکسان نیست.

Bounded Context یا بافت محدودشده، محدوده‌ای مشخص برای یک مدل مفهومی ایجاد می‌کند. درون این محدوده، واژه‌ها و قوانین معنای دقیق خود را دارند و لازم نیست مدل یک بخش با مدل بخش دیگر کاملاً یکسان باشد. این تفکیک به ما اجازه می‌دهد به‌جای ساختن یک مدل بزرگ و پیچیده برای کل سازمان، چند مدل کوچک‌تر و منسجم طراحی کنیم.

نقشه‌برداری بافت‌ها و ارتباط میان سیستم‌ها

بافت‌های محدودشده معمولاً به‌صورت مستقل از یکدیگر کار نمی‌کنند. بخش فروش ممکن است به اطلاعات بخش پرداخت نیاز داشته باشد و بخش صدور بیمه ممکن است از نتیجه ارزیابی خسارت استفاده کند.

Context Mapping یا نقشه‌برداری بافت‌ها برای نمایش این روابط به کار می‌رود. در این نقشه مشخص می‌شود:
– هر بافت چه مسئولیتی دارد.
– کدام بافت به بافت دیگر وابسته است.
– اطلاعات چگونه میان آن‌ها منتقل می‌شود.
– کدام سیستم تولیدکننده و کدام مصرف‌کننده داده است.
– آیا برای جلوگیری از انتقال مفاهیم ناسازگار به لایه محافظ (Anti-Corruption Layer) نیاز داریم یا خیر.

اگر یک سیستم قدیمی اصطلاحات و ساختار داده نامناسبی داشته باشد، نباید مدل داخلی سیستم جدید را مستقیماً بر اساس آن طراحی کرد؛ بلکه می‌توان از یک Anti-Corruption Layer استفاده کرد تا داده‌های سیستم قدیمی به زبان مدل جدید ترجمه شوند.

Entity یا موجودیت چیست؟

Entity موجودیتی است که هویت مستقل دارد و در طول زمان قابل‌شناسایی باقی می‌ماند.

برای مثال، در یک سامانه بیمه، قرارداد بیمه یک Entity است. اگر مبلغ حق‌بیمه یا تاریخ قرارداد تغییر کند، خود قرارداد همچنان همان قرارداد قبلی است؛ زیرا هویت آن با شناسه مشخصی مانند Contract ID حفظ می‌شود.

نمونه‌هایی از Entity عبارت‌اند از:
– قرارداد بیمه
– کاربر
– ملک
– سفارش
– پرونده خسارت
– حساب بانکی

در Entity، هویت از ویژگی‌ها مهم‌تر است. دو Entity ممکن است مشخصات ظاهری کاملاً یکسانی داشته باشند، اما چون شناسه متفاوتی دارند، دو موجودیت جداگانه محسوب می‌شوند.

Value Object یا شیء مقداری چیست؟

Value Object هویتی مستقل ندارد و فقط بر اساس مقدار ویژگی‌هایش تعریف می‌شود.

برای نمونه، آدرس، مبلغ پول، بازه زمانی و شماره تلفن نمونه‌های بارز Value Object هستند. اگر دو آدرس دقیقاً مقادیر یکسانی داشته باشند، از نظر مفهومی برابر هستند؛ زیرا هویت مستقلی ندارند.

ویژگی مهم Value Objectها این است که باید تغییرناپذیر یا Immutable باشند. به‌جای تغییر مستقیم مقدار یک شیء، باید نمونه جدیدی از آن ایجاد شود.

استفاده درست از Value Object باعث می‌شود قوانین مربوط به یک مفهوم در یک نقطه متمرکز شوند و از پراکنده‌شدن منطق در بخش‌های مختلف سیستم جلوگیری شود.

Aggregate و Aggregate Root

Aggregate مجموعه‌ای از Entityها و Value Objectهای مرتبط است که تحت یک مرز مشخص قرار می‌گیرند.

در هر Aggregate، یک موجودیت اصلی به نام Aggregate Root وجود دارد. سایر اجزای داخلی نباید مستقیماً از بیرون تغییر کنند و تمام تغییرات صرفاً باید از طریق ریشه Aggregate انجام شود.

برای مثال در سیستم مدیریت سفارش، «سفارش» می‌تواند Aggregate Root باشد و اقلام، آدرس ارسال و تخفیف‌ها اجزای داخلی آن باشند. هیچ کدی حق ندارد مستقیماً وضعیت یک قلم سفارش را تغییر دهد، مگر آنکه این درخواست از متدهای ریشه سفارش عبور کند.

هدف اصلی Aggregate، تضمین یکپارچگی داده‌ها و حفظ قوانین تغییرناپذیر دامنه (Invariants) است؛ مانند:
– سفارش پرداخت‌شده نباید بدون فرایند رسمی لغو شود.
– مبلغ نهایی سفارش باید با مجموع اقلام سازگار باشد.
– امکان ثبت خسارت برای قرارداد منقضی‌شده وجود نداشته باشد.

Domain Event؛ ثبت اتفاقات مهم کسب‌وکار

Domain Event بیانگر وقوع یک اتفاق مهم در دامنه کسب‌وکار است؛ مانند:
– قرارداد بیمه صادر شد.
– خسارت ثبت شد.
– پرداخت با موفقیت انجام شد.
– سفارش لغو شد.
– ملک برای فروش تأیید شد.

رویداد دامنه رویدادی در زمان گذشته است و نشان می‌دهد چه اتفاقی افتاده، نه اینکه چه دستوری باید اجرا شود. این رویدادها امکان اتصال نامتقارن، مقیاس‌پذیری بالا و پیاده‌سازی معماری‌های Event-Driven، CQRS و Event Sourcing را فراهم می‌کنند.

Repository و جداسازی منطق دامنه از دیتابیس

Repository یا مخزن، مسئول دسترسی به Aggregateها و ذخیره‌سازی آن‌هاست. منطق دامنه نباید به جزئیات فنی دیتابیس وابسته باشد. یک کلاس دامنه نباید بداند داده‌هایش در دیتابیس رابطه‌ای، سندی یا ابری ذخیره می‌شوند.

Repository مانند یک مرز قراردادی میان منطق کسب‌وکار و زیرساخت ذخیره‌سازی عمل می‌کند. این تفکیک مزایای زیر را دارد:
– افزایش تست‌پذیری منطق کسب‌وکار
– استقلال منطق تجاری از فناوری دیتابیس
– امکان تغییر فناوری ذخیره‌سازی بدون تغییر در کدهای دامنه
– تمرکز کامل بر حل مسائل واقعی کسب‌وکار

Domain Service چه زمانی استفاده می‌شود؟

گاهی یک رفتار کسب‌وکار به یک Entity خاص تعلق ندارد و نیازمند هماهنگی میان چند موجودیت مستقل است. در چنین شرایطی از Domain Service استفاده می‌شود؛ مانند:
– محاسبه حق‌بیمه بر اساس تحلیل چندین پارامتر مختلف
– ارزیابی صلاحیت دریافت تسهیلات
– محاسبه کمیسیون و سهم ذی‌نفعان در معاملات ملکی

نکته مهم این است که نباید همه منطق‌ها را به Service منتقل کرد؛ در غیر این صورت، موجودیت‌ها به مدل‌های بی‌جان تبدیل خواهند شد.

مزایا و چالش‌های DDD

مزایای کلیدی:
– انعکاس دقیق واقعیت کسب‌وکار در ساختار نرم‌افزار
– کاهش وابستگی‌ها و جلوگیری از ایجاد کدهای درهم‌تنیده
– افزایش چشمگیر خوانایی و نگه‌داری سیستم در درازمدت
– تسهیل حرکت به سمت Modular Monolith یا معماری مایکروسرویس
– ارتقای تست‌پذیری منطق دامنه

چالش‌ها و هزینه‌ها:
– نیاز به زمان و تحلیل عمیق در فاز ابتدایی
– ضرورت حضور فعال و مستمر کارشناسان خبره کسب‌وکار
– شیب یادگیری نسبتاً تند برای تیم مهندسی
– احتمال خطای Over-engineering در سیستم‌های ساده CRUD

DDD در سامانه صدور و خسارت بیمه

برای ملموس‌شدن موضوع، یک سامانه بیمه را تصور کنید. این سامانه شامل بافت‌های محدودشده متفاوتی است:
– مدیریت سرنخ‌ها و فروش
– صدور بیمه‌نامه
– مدیریت پرداخت و اقساط
– ثبت و ارزیابی خسارت
– مدیریت شبکه نمایندگان

واژه «مشتری» در بخش بازاریابی یک سرنخ است، در بخش صدور «بیمه‌گذار» و در پرونده خسارت «زیان‌دیده یا ذی‌نفع». با ایجاد Bounded Context و ارتباط رویدادمحور میان آن‌ها، سیستم بدون تداخل و با نهایت شفافیت رشد خواهد کرد.

چه زمانی نباید از DDD استفاده کنیم؟

اگر پروژه نرم‌افزاری شامل عملیات ساده ثبت، ویرایش و نمایش داده‌ها (CRUD) است و قوانین پیچیده‌ای ندارد، پیاده‌سازی کامل DDD یک سربار غیرضروری خواهد بود.

طراحی دامنه‌محور زمانی ارزش‌آفرین است که:
– منطق تجاری دارای قوانین غنی و پیچیده باشد.
– تغییرات دائمی در مقررات و مدل کسب‌وکار وجود داشته باشد.
– پروژه در مقیاس بزرگ سازمانی و با طول عمر بالا تعریف شده باشد.

جمع‌بندی

طراحی دامنه‌محور پیش از آنکه مجموعه‌ای از الگوهای کدنویسی باشد، یک مدل فکری ساختاریافته است. DDD یادآوری می‌کند که نقطه شروع مهندسی نرم‌افزار، شناخت دقیق مسئله، درک زبان کسب‌وکار و مرزبندی صحیح سیستم‌هاست.

بهترین معماری، لزوماً پیچیده‌ترین آن نیست؛ بلکه معماری‌ای است که بتواند پیچیدگی ذاتی کسب‌وکار را مهار کرده و نرم‌افزار را در برابر تغییرات زمانه منعطف نگه دارد.

پرسش پایانی

در تجربیات کاری و پروژه‌هایی که با آن‌ها روبه‌رو بوده‌اید، بزرگ‌ترین مانع برای ایجاد یک زبان مشترک میان کارشناسان کسب‌وکار و تیم توسعه چه بوده است؟ نبود شناخت دقیق از دامنه یا تمرکز بیش‌ازحد توسعه‌دهندگان بر ابزارهای فنی؟

منابع پیشنهادی برای مطالعه عمیق‌تر:
۱. کتاب Domain-Driven Design نوشته Eric Evans
۲. کتاب Implementing Domain-Driven Design نوشته Vaughn Vernon
۳. کتاب Learning Domain-Driven Design نوشته Vlad Khononov
۴. کتاب Patterns, Principles, and Practices of Domain-Driven Design نوشته Scott Millett و Nick Tune

حافظ گلشن‌نژاد | دانشجوی مهندسی نرم‌افزار و پژوهشگر فناوری
وب‌سایت: hamyaram.com
کانال تخصصی در بله: @Dev_journey
ارتباط مستقیم: 09912767753

اشتراک گذاری

دیدگاه خود را بیان کنید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *