Implement Entities in Domin Layer - DDD

Implement Entities In DDDادامه از : Entities, Value Objects, Aggregates and Rootsپیاده سازی Entity Classوقتی از اصول DDD پیروی کنید، در نهایت یک کلاس پایه برای همه موجودیت های دامنه ایجاد می کنید. این ایده خوبی است زیرا به شما امکان می دهد منطق مشترک را در یک مکان جمع آوری کنید. وقتی تصمیم به انجام آن می‌گیرید، ناگزیر با این سؤال مواجه می‌شوید که دقیقاً چه چیزی باید در آن موجودیت پایه گنجانده شود و چگونه باید ارائه شود.اغلب توسعه دهندگان از interfaceها به عنوان موجودیت پایه(base entity) استفاده می کنند. کد آن ممکن است شبیه به این باشد://Interface as a base entity public interface IEntity { Guid Id { get; } }اینترفیس IEntity فقط یک ویژگی Id را با نوع کلید اصلی مشخص شده تعریف می کند که در مثال بالا Guid است. این می تواند انواع دیگری مانند string، int، long یا هر آنچه شما نیاز دارید باشد.در حالی که این رویکرد تضمین می کند که همه موجودیت های دامنه دارای حداقل property هستند (ویژگی ID در این مثال)، در بیشتر موارد داشتن یک اینترفیس به عنوان موجودیت پایه، ایده بدی است.اول از همه، اینترفیس‌ها به شما اجازه نمی‌دهند هیچ منطقی را در آن‌ها نگه دارید، و باید همان منطق را در تمام کلاس‌های ارث بری کننده پیاده‌سازی کنید، که منجر به تکرار کد و نقض اصل DRY می‌شود. حتی با مثال ساده بالا، باید ویژگی ID را در هر موجودیتی معرفی کنید.ثانیا، استفاده از یک اینترفیس ، رابطه مناسب بین موجودیت های دامنه را نشان نمی دهد. دو کلاسی که interface یکسانی را پیاده‌سازی می‌کنند، نشان نمی‌دهند که چگونه با یکدیگر مرتبط هستند. آنها ممکن است به سلسله مراتب کاملاً غیر مرتبط تعلق داشته باشند. به عبارت دیگر، interface IEntity یک رابطه "can do" را معرفی می کند، در حالی که موجودیت های دامنه باید توسط یک رابطه "is a" به موجودیت پایه متصل شوند. هر کلاس دامنه نه تنها دارای ویژگی Id است، بلکه خود یک موجودیت است.استفاده از کلاس پایه به جای یک interface می تواند ایده بهتری باشد.اما چگونه کلاس های پایه را پیاده سازی کنیدخوب، اما در base domain class به چه منطقی نیاز داریم؟بدیهی است که باید یک فیلد Id داشته باشد که به کلید اصلی جدول نگاشت می شود. همه جداول در پایگاه داده باید دارای شناسه هایی با یک نوع باشند تا بتوانیم ویژگی Id را در base class قرار دهیمبنابراین، base domain class چگونه باید باشد؟ بیایید آن را مرور کنیم.ما تمام property های رایج را در این کلاس Abstract BaseEntity نگه می داریم. دلیل آن استفاده مجدد از آن در هر موجودیتی است.public abstract class BaseEntity<T> { public virtual T Id { get; set; } }شما می توانید برخی از property های دیگر را نیز وارد کنید، با این حال، برای سادگی فعلا، من فقط Id را نگه می دارم. استفاده از نوع T گزینه ای را برای پویا کردن شناسه می دهد. برای مثال، ممکن است بر اساس الزامات جدول، شناسه را به صورت int، bigInt ، Guid و یا هر آنچه شما نیاز دارید باشد.یکی از بدترین تخلفاتی که برنامه نویسان تازه کار انجام می دهند، داشتن public ID setters مانند قطعه کد فوق است.فقط زمانی که نیاز به موجودیت‌هایی با انواع Id مختلف در single project/bounded context ایجاد می‌شود، باید یک پارامتر نوع را در کلاس پایه Entity معرفی کنید.موجودیت ها مطابق زیر از Entity<TKey> class مشتق می شوند:public class Book : BaseEntity<Guid> { public string Name { get; set; } public float Price { get; set; } }اگر نوع شناسه موجودیت شما Guid است، چند نکته خوب برای پیاده سازی آن وجود دارد:سازنده ای(constructor) ایجاد کنید که Id را به عنوان پارامتر دریافت کرده و به abstract class BaseEntity ارسال کند.اگر یک موجودیت با سازنده ایجاد می کنید که پارامترها را می گیرد، یک سازنده خالی protected/private نیز ایجاد کنید. این زمانی استفاده می‌شود که database provider شما موجودیت شما را از پایگاه داده می‌خواند (در مورد deserialization).خوب است که یک شناسه معتبر روی موجودیت حتی قبل از ذخیره آن در پایگاه داده داشته باشید.از Guid.NewGuid() Method برای تنظیم Id استفاده نکنید! از یک IGuidGenerator Custom service هنگام ارسال شناسه از کدی که موجودیت را ایجاد می کند استفاده کنید. این سرویس می بایست برای تولید GUID های متوالی بهینه شده باشد، که برای clustered indexes در پایگاه های داده رابطه ای حیاتی است.چرا GUID را ترجیح می دهیم؟* در تمامی database providers قابل استفاده است.* این اجازه می دهد تا بدون نیاز به یک رفت و برگشت برای تولید مقدار ID در پایگاه داده، کلید اصلی را در سمت client تعیین کنید. این می تواند هنگام درج رکوردهای جدید در پایگاه داده کارایی بیشتری داشته باشد و به ما امکان می دهد قبل از تعامل با پایگاه داده، PK را بشناسیم.* حدس زدن GUID ها غیرممکن است، بنابراین در برخی موارد در مقایسه با مقادیر auto-increment Id، ایمن تر هستند.در حالی که برخی از معایب نیز وجود دارد.* مهمترین مشکل GUID این است که به طور پیش فرض ترتیبی نیست. وقتی از GUID به عنوان کلید اصلی استفاده می‌کنید و آن را به‌عنوان clustered indexes (که پیش‌فرض است) برای جدول خود تنظیم می‌کنید، مشکل عملکردی قابل توجهی در درج ایجاد می‌کند (زیرا درج رکورد جدید ممکن است نیاز به ترتیب مجدد رکوردهای موجود داشته باشد).اجازه ندهید پایگاه داده مسئول برنامه شما باشد.شناسه های خود را در سطح Application ایجاد کنید نه در سطح Persistence.اما از نگاه DDD مشکل این که پایگاه داده شناسه های موجودیت شما را تولید کند چیست ؟مشکل سازترین چیز این است که یک جنبه مهم از دامنه خود را به نرم افزار شخص ثالث واگذار می کنید.اما بر اساس آنچه که در بالا گفته شد برای BaseEntity خواهیم داشت :public abstract class BaseEntity<T> { protected BaseEntity() { } public T Id { get; private set; } public BaseEntity(T id) { Id = id } }و یک موجودیت نمونه بصورت زیر خواهد بود :public class Book : BaseEntity<Guid> { public string Name { get; set; } public float Price { get; set; } public Book(Guid id) : base(id) { } }مثال استفاده در یک application service :public class BookAppService : ApplicationService, IBookAppService { private readonly IRepository<Book> _bookRepository; public BookAppService(IRepository<Book> bookRepository) { _bookRepository = bookRepository; } public async Task CreateAsync(CreateBookDto input) { await _bookRepository.InsertAsync( new Book(GuidGenerator.Create()) { Name = input.Name, Price = input.Price } ); } }خوب در این لینک یک پیاده سازی نمونه از IGuidGenerator Custom service را می توانید مشاهده کنید.بیشتر بخوانید: Entities, Value Objects, Aggregates and Rootsبیشتر بخوانید : Implementing DDD - Clean Architectureبیشتر بخوانید : لایه Domain در طراحی دامنه گرا DDDبیشتر بخوانید : نقشه راه توسعه دهندگان Asp.NET Core