فرآیند طراحی فنی

پس از یاد گرفتن طراحی استراتژی و تکنیک های آن می توانید دامنه و موضوعات حول محور آن را به خوبی شناسایی کنید، شما می توانید با کمک این تکنیک ها فضای بزرگ و پیچیده مسئله را به مسائل کوچک تر با شفافیت بیشتر تقسیم کنید و سپس برای هر کدام از آنها راه حل خود را ارائه دهید. در نهایت تمام این راه حل ها در کنار هم، معماری و طراحی سیستم نرم افزاری را شکل می دهند که باعث می شود Core Domain برای اهدافی که به وجود آمده خدمت رسانی کند و در نهایت باعث حل مشکلات کاربران سیستم و درآمدزایی برای کسب و کار شود.

حال برای پیاده سازی راه حل ها باید طراحی فنی بلد باشید تا بتوانید راه حل های استراتژیک خود را به درستی پیاده سازی کنید.

هر پروژه نرم افزاری یک ساختاری برای پیاده سازی دارد. ساختار پروژه ها میتواند به قسمت های مختلف تقسیم شود. هر قسمت را یک لایه می توان در نظر گرفت. ساده ترین نوع ساختار، ساختار یک لایه است. در این نوع ساختار کل پیاده سازی کنار هم قرار می گیرد. بخشی از پروژه نرم افزاری را کدهای مربوط به رابط کاربری نرم افزار در بر می گیرد. در واقع این رابط کاربری همان ظاهر نرم افزار است جایی که استفاده کنندگان نرم افزار از آن برای تعامل با نرم افزار استفاده می کنند. بخشی دیگر از پروژه مربوط به زیرساخت تکنولوژی های مورد استفاده در نرم افزار است که به توسعه دهنده قابلیت ذخیره سازی دیتا، ارسال پیام و پردازش اطلاعات را می دهد. بخش دیگر مربوط به domain است جایی که قلب تپنده نرم افزار است و دلیل وجود نرم افزار.

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

راه حلی که مهندسی نرم افزار طی چند سال اخیر به سمت آن سوق پیدا کرده ساختار چند لایه است. شماتیک کلی این ساختار از 4 لایه تشکیل شده است:

در لایه User Interface تمام کدهای مربوط ظاهر نرم افزار نوشته می شود که راه ورود کاربران به سیستم است.

در لایه Infrastructure تمام کدهای مربوط به زیرساخت پروژه برای استفاده از تکنولوژی های مورد نیاز نوشته میشود. مثل کدهای مربوط به برقرار ارتباط به دیتابیس و ذخیره سازی داده یا کدهای مربوط به برقرار ارتباط با سیستم های دیگر. هر گونه برقراری ارتباط با دنیایی بیرون از طریق این لایه انجام می شود.

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

لایه Application شبیه به یک مدیر عمل می کند. هیچ درخواستی مستقیم از لایه User Interface به سمت لایه Domain ارسال نمی شود بلکه به سمت لایه Application ارسال می شود سپس این لایه تصمیم می گیرد که درخواست را به چه صورت به سمت لایه Domain ارسال کند. این لایه ترتیب توالی کار ها را مدیریت می کند و منطق مربوط به نحوه عملکرد نرم افزار در این لایه پیاده سازی می شود. اما هیچ کدام از منطق های کسب و کار در این لایه پیاده سازی نمی شود.

در این نوع لایه بندی، آن هایی اهمیت بیشتری دارند لایه های سطح بالا حساب می شوند و آن هایی که اهمیت کمتری دارند لایه های سطح پایین محسوب می شوند. توصیه می شود لایه های سطح پایین به لایه های سطح بالا وابسته باشند نه برعکس. پس طبق این توصیه، لایه User Interface به لایه Application وابسته است. لایه Application به لایه Domain وابسته است. لایه Infrastructure به لایه Domain و لایه Application وابسته است ولی لایه Domain به هیچ کدام از لایه ها وابسته نیست.

تمام اجزایی که داخل لایه Domain قرار می گیرند نباید دخالتی در نمایش دادن خود در رابط کاربری، ذخیره سازی خود در لایه Infrastructure ، مدیریت توالی کارها در لایه Application داشته باشند، بلکه تمام تمرکز آنها باید بر نشان دادن کسب و کار و قوانین آن باید باشد. با این روش دامنه قابلیت تکامل دارد و می تواند هر لحظه دانش کسب و کار را بیشتر وارد پروژه کند و موفقیت پروژه را رقم بزند.

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

برای مثال فرض کنید لایه Infrastructure یک سری خدمات برای ذخیره سازی دیتا داخل دیتابیس ارائه میکند و این خدمات را از طریق یک سری سرویس که داخل کلاس SomethingRepository پیاده سازی شده ارائه می کند. حال توسعه دهنده نیاز دارد این سرویس ها را داخل لایه Application فراخوانی کند، اگر این سرویس ها بدون انتزاع پیاده سازی شده باشند، توسعه دهنده مجبور است از این پیاده سازی به صورت مستقیم استفاده کند و این یعنی وابستگی قوی به لایه Infrastructure و به پیاده سازی خدمات مربوط به دیتابیس، ولی اگر برای آن پیاده سازی یک انتزاع داشته باشد مثلا ISomethingRepository ، توسعه دهنده به جای استفاده از پیاده سازی (SomethingRepository) از انتزاع آن پیاده سازی استفاده می کند (ISomethingRepository) و با این کار وابستگی به پیاده سازی خدمات مربوط به دیتابیس از بین رفته ولی همچنان از آنجایی که آن انتزاع داخل لایه Infrastructure قرار گرفته، همچنان وابستگی Application به Infrastructure وجود دارد، لذا اگر آن انتزاع (ISomethingRepository) را داخل خود لایه Application قرار دهیم، از آنجایی لایه Infrastructure به لایه Application وابسته است می تواند به انتزاع دسترسی داشته باشد و آن را پیاده سازی کنید از طرفی لایه Application دیگر به لایه Infrastructre وابستگی محکمی ندارد و از طرفی از خدمات Infrastructure نیز می تواند استفاده کند.

معمار نرم افزار در شروع پروژه با توجه به تصمیماتی که در طراحی استراتژی گرفته، ساختار پروژه و زیرساخت های مورد نیاز و تکنولوژی های مورد نیاز را در پروژه انتخاب می کند سپس تیم توسعه آن را پیاده سازی میکند. همچنان که پروژه رو به جلو حرکت می کند ممکن است تغییراتی در ساختار پروژه و زیرساخت ها و تکنولوژی های مورد استفاده اعمال شود که تمام این تغییرات در راستای بهبود پروژه و هدف کسب و کار خواهند بود. در ادامه به بررسی روش هایی می پردازیم که به توسعه دهنده سیستم کمک می کند تا پیاده سازی بهینه تری از domain در پروژه داشته باشد.

فرآیند پیاده سازی Model در نرم افزار

بعد از مدل سازی کردن domain نوبت به پیاده سازی آن در پروژه می رسد. به طور کلی و خلاصه Model به دست آمده تشکیل شده از یک سری اِلمان است که یک شبکه ارتباطی بین آنها برقرار است و یک سری فرآیند در کسب و کار را پیش می برند. حال DDD برای پیاده سازی این اِلمان ها سه الگو معرفی کرده است:

در ادامه به بررسی هر کدام می پردازیم.

Entity: در هر کسب و کاری یک سری موجودیت وجود دارد که خدمت رسانی کسب و کار وابسته به وجود این موجودیت هاست. این موجودیت می تواند یک شی باشد مثلا شی ماشین یا می تواند یک موجود جاندار باشد مثال مشتری. هر موجودیت یک سری ویژگی ها و یک سری رفتار ها دارد. کسب و کار با استفاده از ویژگی ها و رفتارهای این موجودیت ها به کاربران خود خدمات ارائه می کند. هر موجودیت ویژگی ها و رفتارهای بیشماری می تواند داشته باشد، اما، تنها آن ویژگی ها و رفتارهایی را در پروژه پیاده سازی می کنیم که برای کسب و کار اهمیت دارد.

تمام Entity ها دو ویژگی خاص دارند که هنگام پیاده سازی باید رعایت شوند. این دو ویژگی عبارتند از:

هر Entity باید یک شناسه منحصر به فرد در کل کسب و کار داشته باشد تا به واسطه همین شناسه شناسایی شود. همچنین یک Entity در طول عمر خود ممکن است بارها تغییر وضعیت دهد یا برخی از ویژگی هایش بروزرسانی شوند که در این صورت هرگز نباید شناسه آن تغییر کند. به عبارت دیگر شناسه هر Entity هویت آن موجودیت است از طرفی ویژگی های آن Entity ممکن است تغییر کند اما هویت آن همیشه ثابت است.

برای مثال در یک فروشگاه اینترنتی، موجودیتی به اسم مشتری وجود دارد که برای پیاده سازی آن از الگوی Entity باید استفاده کرد چرا که هر مشتری داخل این فروشگاه اینترنتی باید یک هویت منحصر به فرد داشته باشد. پس احتمالا توسعه دهنده این فروشگاه اینترنتی یک کلاس ایجاد می کند به اسم Customer و تمام ویژگی ها و رفتارهای مشتری را داخل این کلاس قرار می دهد و احتمالا برای شناسه آن یک فیلد به اسم Id قرار می دهد و با استفاده از منابعی که دارد برای هر مشتری یک شناسه منحصر به فرد تولید می کند و داخل این فیلد Id قرار می دهد.

هر Entity در دنیای واقعی یک سری رفتارها و وظایف و قوانین مشخص دارد، مثال یک مشتری در یک فروشگاه آنلاین رفتارها، وظایف و قوانین زیر را دارد:

تمام این رفتارها، وظایف و قوانین باید در داخل کلاس Customer پیاده سازی شود چون متعلق به خود موجودیت است. به این نوع طراحی، طراحی رفتار محور گفته می شود. در واقع به جای تمرکز بر داده ها بر رفتارها تمرکز می کند چرا که هدف از طراحی به سبک DDD، شناخت چگونه کار کردن سیستم است پس در هنگام مدل سازی کردن کسب و کار علاوه بر شناسایی ویژگی های مهم هر موجودیت که همان داده ها هستند، بر رفتارهای موجودیت ها نیز تمرکز می کنیم، در نهایت Entity که ساخته می شود و در کد پیاده سازی میشود ترکیبی از ویژگی ها، رفتارها، وظایف و قوانین مربوط به آن Entity است.

Value Object: دسته دیگری از اِلمان های Model هستند که هویت آنها اهمیت ندارد و تنها مقداری که داخل خود نگهداری می کنند برایمان مهم است. به این المان ها Value Object گفته می شود. ویژگی های هر Entity نوعی از Value Object ها هستند. مثال فیلد آدرس در موجودیت مشتری یک نوع Value Object حساب میشود. حال این Value Object می تواند یک رشته کاراکتر ساده باشد یا می تواند یک Class در دنیای برنامه نویسی باشد که درون خود چند Value Object دیگر نگهداری می کند. در دنیای برنامه نویسی شی گرا ساده ترین نوع Value Object ها نوع داده ها هستند، یعنی به فرض مثال نوع داده String یا Int یک نوعی از Value Object ها به حساب می آیند. در مثالی که برای آدرس در موجودیت مشتری گفتیم، آدرس می تواند یک نوعی از String در کلاس Customer باشد یا می تواند یک کلاس به اسم Address باشد که درون خود شهر و استان و محله را به عنوان سه نوع داده String نگه می دارد.

به عبارت دیگر درباره Entity ها "کیستی" و "هویت" آن برای ما مهم است اما درباره Value Object ها "چیستی" و "مقدار" آن برای ما مهم است.

اگر دو Entity با شناسه های متفاوت تمام ویژگی هایشان با هم یکسان باشند نمی توان گفت آن دو Entity با هم برابر هستند، چون هویت آنها با هم متفاوت است اما اگر دو Value Object مقدار یکسانی داشته باشند میتوان گفت آن دو با یکدیگر برابر هستند، چون هویت ندارند و با مقدارشان شناسایی می شوند، پس زمانی که مقدار یکسان داشته باشند باهم برابرند. بنابراین Value Object ها باید به صورتی پیاده سازی شوند که بعد از متولد شدن دیگر مقادیر آنها تغییر نکنند، چرا که در صورت تغییر اعتبار خود را از دست داده اند.

همانند Entity ها Value Object ها نیز می توانند رفتار، وظایف و قوانین مربوط به خود را داشته باشند که در این صورت تمام این موارد باید در کلاسی که برای آن Value Object می نویسیم پیاده سازی کنیم.

Service: یک سری فرآیندهایی که در کسب و کار باید انجام شوند جزو رفتارها، وظایف و قوانینی هستند که میتوان مسئولیت آنها را به Entity ها و Value Object ها واگذار کرد، اما یک سری از فرآیندها هستند که نمی توان مسئولیت آنها را به هیچ Entity یا Value Object یی واگذار کرد، این فرآیندها باید به صورت مستقل پیاده سازی شوند.

اگر سعی کنید این فرآیندها را در یکی از Entity ها بگنجانید، اتفاقی که می افتد اینست که آن Entity مفهوم خود را در پروژه از دست میدهد و بعد از مدتی دچار پیچیدگی های نامرتبط با خود می شود. هر Entity با توجه به ویژگی ها و رفتارهایی که برای آن در مدل سازی شناسایی می شود یک مرز مفهومی در دامنه کسب و کار برای خود تعریف می کند، با وارد کردن وظایف نا مرتبط به داخل آن Entity باعث از بین رفتن مرزهای آن Entity می شوید. بعد از مدتی وقتی افراد تیم به آن Entity نگاه می کنند دلیل وجود این Entity را متوجه نخواهند شد و مفهوم آن را درک نخواهند کرد حتی اگر تصمیم بگیرند آن را ریفکتور کنند به علت وابستگی های نامرتبط و وابسته شدن اشیا دیگر پروژه به آن Entity به واسطه آن فرآیند، باعث می شود Refactor پروژه کاری سخت و پرهزینه باشد.

الگوی سوم برای پیاده سازی Model الگوی Service است. طبق این الگو هر فرآیندی که جزوی از رفتار ها، وظایف و قوانین هیچ Entity و Value Object یی نباشد باید به صورت یک Service مستقل ایجاد شود. متدی که داخل کلاس Service پیاده سازی می شود معمولا چند ورودی می گیرد و فرآیند مورد را نظر را روی آنها اعمال می کند سپس در خروجی در اختیار استفاده کنندگان قرار می دهد. Service ها همیشه بدون نگهداری وضعیت هستند یعنی سرویس فراخوانی میشود، کار خود را انجام می دهد، خروجی را به بیرون پاس می دهد و هیچ دیتایی داخل خود نگهداری نمی کند، پس اگر هزاران Object سرویس را در لحظه فراخوانی کنند سرویس از هیچ کدام هیچ دیتایی نگهداری نمی کند و بعد از اجرا شدن اطلاعی از دیتا هایی در ورودی دریافت کرده بود یا در خروجی به بیرون پاس داده بود، نخواهد داشت. کلاسی که برای پیاده سازی Service در نظر می گیرید بهتر است داخل اسم خود از افعال استفاده کرده باشد چرا که آن سرویس برای یک فرآیند مشخص به وجود آمده و بهتر است برای واضح تر بودن وظیفه Service کاری که انجام می دهد همراه با فعل کار داخل اسم کلاس Service باشد.

برای مثال فرآیند جابه جایی پول بین حساب های بانکی در نرم افزار بانکی را درنظر بگیرید. از آنجایی که برای این کار نیاز به دو Entity Account هست پس ایده خوبی نیست که این فرآیند داخل خود Entity پیاده سازی کنیم چرا که در آن صورت داخل خود Entity باید یک شی دیگر از خود Entity نگهداری کنیم پس بهتر است یک کلاس بسازیم به اسم TransferMoneyService و سپس داخل این کلاس یک متد بسازیم به اسم Transfer که در ورودی دو Object از جنس Account دریافت می کند و پول را از یکی به دیگری انتقال میدهد.

Modules: بعد از شناسایی Entity ها ، Value Object ها و Service های موجود در دامنه باید آنها در جای مناسبی در پروژه قرار داد. قبل تر در مورد اینکه دامنه را چگونه به صورت فیزیکی در پروژه قرار بدهیم صحبت کردیم و پیشنهاد شد برای اینکه دامنه در پروژه مشهود باشد بهتر است یک پوشه به اسم Domain در پروژه قرار دهیم و هر آن چیزی که مربوط به دامنه است داخل این پوشه قرار بگیرد.

Entity ، Value Object و Domain Service ها مواردی هستند که باید در پوشه Domain قرار بگیرند، اما، نحوه قرار گیری آنها مهم است. زمانی که فقط یک Entity در دامنه مربوطه وجود داشته باشد می توان آن یک مورد را به تنهایی داخل پوشه Domain قرار داد اما زمانی که تعداد زیادی Entity ، Value Object و Domian Service داشته باشیم و هر کدام به یک قسمتی از دامنه مربوط باشند بهتر است آنها را دسته بندی کنیم به طوری که اسم هر دسته بندی گویای داستان آن دسته بندی باشد. به هر کدام از این دسته بندی ها یک Module گفته میشود. فرض کنید در دامنه یک پروژه این موجودیت ها را پیاده سازی کرده اید:
Customer.cs ، CustomerRegisteredEvent.cs ، CustomerService.cs ، Order.cs ، OrderService.cs ، در این سناریو می توانید دو Module بسازید یکی به اسم Customer و دیگری به اسم Order و موارد مربوطه را داخل هر کدام قرار دهید.

Domain
├─ Customers
│ ├─ Customer.cs
│ ├─ CustomerRegisteredEvent.cs
│ └─ CustomerService.cs
│
└─ Orders
 ├─ Order.cs
 └─ OrderService.cs

با ایجاد این دسته بندی برای توسعه دهندگان بعدی پروژه یک نقشه ذهنی می سازید که هر کسی که به این ساختار نگاه کند متوجه می شود داخل دامنه چه مفاهیمی و چه روابطی وجود دارد و جزییات هر کدام چیست. به عبارت دیگر توسعه دهندگان هر پروژه با بررسی Module های موجود در پروژه می توانند دو موضوع بسیار مهم را دامنه یاد بگیرند:

نوع رابطه بین Module ها و مفاهیم و مسائل سطح بالا در دامنه

جزییاتی که داخل هر کدام از مفاهیم دامنه وجود دارد و برای تحقق اهداف دامنه ضروری هستند

Aggregates: در هر دامنه ای چندین Entity و Value Object وجود دارد. بسیاری از آنها با یکدیگر ارتباط دارند، از طرفی برای انجام یک سری فرآیند ها بیش از یک Entity و Value Object درگیر می شوند. مشکلی که ممکن است در این فرآیندها پیش بیآید عدم یکپارچگی و در نهایت نقض صحت عملکرد سیستم است.

فرض کنید دو Entity دارید به اسم های A Entity و B Entity که در یک فرآیندی این دو باید با یکدیگر تغییر کنند و از طرفی یک قانونی در دامنه دارید به اسم A Rule که بعد برای تغییر هر کدام از آنها باید این قانون رعایت شود، در این شرایط شما مجبور هستید در طول انجام فرآیند به صورت یک تراکنش، تغییرات خود را اعمال کنید تا مطمئن شوید A Entity و B Entity با یکدیگر تغییر کرده اند و داخل تراکنش A Rule را چک کنید، از طرفی در تمام فرآیندهایی که هر کدام از Entity های A و B تغییر می کنند باید A Rule را چک کنید تا مطمئن شوید قوانین دامنه نقض نشده باشد. کنترل کردن چنین شرایطی بدین شکل، مخصوصا در سیستم های بزرگ کاری بسیار پیچیده به حساب می آید.

راه حلی که DDD برای این شرایط در نظر گرفته، استفاده از Aggregate ها است. در واقع در هر دامنه Entity هایی که با یکدیگر ارتباط دارند و داخل یک مرز مشخصی از لحاظ مفهوم قرار می گیرند می توان داخل یک گروه مشخصی قرار داد. به هر کدام از این گروه ها Aggregate گفته می شود.

برای ساخت یک Aggregate یک کلاس ایجاد می کنید و تمام Entity ها و Value Object هایی که در این گروه قرار گرفته اند را داخل Aggregate قرار می دهید. از این پس برای تغییر هر کدام از Entity ها، Aggregate یک تراکنش باز می کند و هر Entity که لازم باشد داخل این تراکنش تغییرات لازم را می پذیرد و بعد یا قبل از انجام تغییرات تمام قوانین دامنه را یک بار چک می کند تا از صحت عملکرد فرآیندها مطمئن شود. هر Aggregate یک Aggregate Root دارد که یکی از Entity های موجود است و ارتباط دنیای بیرون با این Aggregate از طریق همین Aggregate Root خواهد بود. قوانینی که توسط Aggregate ها باید رعایت شوند:

دسترسی دنیای بیرون به Aggregate فقط از طریق Aggregate Root ممکن است

دنیای بیرون به Entity های موجود در Aggregate دسترسی ندارد مگر در مواقع لازم که آن هم به صورت موقت و محدود است

شناسه Aggregate Root دارای شناسه عمومی است که از دنیای بیرون برای همه قابل دسترسی است ولی شناسه Entity های دیگر داخلی است و فقط از داخل Aggregate قابل دسترسی است

از آنجایی که دسترسی به Entity های داخل Aggregate فقط از طریق Aggregate Root ممکن است، بنابراین فقط برای یافتن Aggregate Root می توان در دیتابیس Query زد و باقی Entity ها باید توسط Aggregate Root از دیتابیس واکشی شود

تمام Entity های موجود در Aggregate برای دسترسی به Aggregate دیگر میتوانند از Aggregate Root آن Aggregate این کار را انجام دهند

هر تغییری که روی هر کدام از Entity های یک Aggregate انجام شود باید کل Aggregate توسط Aggregate Root صحت سنجی شود تا مطمئن شد هیچ کدام از قوانین نقض نشده است

بنابراین تمام Entity ها و Value Object هایی که با یکدیگر ارتباط دارند و یک سری قوانین کسب و کار باید با استفاده از تمام آنها رعایت شوند را در یک Aggregate قرار دهید. این Aggregate یک مرز فرضی حول تمام این Entity ها و Value Object ها محسوب می شود. با هر تغییری داخل این مرز یک بار تمام قوانین داخل مرز باید مجدد چک شوند تا صحت Aggregate تایید شود. هیچ شی ای از دنیای بیرون به داخل این مرز دسترسی ندارد مگر به Aggregate Root. در مواقع لازم دنیای بیرون می تواند به اعضای داخل این مرز دسترسی موقت و فقط خواندنی داشته باشد، به عبارت دیگر اگر یک شی ای از خارج Aggregate نیاز به یکی از اعضای داخل آن نیاز داشته باشد، آن شی را از Aggregate دریافت می کند اطلاعات مورد نیاز خود را استخراج می کند سپس هیچ رفرنسی به آن شی نگه نمی دارد و هیچ تغییر در آن ایجاد نمی کند. بدین ترتیب میتوان تضمین کرد قوانین داخل Aggregate همیشه رعایت می شوند.

Factories: در دنیای واقعی مفهوم کارخانه را می توان بدین شکل تفسیر کرد: جایی که مواد اولیه را دریافت کرده و محصول نهایی را تولید می کند. در توسعه پروژه نرم افزاری شما نیاز به ایجاد اشیا مختلف از انواع مختلف دارید. بسیاری از این اشیا همان مفاهیمی هستند که در دامنه کسب و کار زندگی می کنند مثل مفهوم "سفارش" یا مفهوم "مشتری". ایجاد کردن یک شی از یک مفهوم ساده، اغلب اوقات کار راحتی است، اما، اگر بخواهید یک شی جدید از یک Aggregate ای که داخل خود چندین Entity نگهداری می کند ایجاد کنید، ممکن است کار راحتی نباشد، چرا که، برای ایجاد این شی جدید باید از تمام Entity های داخل آن Aggregate نیز یک شی جدید بسازید، از طرفی ممکن است برای ساخت تمام Entity های داخل Aggregate نیاز به یک سری پردازش ها داشته باشید، همه این موارد کنار هم باعث می شوند ساخت شی جدید از یک Aggregate کار نسبتا پیچیده ای شود. DDD برای ساخت این اشیا پیچیده استفاده از Factory را پیشنهاد می دهد. دقیقا مثل همان تعریف ابتدای مبحث، Factory یک کلاس یا که متد است که با دریافت تمام اطلاعات لازم در ورودی می تواند یک شی جدید از Aggregate مورد نظر برای Client تولید کند در حالی که تمام پیچیدگی های ایجاد آن شی داخل Factory کپسوله شده و تمام قوانین موجود در Aggregate رعایت شده.

چرا استفاده از Factory بهتر از استفاده از Constructor است؟ با ایجاد شی جدید از Entity ها داخل Constructor، در واقع آن Aggregate را به صورت شدید به Entity های داخل خود وابسته کرده اید و هر تغییری در آن Entity ها منجر به تغییر در Constructor آن Aggregate می شود که این رویکرد در تناقص با رویکرد وابستگی ضعیف در اصول مهندسی نرم افزار است. از طرفی اگر Client بخواهد از تمام Entity های داخل Aggregate، یک شی جدید بسازید در واقع نسبت به جزییات داخل Aggregate آگاهی پیدا می کند و از طرفی باید برای رعایت قوانین Aggregate آن قوانین را سمت خود چک کند که تمام این موارد یعنی نقض قانون کپسوله سازی.

Factory را می توان به دو روش Factory Method و Abstract Factory پیاده سازی کرد. در روش اول یک متد داخل Aggregate پیاده سازی می کنید که وظیفه آن دریافت اطلاعات لازم و ساخت یک شی جدید از خودش است. در روش دوم یک کلاس می سازید و متدهای Factory را در آن پیاده سازی می کنید. چه زمانی از کدام روش استفاده کنیم؟ زمانی که شما فقط می خواهید از یک Aggregate یک شی جدید بسازید بهتر است Factory Method را داخل همان Aggregate پیاده سازی کنید ولی زمانی که نیاز دارید از چند Aggregate یک شی جدید به صورت یک جا بسازید باید از روش دوم استفاده کنید.

در هر دو روش برای رعایت وابستگی ضعیف توصیه می شود برای Aggregate خود را از یک Interface ارث بری کنید و خروجی Factory نوعی از آن Interface باشد.

هنگام ساخت شی جدید در Factory، وظیفه صحت سنجی قوانین Aggregate به عهده Aggregate Root و صحت سنجی قوانین داخلی هر Entity به عهده خود Entity است. حتما برای حالتی که، دیتای ورودی، قوانین Aggregate را نقض می کند، یک استاندارد برای خروجی در نظر بگیرید مثال میتوانید در چنین شرایطی Exception برگردانید یا دیتای null برگردانید.

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

وابستگی شدید تمام قسمت های نرم افزار به تکنولوژی دیتابیس مربوطه

نقض قوانین دامنه به واسطه پخش شدن قوانین دامنه در پروژه

تغییر رویکرد معماری از Domain-Centric به Database-Centric

فرض کنید قصد دارید به مشتریانی که تا کنون بیش از 1000 دلار از شما خرید کرده اند یک پیامک کد تخفیف ارسال کنید. احتمالا در این سناریو در لایه Presentation یک ایجنت دارید که یک سرویس از لایه Application فراخوانی می کند، اگر شما در لایه Application بخواهید مستقیما به دیتابیس وصل بشوید و لیست این مشتریان را استخراخ کنید در واقع به صورت مستقیم این لایه را به تکنولوژی که برای اتصال به دیتابیس استفاده می کنید وابسته کرده اید. از طرفی مشخص کردن این موضوع که کدام مشتری شامل این تخفیف می شود یک قانون مهم کسب و کاری است و باید توسط لایه Domain تعیین و تکلیف شود، پس عملا مرزبندی Domain را از بین برده اید و مرکزیت نرم افزار را از Domain-Model-Centric که هدف اصلی DDD است به Database-Model-Centric تغییر داده اید.

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

دو قانون مهم در پیاده سازی Repository:

هر Repository باید یک Interface داشته باشد

فقط Aggregate ها می توانند Repository داشته باشند

Repository دو کار مهم انجام می دهد، یکی تعامل با دیتابیس از طریق تکنولوژی مخصوص آن دیتابیس و دیگری جابه جایی دیتا بین دیتابیس و نرم افزار. اگر Repository ها را بدون Interface تعریف کنید تمام قسمت هایی که با Repository ها سر و کار دارند به آنها وابستگی پیدا می کنند و اگر روزی طراح نرم افزار تصمیم بگیرد تکنولوژی دیتابیس پروژه را تغییر دهد احتمال دارد علاوه بر تغییر Repository ها نیاز به تغییر در استفاده کنندگان Repository ها نیز داشته باشید. پس برای هر Repository یک Interface داخل لایه Domain تعریف کنید و پیاده سازی آن را داخل لایه Infrastructure انجام دهید و هر زمانی که نیاز به تغییر تکنولوژی داشته باشید بدون نیاز به تغییر در جاهای مختلف، فقط Repository را تغییر خواهید داد.

از آنجایی که تمام قوانین کسب و کار از طریق Aggregate ها پیاده سازی می شوند و برای ایجاد هر کدام از Entity های داخل آن نیاز به کنترل کردن قوانین دامنه توسط آن Aggregate است، پس برای آنکه اجازه ندهیم Entity های داخل هر Aggregate بدون دخالت Aggregate ایجاد شوند و از نقض قوانین دامنه جلوگیری کنیم، هیچ کدام از Entity ها نباید Repository داشته باشند. در واقع از آنجایی که Entity ها از طریق Aggregate ها ایجاد می شوند، برای واکشی دیتای آنها از دیتابیس یا ذخیره سازی آنها در دیتابیس باید از طریق Repository مربوط به آن Aggregate این کارها را انجام داد. پس زمانی که می خواهید یک Aggeragte را از طریق Repository از دیتابیس واکشی کنید باید تمام Entity های داخل آن را نیز از واکشی کنید تا Aggregate بتواند تمام قوانین را یک بار کنترل کند.

به یاد داشته باشید در Aggregate ها، مدل و قوانین کسب و کار را پیاده سازی می کنید، اما، لزوما دقیقا همان مدل را داخل دیتابیس خود نخواهید داشت مثلا ممکن است در Aggregate Customer یک لیستی از آدرس های مشتری داشته باشید اما در دیتابیس یک جدول به نام Customer خواهید داشت و یک جدول به نام CustomerAddress. در واقع می توان گفت Domain Model قوانین کسب و کار را پیاده سازی می کند و Database Model فقط نحوه ذخیره سازی دیتا های مورد نیاز کسب و کار را. توجه داشته باشید برای حفظ یکپارچگی نرم افزار تا جای ممکن این دو مدل را شبیه به یکدیگر نگهداری کنید اما هرگز نباید عینا مثل هم باشند.