🧠 چه زمانی از Value Objectها استفاده کنیم؟
من از Value Objectها برای حل مشکل primitive obsession و کپسولهسازی (encapsulation) قوانین دامنه (domain invariants) استفاده میکنم.
کپسولهسازی بخش مهمی از هر مدل دامنه است. شما نباید بتوانید یک Value Object را در حالت نامعتبر ایجاد کنید.
Value Object
ها همچنین type safety را فراهم میکنند. به امضای متد زیر دقت کنید:
public interface IPricingService
{
decimal Calculate(Apartment apartment, DateOnly start, DateOnly end);
}
حالا به امضای متد زیر نگاه کنید که در آن از Value Objectها استفاده شده است. میتوانید ببینید که این نسخه از IPricingService بسیار واضحتر (explicit) است. همچنین از مزیت type safety برخوردار میشوید. هنگام کامپایل کد، Value Objectها احتمال بروز خطا را کاهش میدهند.
public interface IPricingService
{
PricingDetails Calculate(Apartment apartment, DateRange period);
}
در ادامه چند نکته دیگر را ببینید که باید هنگام تصمیمگیری برای استفاده از Value Objectها در نظر بگیرید:
• پیچیدگی قوانین (invariants): اگر نیاز به اعمال قوانین پیچیده دارید، استفاده از Value Object را در نظر بگیرید.
• تعداد مقادیر اولیه (primitives): زمانی که تعداد زیادی مقدار اولیه وجود دارد، استفاده از Value Object منطقی است.
• تعداد تکرارها: اگر نیاز دارید قوانین را فقط در چند نقطه از کد اعمال کنید، میتوانید بدون Value Object هم آن را مدیریت کنید.
💾 ذخیرهسازی Value Objectها با EF Core
Value Object
ها بخشی از موجودیتهای دامنه (domain entities) هستند و باید در پایگاه داده ذخیره شوند.
در اینجا نحوه استفاده از EF Owned Types و Complex Types برای ذخیرهسازی Value Objectها آورده شده است.
🧱 Owned Types
Owned Type
ها را میتوان با فراخوانی متد OwnsOne در هنگام پیکربندی موجودیت تنظیم کرد.
این کار به EF اعلام میکند که باید Value Objectهای Address و Price را در همان جدول موجودیت Apartment ذخیره کند.
Value Object
ها با ستونهای اضافی در جدول apartments نمایش داده میشوند.
public void Configure(EntityTypeBuilder<Apartment> builder)
{
builder.ToTable("apartments");
builder.OwnsOne(property => property.Address);
builder.OwnsOne(property => property.Price, priceBuilder =>
{
priceBuilder.Property(money => money.Currency)
.HasConversion(
currency => currency.Code,
code => Currency.FromCode(code));
});
}
چند نکته در مورد Owned Typeها:
• Owned Typeها دارای یک کلید پنهان هستند.
• از نوع اختیاری (nullable) پشتیبانی نمیکنند.
• مجموعههای متعلق پشتیبانی میشوند و با OwnsMany قابل پیکربندی هستند.
•Table splitting
به شما اجازه میدهد Owned Typeها را در جدول جداگانه ذخیره کنید.
🧩 Complex Types
ویژگی جدیدی در EF هستند که در 8 NET. در دسترساند.
آنها با مقدار کلید (key value) شناسایی یا پیگیری نمیشوند.
اینها باید بخشی از نوع موجودیت (entity type) باشند.Complex Type ها برای نمایش Value Objectها در EF مناسبتر هستند.
در اینجا نحوه پیکربندی Address به عنوان یک Complex Type را میبینید:
public void Configure(EntityTypeBuilder<Apartment> builder)
{
builder.ToTable("apartments");
builder.ComplexProperty(property => property.Address);
}
چند محدودیت برای Complex Typeها وجود دارد:
🔹️ از مجموعهها (collections) پشتیبانی نمیکنند.
🔹️ از مقادیر اختیاری (nullable values) پشتیبانی نمیکنند.
🧾 نتیجهگیری
Value Object
ها به شما کمک میکنند تا یک مدل دامنه غنی طراحی کنید.
میتوانید از آنها برای حل مشکل primitive obsession و کپسولهسازی قوانین دامنه استفاده کنید.Value Objectها با جلوگیری از ایجاد اشیای دامنه نامعتبر، احتمال خطا را کاهش میدهند.
شما میتوانید برای نمایش Value Objectها از record یا کلاس پایه ValueObject استفاده کنید.انتخاب بین آنها باید بر اساس نیازها و پیچیدگی دامنه شما باشد.
من معمولاً بهصورت پیشفرض از record استفاده میکنم مگر زمانی که به ویژگیهای خاص کلاس پایه نیاز داشته باشم.
برای مثال، کلاس پایه زمانی مفید است که بخواهید اجزای برابری (equality components) را کنترل کنید.
🔖هشتگها:
#DomainDrivenDesign #ValueObject #CleanCode #EntityVsValueObject #ImmutableObjects