DDD با EF Core: زنده کردن مدل دامنه در دیتابیس ✨
تا به حال به این فکر کردهاید که چگونه میتوان مدلهای دامنهی غنی (Rich Domain Models) را که مملو از منطق و رفتار هستند، به دیتابیسهای رابطهای که صرفاً مجموعهای از دادهها هستند، نگاشت کرد؟ در گذشته، این کار چالشبرانگیز بود و اغلب باعث میشد توسعهدهندگان به سمت مدلهای دادهای ساده (Anemic Data Models) روی بیاورند.
اما امروز، EF Core (Entity Framework Core) با ویژگیهای قدرتمند خود، این کار را به طرز شگفتانگیزی ساده کرده است. EF Core به ما اجازه میدهد که مدلهای دامنهی خود را بدون نیاز به مصالحه در طراحی، به دیتابیس نگاشت کنیم.
نگاشت Entities (موجودیتها)
نگاشت Entities در EF Core ساده است. کافی است کلاس Entity خود را در DbContext به عنوان یک DbSet اضافه کنید.
مثال:
// Domain Model (Entity)
public class Order
{
public int OrderId { get; private set; }
public decimal TotalAmount { get; private set; }
public OrderStatus Status { get; private set; }
// Private constructor for EF Core
private Order() { }
public Order(int orderId, decimal totalAmount)
{
this.OrderId = orderId;
this.TotalAmount = totalAmount;
this.Status = OrderStatus.Pending;
}
}
DbContext:
public class ApplicationDbContext : DbContext
{
public DbSet<Order> Orders { get; set; }
}در این مثال، EF Core به صورت پیشفرض، کلاس Order را به یک جدول در دیتابیس نگاشت میکند. اما چند نکته کلیدی برای حفظ اصول DDD وجود دارد:
- Private Setters: در DDD، رفتار موجودیتها باید از طریق متدها کنترل شود، نه از طریق تغییر مستقیم ویژگیها. با استفاده از
private set برای ویژگیها، تضمین میکنید که این قوانین رعایت میشوند. - Constructor خصوصی: EF Core برای ایجاد نمونههای کلاسها از یک constructor بدون پارامتر (parameterless constructor) استفاده میکند. میتوانید یک constructor خصوصی برای EF Core و یک constructor عمومی برای منطق دامنه ایجاد کنید.
نگاشت Value Objects (اشیاء ارزشی)
اینجاست که قدرت EF Core نمایان میشود! نگاشت Value Objects که هویت مستقل ندارند، در گذشته پیچیده بود. اما EF Core دو راه حل برای این کار ارائه میدهد:
- Owned Entities: این روش، بهترین راه حل برای نگاشت Value Objects است. یک
Owned Entity یک نوع دادهای است که به صورت مستقیم در جدول موجودیت والد خود ذخیره میشود.
مثال:
فرض کنید یک Value Object برای آدرس ( Address ) داریم:
public class Address
{
public string Street { get; }
public string City { get; }
public Address(string street, string city)
{
this.Street = street;
this.City = city;
}
}حالا، در DbContext، با استفاده از متد OwnsOne به EF Core میگوییم که کلاس Address را به عنوان یک Owned Entity نگاشت کند:
public class ApplicationDbContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().OwnsOne(c => c.Address);
}
}این کار باعث میشود که ویژگیهای Street و City به صورت ستونهایی در جدول Customers ذخیره شوند.
- Value Converters: اگر Value Object شما یک نوع دادهای اولیه (Primitive) را کپسولهسازی میکند (مثلاً یک Value Object به نام
EmailAddress که یک string را در خود دارد)، میتوانید از Value Converters استفاده کنید.
به این فکر کنید: آیا در پروژهتان، ویژگیهایی دارید که به صورت مجزا در کلاسها پخش شدهاند، در حالی که میتوانند به صورت یک Value Object کپسولهسازی شوند؟
EF Core به ما اجازه میدهد که به اصول DDD وفادار بمانیم و در عین حال، از یک ORM قدرتمند برای نگاشت مدلهای پیچیدهمان به دیتابیس استفاده کنیم. این یک گام بزرگ به سوی ساخت نرمافزارهای پایدار، قابل نگهداری و مقیاسپذیر است.
Owned Entities در EF Core: نگاشت اشیاء ارزشی به روشی مدرن ✨
در درسهای قبل، با Value Objects (اشیاء ارزشی) آشنا شدیم و فهمیدیم که چگونه این اشیاء کوچک و تغییرناپذیر میتوانند به خوانایی و قدرت مدل دامنهی ما کمک کنند. اما سوالی که پیش میآید این است: چگونه این اشیاء را که هویت مستقل ندارند، به یک دیتابیس رابطهای نگاشت کنیم؟
در گذشته، این کار پیچیده و زمانبر بود. توسعهدهندگان مجبور بودند یا از نوعهای دادهای اولیه (Primitive Types) استفاده کنند و یا مدلهای خود را به نحوی تغییر دهند که با دیتابیس همخوانی داشته باشند. اما EF Core (Entity Framework Core) این مشکل را با یک راهکار ساده و زیبا حل کرده است: Owned Entities.
Owned Entity چیست؟
یک Owned Entity یک نوع دادهای است که در دیتابیس به صورت مستقل از نوع دادهای اصلیاش (مالکش) وجود ندارد. در واقع، ویژگیهای آن به عنوان ستونهایی در جدول والدش ذخیره میشوند.
فرض کنید یک Entity به نام Customer داریم که دارای یک Value Object به نام Address است. Address به خودی خود هویتی ندارد؛ این آدرس به عنوان بخشی از Customer معنا پیدا میکند.
مدل دامنه:
public class Address
{
public string Street { get; private set; }
public string City { get; private set; }
public Address(string street, string city)
{
this.Street = street;
this.City = city;
}
}
public class Customer
{
public int Id { get; private set; }
public string Name { get; private set; }
public Address Address { get; private set; }
}اگر کلاس Address را به عنوان یک Owned Entity نگاشت کنیم، EF Core به صورت خودکار ویژگیهای Street و City را به عنوان ستونهایی در جدول Customers در دیتابیس ایجاد میکند.
چگونه یک Owned Entity را پیکربندی کنیم؟
پیکربندی Owned Entities در EF Core بسیار ساده است. کافی است در متد OnModelCreating در DbContext خود، از متد OwnsOne استفاده کنید.
public class ApplicationDbContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().OwnsOne(c => c.Address);
}
}نتیجه در دیتابیس:
با این پیکربندی، EF Core یک جدول به نام Customers ایجاد میکند که دارای ستونهایی مانند Id, Name, Address_Street و Address_City است.
چرا Owned Entities بهترین راه حل هستند؟
- حفظ مدل دامنه: این ویژگی به ما اجازه میدهد که بدون هیچ مصالحهای، از مدلهای دامنهی غنی با Value Objects استفاده کنیم.
- کپسولهسازی منطق: منطق مربوط به Value Object، مانند اعتبارسنجی آدرس، در داخل خود کلاس
Address باقی میماند. - سادگی نگاشت: به جای اینکه برای هر Value Object یک جدول جداگانه بسازید و روابط پیچیده ایجاد کنید، Owned Entities به صورت طبیعی با ساختار دیتابیسهای رابطهای همخوانی دارند.
به این فکر کنید: آیا در پروژهتان، مقادیر وابسته به یک موجودیت را به صورت مجزا در دیتابیس ذخیره میکنید؟ آیا میتوانید آنها را به یک Value Object تبدیل کرده و از Owned Entities برای نگاشت آنها استفاده کنید؟
Owned Entities در EF Core یک ابزار قدرتمند است که به ما کمک میکند تا شکاف بین مدل دامنهی غنی و لایه Persistence را پر کنیم و نرمافزاری بسازیم که هم از نظر منطقی درست طراحی شده باشد و هم به راحتی قابل نگهداری باشد.