Dariush Tasdighi - داریوش تصدیقی
Dariush Tasdighi - داریوش تصدیقی
خواندن ۸ دقیقه·۴ سال پیش

آموزش Domain Driven Design (DDD) + EF Core (قسمت اول)

توجه: این مقاله به مرور زمان، ویرایش و یا تکمیل می‌شود!
تقاضا: در صورتی که با مشکل تایپی، دستوری و یا مفهومی در این مقاله برخورد کردید، از شما دوست عزیز و گرامی، صمیمانه تقاضا می‌کنم که اینجانب را مطلع کرده، تا نسبت به تصحیح و یا تکمیل آن، در اسرع وقت، اقدام نمایم.
با کمال تشکر
داریوش تصدیقی
کانال تلگرام: IranianExperts@
شماره تلفن همراه: ۰۹۱۲۱۰۸۷۴۶۱
نشانی پست الکترونیکی: DariushT@GMail.com
فیلم‌های آموزشی https://www.aparat.com/DariushT
آدرس سایت‌ها: https://WebsiteAnalytics.ir - http://IranianExperts.ir - http://Date2Date.ir
نسخه مقاله: ۱.۱ - تاریخ بروزرسانی: ۱۴۰۰/۰۲/۲۷

مقدمه

در این مقاله هرگاه بخواهیم در خصوص Domain Driven Design (طراحی دامنه محور) صحبت کنیم، به طور خلاصه از نماد DDD استفاده خواهیم کرد.

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

باید دقت داشته باشیم زمانی که پیچیدگی‌های پیاده‌سازی یک سامانه زیاد می‌شود، لزوما نباید به DDD فکر کنیم! بلکه DDD صرفا راه‌حل‌هایی را پیشنهاد می‌دهد که پیچیدگی‌های سامانه‌ای که مربوط به کسب و کار مورد نظر وجود دارد را کاهش دهد!

نکته:‌ DDD یک معماری نمی‌باشد! DDD صرفا یک نگاه و تفکر مناسب برای حل مشکلات و پیچیدگی‌های سامانه مورد نظر در یک کسب و کار مشخص می‌باشد.

این مجموعه مقالات با چه گرایشی نوشته می‌شوند؟

هر چند که بسیاری از مفاهیمی که در این مجموعه مقالات منتشر می‌شوند، فارغ از تکنولوژی و زبان خاص برنامه‌نویسی است، ولی گرایش اصلی این مقالات دات‌نت ۵ و زبان برنامه‌نویسی #C و طبعا EF Core 5 می‌باشد.

دیگر نگران نباشید!

بسیاری از کسانی که در خصوص تفکر DDD تجربه دارند و با Entity Framework نیز کار کرده‌اند، مشکلاتی را برای پیاده‌سازی مشاهده کرده‌اند که بعضا باعث ناامیدی آن‌ها شده است! و شاید از آن‌ها شنیده باشید که EF برای DDD مناسب نمی‌باشد! حال آن‌که در EF Core 5، به راحتی و زیبایی می‌توانیم DDD را پیاده‌سازی نماییم.

متخصصین دامنه (Domain Expert) چه کسانی هستند؟

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

اصطلاحا به کسانی که بر روی موضوع و رویه‌های سامانه مسلط می‌باشند Domain Expert اطلاق می‌شود.

باید با یک زبان مشترک (Ubiquitous Language) صحبت کنیم!

باید به عنوان متخصصین برنامه‌نویسی، در زمان بحث و تبادل اطلاعات با Domain Expert با یک زبان و ادبیات مشترکی در خصوص مفاهیم صحبت نماییم. مانند اسامی، رفتارها، موجودیت‌ها و غیره. اصطلاحا به این ادبیات و زبان مشترک Ubiquitous Language اطلاق می‌شود.

چه موجوداتی در DDD وجود دارند؟

در زمان پیاده‌سازی معماری DDD به چند مفهوم در زمان پیاده‌سازی نیاز داریم:

  • Entity
  • Aggregate Root
  • Event
  • Value Object
  • Strongly Typed Enumeration

که دو مورد آخر مفاهیمی هستند که نیازی نیست در مورد آن‌ها از Domain Expert سوال کنیم و یا درباره آن‌ها به Domain Expert توضیح دهیم، چرا که بی‌جهت به آن‌ها استرس وارد کرده و ذهن آن‌ها را درگیر می‌کنیم!

توضیحاتی در خصوص Entity و Aggregate Root

در خصوص Entity و Aggregate Root باید گفت که آن‌ها موجوداتی هستند که دارای مشخصه (Identity) می‌باشند. به طور خلاصه و در زمان پیاده‌سازی کافی است که برای آن‌ها یک Property به نام Id تعریف نماییم.

برای دو موجود ValueObject و Strongly Typed Enumeration ما مفهومی به نام مشخصه یا شناسه (Id) نداریم!

نکته: معمولا توصیه می‌شود که Id را از جنس System.Guid تعریف نماییم.

کلاس AggregateRoot، کلاسی است که تمام ویژگی‌های Entity را داشته و صرفا دارای یک شخصیت جهان شمول می‌باشد، لذا توصیه می‌شود که کلاس AggregateRoot را از کلاس Entity ارث‌بری نماییم:

Public class AggregateRoot : Entity { }

کلاس AggregateRoot کلاسی است که در آن از Entity و یا از مجموعه‌هایی از Entity های مختلف ایجاد می‌شود و رابطه کلاس AggregateRoot و کلاس‌های Entity دقیقا رابطه Aggregation می‌باشد.

اصولا کلاس‌های #C، چندین نوع ارتباط دارند:

Inheritance (is)

Association Relation

  • Dependency (Using)
  • Association (has)
  • Aggregation (has, part-whole (جز از کل))
  • Composition (has, part-whole, ownership)

Aggregation and Composition are subsets of Association which describe relationship more accurately

Aggregation: independent relationship. An object can be passed and saved inside class via constructor, method, setter and so on…

Composition: dependent relationship. An object is created by owner object.

Dependency (references)(using)

It means there is no conceptual link between two objects. e.g. EnrollmentService object references Student & Course objects (as method parameters or return types)

public class EnrollmentService { public void Enroll(Student myStudent, Course myCourse){} }

Association (has-a)

It means there is almost always a link between objects (they are associated). Order object has a Customer object:

public class Order { public Customer MyCustomer {get; set;} }

Aggregation (has-a + part-whole)

Special kind of association where there is part-whole art relation between two objects. they might live without each other though.

public class PlayList { public List<Song> Songs {get; set;} }

OR

public class Computer { public Monitor MyMonitor {get; set;} }

Note: the trickiest part is to distinguish aggregation from normal association. Honestly, I think this is open to different interpretations.

Composition (has-a + whole-part + ownership)

Special kind of aggregation. An Apartment is composed of some Rooms. A Room cannot exist without an Apartment. when an apartment is deleted, all associated rooms are deleted as well.

public class Apartment { private Room _bedroom; public Apartment() { _bedroom = new Room(); } }

در دیدگاه Domain Driven Design، یک Entity به طور منفرد ایجاد می‌شود و به عنوان جزئی از کل شیء AggregateRoot به آن Inject یا Assign یا Insert می‌شود. طبعا به محض از بین رفتن شیء AggregateRoot تمام زیرمجموعه‌های آن نیز از بین خواهد رفت و ما همیشه از طریق شیء AggregateRoot می‌توانیم به اجزای آن دسترسی داشته باشیم. به همین دلیل اگر Domain یک AggregateRoot به نام Order و یک Entity‌ به نام OrderItem داشته باشیم، در داخل کلاس MyDatabaseContext، صرفا برای کلاس Order یک Property به شکل ذیل ایجاد می‌کنیم و طبعا برای کلاس OrderItem این کار را انجام نمی‌دهیم:

public DdSet<Order> Orders { get; }

در تکمیل بحث فوق، کلاس Order (سفارش) یک AggregateRoot‌ بوده و کلاس OrderItem یک Entity می‌باشد و ما معمولا یک شیء سفارش ایجاد کرده و سپس آیتم‌های آن‌را ایجاد و سپس به شیء Order اضافه می‌کنیم و بدیهی است به محض از بین بردن (رفتن) یک سفارش، به طور خودکار تمام آیتم‌های آن نیز حذف خواهند شد.

نکته: در رابطه با ارتباط یک AggregateRoot و Entity های آن باید دقت داشته باشیم که باید Cascade Delete فعال باشد!

به عنوان مثالی دیگر، کلاس Post (مطلب) یک AggregateRoot بوده و توضیحاتی (Comment) که دیگران در خصوص آن مطلب می‌نویسند، یک کلاس Entity‌ می‌باشد. یعنی ابتدا یک مطلب در سامانه درج (ایجاد) می‌شود و سپس دیگران در خصوص آن مطلب، Comment می‌گذارند. بدیهی است که به محض حذف مطلب، باید به طور خودکار تمام Comment‌ های آن حذف گردد. در این حالت، یک Comment به تنهایی بی‌معنی و بلااستفاده می‌باشد. ولی باید دقت داشته باشیم که رابطه بین یک کلاس درس و شاگرد چنین ارتباطی نیست! یعنی اگر یک کلاس درسی منحل شود، شاگردان آن کلاس از بین نخواهند رفت! و آن‌ها کماکان به عنوان موجوداتی مستقل وجود خواهند داشت. پس در این حالت می‌توان بیان کرد که هر دو کلاس User و Class، کلاس‌هایی از نوع AggregateRoot می‌باشند.

معمولا وقتی که می‌خواهیم یک کلاس AggregateRoot، مثلا به نام Order ایجاد نماییم، در داخل پروژه (Class Library) Domain، یک پوشه با نام جمع Order، یعنی Orders ایجاد کرده و سپس در داخل آن پوشه، کلاسی به نام Order‌ ایجاد می‌کنیم و تمامی متعلقات آن‌را نیز در کنار کلاس Order قرار می‌دهیم. این متعلقات عبارتند است:

  • تمام Entity های مربوط به آن (OrderItem)
  • اینترفیس IOrderRepository
  • تمام ValueObject های مربوط به آن

می‌توان در یک AggregateRoot، یک ارجاع Reference ای از یک AggregateRoot‌ دیگر قرار داده شود. حال به صورت منفرد و یا به صورت یک مجموعه (Collection).

در زمانی که می‌خواهیم یک Reference‌ از یک AggregateRoot‌ در داخل یک AggregateRoot‌ دیگر قرار دهیم، نباید مانند نگاه بانک‌های اطلاعاتی و یا نگاه سنتی EF، Id آن‌را قرار دهیم! بلکه باید صرفا از جنس همان AggregateRoot‌، یک Property ایجاد نماییم. به عنوان مثال فرض کنید که دو AggregateRoot به نام‌های User و Customer‌ داریم. زمانی که می‌خواهیم اعلام کنیم که یک Customer، مربوط به یک User‌ می‌باشد، در داخل کلاس Customer، یک Property‌ از جنس User‌ و به شکل ذیل ایجاد می‌کنیم:

public User User {get; private set;}

نکته:‌ در ادامه بحث، خواهیم فهمید که چرا برای set، یک Access Modifier به صورت private‌ نوشته‌ایم!

کلاس‌های Value Object مانند کلاس‌های Entity‌ بوده، با این تفاوت که مطلقا دارای مشخصه (Identity) نمی‌باشند و بسیاری از Validation‌ های مورد نیاز خود را در زمان ایجاد چک می‌کنند. به عنوان مثال فرض کنید که می‌خواهیم برای کلاس User، دو Property‌ به نام‌های FirstName, LastName ایجاد نماییم. فرض آن است که در خصوص FirstName و LastName ممکن است بخواهیم Required بودن و حداکثر طول رشته آن‌ها را کنترل کنیم و در ضمن شاید بخواهیم که هر وقت خواستیم اطلاعات User‌ را نمایش دهیم، این دو Property به درستی در کنار هم قرار گرفته و نمایش داده شوند. در این حالت یک کلاس ValueObject‌ به نام FullName ایجاد می‌کنیم که هم در زمان ایجاد شیء آن، Validation های لازم انجام شود. سپس در داخل کلاس User، یک Property‌ به شکل ذیل ایجاد می‌کنیم:

public FullName FullName { get; private set; }

از این‌گونه ValueObject‌ ها به کرات می‌توانیم ایجاد کرده و در کلاس‌های Entity و AggregateRoot استفاده نماییم. نموده‌های دیگری از ValueObject ها مانند Email, Username, Money و غیره را می‌توان نام برد.

نکته:‌ در صورتی که بخواهیم یک کلاس ValueObject ایجاد کرده و اطمینان داریم که ممکن است در AggregateRoot‌ و یا Entity های دیگری نیز مورد استفاده قرار گیرد، عرف بر این است که در پروژه Domain، یک پوشه به نام SharedKernel ایجاد کرده و کلاس‌های ValueObject پرکاربرد را در داخل آن قرار ‌دهیم.

برای کلاس‌ها و اینترفیس‌های پایه مانند ValueObject, IAggregateRoot معمولا در داخل پروژه Domain، یک پوشه به نام SeedWork ایجاد کرده و آن‌ها را در داخل این پوشه قرار می‌دهیم.

https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/seedwork-domain-model-base-classes-interfaces

همانطور که عنوان شد، می‌توانیم از یک AggregateRoot‌ در داخل یک AggregateRoot‌ دیگر به صورت Reference‌ استفاده نماییم و حتی از یک ValueObject‌ پرکاربرد در Entity‌ ها و یا AggregateRoot‌ های دیگر استفاده کنیم، ولی باید یادمان باشد که به هیچ عنوان نمی‌توانیم از یک Entity‌ که برای یک AggregateRoot‌ استفاده کرده‌ایم، در یک AggregateRoot‌ دیگر نیز استفاده نماییم.

مفهوم Nested در Domain Driven Design‌ با رعایت نکات فوق بلامانع می‌باشد. مثلا می‌توانیم در داخل یک ValueObject‌ از یک ValueObject دیگر استفاده نماییم و یا می‌توانیم در یک Entity از Entity و یا ValueObject های دیگر استفاده نماییم.

منابع

https://github.com/kdakan/DDD-Domain-Driven-Design

تذکر:‌ این مقاله هنوز تکمیل و ویرایش نشده است!

efcoreddddomain driven designdomain
محقق، معمار، مشاور، مدرس و برنامه‌نویس حوزه فن‌آوری اطلاعات - تحلیل‌گر و فعال بازار بورس و سرمایه
شاید از این پست‌ها خوشتان بیاید