برنامه نویسی

روشی مدرن برای ایجاد اشیاء با ارزش برای حل وسواس اولیه در دات نت

Summarize this content to 400 words in Persian Lang
وسواس بدوی تمایل به استفاده از انواع داده های پایه برای نمایش مفاهیم پیچیده تر است.این یک ضد الگوی رایج است که می‌تواند منجر به کد نامشخص و سیستم‌های سخت‌تر برای نگهداری شود.

در این پست وبلاگ، به شما توضیح خواهم داد که چرا وسواس اولیه می تواند منجر به مشکلاتی در برنامه های شما شود و چگونه با استفاده از Value Objects در دات نت به این موضوع رسیدگی کنید.

در وب سایت من: antondevtips.com من بهترین شیوه های دات نت و معماری را به اشتراک می گذارم.برای تبدیل شدن به یک توسعه دهنده بهتر مشترک شوید.کد منبع این پست وبلاگ را به صورت رایگان دانلود کنید.

چرا وسواس بدوی یک مشکل است؟

وسواس بدوی زمانی اتفاق می‌افتد که از انواع داده‌های پایه (مانند int، string یا DateTime) برای نمایش مفاهیم پیچیده در دامنه شما بیش از حد استفاده شود.این عمل می تواند منجر به کد نامشخص، اشکالات و مشکل در نگهداری و گسترش سیستم شما شود.

چرا وسواس بدوی یک مشکل است؟

عدم بیان: انواع ابتدایی مانند int یا string معنای داده هایی را که نشان می دهند روشن نمی کنند. به عنوان مثال، یک رشته می تواند یک آدرس ایمیل، یک نام کاربری یا یک URL باشد، اما هیچ راهی برای تشخیص فقط با نگاه کردن به نوع آن وجود ندارد.

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

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

مشکل در تدوین کد: همانطور که برنامه شما رشد می کند، نیازها ممکن است تغییر کنند. اگر در همه جا از انواع اولیه استفاده کرده باشید، ایجاد این تغییرات به روشی ثابت و بدون شکست دشوار می شود.

نمونه هایی از وسواس اولیه

بیایید مثالی را بررسی کنیم که در آن a User موجودیت دارد Email داراییهنگام ایجاد یک کاربر، ممکن است هنگام ایجاد یک اعتبارسنجی را پیاده سازی کنید User شی

ممکن است استدلال کنید که چرا به چنین اعتبارسنجی نیاز دارید زیرا برای درخواست‌های webapi خود اعتبارسنجی ورودی دارید.اما حقیقت این است که اعتبارسنجی برای درخواست‌های ورودی، شما را از یک اشکال در کد که در آن پارامترهای اشتباه را در سایر اجزای برنامه خود ارسال می‌کنید یا نقشه‌برداری می‌کنید، نجات نمی‌دهد.چنین اعتبارسنجی روشی محبوب در طراحی Domain Driven است که در آن هنگام ایجاد اشیاء خود یک محافظ ایمنی نهایی ایجاد می کنید.

بیایید یک مثال را بررسی کنیم (با اعتبار سنجی ایمیل ساده، غیر آماده تولید):

public class User
{
public string Email { get; set; }

public User(string email)
{
if (string.IsNullOrWhiteSpace(email) || !email.Contains(“@”))
{
throw new ArgumentException(“Invalid email address”, nameof(email));
}

Email = email;
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

چرا این یک مشکل است؟

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

بیایید مثال دیگری را بررسی کنیم:

public class User
{
public string Email { get; }
public string Username { get; }

public User(string email, string username)
{
if (string.IsNullOrWhiteSpace(email))
{
throw new ArgumentException(“Email is required”, nameof(email));
}

if (string.IsNullOrWhiteSpace(username))
{
throw new ArgumentException(“Username is required”, nameof(username));
}

Email = email;
Username = username;
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بیایید سعی کنیم یک کاربر ایجاد کنیم:

var user = new User(“anton@test.com”, “anton”);
var user = new User(“anton”, “anton@test.com”);

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این کد با موفقیت کامپایل و اجرا می شود. آیا متوجه مشکلی شدید؟

همانطور که استفاده می کنیم string ایمیل و نام کاربری را تایپ کنید – می توانید پارامترها را به ترتیب اشتباهی ارسال کنید.و اگر در هنگام ساخت اشیا اعتبارسنجی نداشته باشید، همانطور که در بالا به شما نشان دادم، در نهایت با داده های اشتباه در پایگاه داده مواجه خواهید شد.این می تواند منجر به مشکلات جدی شود.

راه حل این مشکل استفاده است اشیاء ارزشی، که داده ها و رفتار مرتبط را در یک واحد واحد و معنی دار محصور می کند.

اشیاء ارزشی چیست؟

اشیاء ارزشی نشان دهنده یک مقدار در دامنه شما است که هویت ندارد اما با ویژگی های آن تعریف می شود.به عنوان مثال، یک Address ممکن است یک شی ارزش باشد، همانطور که با ویژگی های آن (خیابان، شهر، پست) تعریف می شود.

اشیاء ارزش یک مفهوم کلیدی در طراحی دامنه محور (DDD) هستند.

ویژگی های کلیدی اشیاء ارزشی:

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

مزایای استفاده از اشیاء ارزشی:

بیانگر بودن: کد خواناتر و خود توضیحی تر می شود.

کپسولاسیون: قوانین کسب و کار در داخل شئ ارزش گنجانده شده اند و از ثبات در هر مکانی که استفاده می شوند اطمینان حاصل می کنند.

کاهش اشکالات: با محدود کردن دامنه انواع اولیه، خطر انتقال داده های نادرست را کاهش می دهید.

قابلیت استفاده مجدد: ارزش اشیاء را می توان در بخش های مختلف برنامه مورد استفاده مجدد قرار داد و اصل DRY (خودت را تکرار نکن) ترویج می کند.

حالا بیایید بررسی کنیم که چه گزینه هایی برای ایجاد Value Objects در NET داریم.

یک برنامه کاربردی

امروز به شما نحوه پیاده سازی را نشان خواهم داد اشیاء ارزشی برای یک برنامه حمل و نقل که مسئول ایجاد و به روز رسانی مشتریان، سفارشات و محموله های محصولات سفارش داده شده است.

این نرم افزار دارای موجودیت های زیر است:

مشتریان
سفارشات، موارد سفارش
محموله ها، اقلام حمل و نقل

من از شیوه های Domain Driven Design برای نهادهایم استفاده می کنم.بیایید a را بررسی کنیم Customer و Shipment موجودیت هایی که از انواع اولیه برای همه ویژگی ها استفاده می کنند:

public class Customer
{
public Guid Id { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public string Email { get; private set; }
public string PhoneNumber { get; private set; }
public IReadOnlyListOrder> Orders => _orders.AsReadOnly();

private readonly ListOrder> _orders = [];

private Customer() { }

public static Customer Create(
string firstName,
string lastName,
string email,
string phoneNumber)
{
return new Customer
{
Id = Guid.NewGuid(),
FirstName = firstName,
LastName = lastName,
Email = email,
PhoneNumber = phoneNumber
};
}

public void AddOrder(Order order)
{
_orders.Add(order);
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

public class Shipment
{
private readonly ListShipmentItem> _items = [];

public Guid Id { get; private set; }

public string Number { get; private set; }

public Guid OrderId { get; private set; }

public string Address { get; private set; }

public string Carrier { get; private set; }

public string ReceiverEmail { get; private set; }

public ShipmentStatus Status { get; private set; }

public IReadOnlyListShipmentItem> Items => _items.AsReadOnly();

public DateTime CreatedAt { get; private set; }

public DateTime? UpdatedAt { get; private set; }

private Shipment()
{
}

public static Shipment Create(
string number,
Guid orderId,
string address,
string carrier,
string receiverEmail,
ListShipmentItem> items)
{
var shipment = new Shipment
{
Id = Guid.NewGuid(),
Number = number,
OrderId = orderId,
Address = address,
Carrier = carrier,
ReceiverEmail = receiverEmail,
Status = ShipmentStatus.Created,
CreatedAt = DateTime.UtcNow
};

shipment.AddItems(items);

return shipment;
}

public void AddItems(ListShipmentItem> items)
{
_items.AddRange(items);
UpdatedAt = DateTime.UtcNow;
}

public void AddItem(ShipmentItem item)
{
_items.Add(item);
UpdatedAt = DateTime.UtcNow;
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این موجودات به انواع اولیه وسواس دارند: شماره محموله، آدرس، آدرس ایمیل، شماره تلفن و غیره.بیایید نحوه جایگزینی این انواع اولیه با اشیاء ارزش را بررسی کنیم.

ایجاد آبجکت های ارزشی در دات نت

من می توانم محبوب ترین گزینه های زیر را برای ایجاد اشیاء ارزش لیست کنم:

با استفاده از کتابخانه ValueOf
با استفاده از C# Records
با استفاده از C# Record Structs

بیایید هر گزینه را عمیق تر بررسی کنیم.

ایجاد اشیاء ارزش با ValueOf

ValueOf یکی از کتابخانه های محبوب برای ایجاد اشیاء ارزش با ارائه یک کلاس پایه است که بسیاری از کدهای boilerplate را مدیریت می کند.

ابتدا باید بسته را نصب کنید:

dotnet add package ValueOf

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا نحوه استفاده از آن آورده شده است ValueOf کتابخانه برای ایجاد اشیاء ارزش:

using ValueOf;

public class EmailAddress : ValueOfstring, EmailAddress>
{
protected override void Validate()
{
if (string.IsNullOrWhiteSpace(Value) || !Value.Contains(“@”))
{
throw new ArgumentException(“Invalid email address.”);
}
}
}

public class OrderNumber : ValueOfstring, OrderNumber>
{
protected override void Validate()
{
if (string.IsNullOrWhiteSpace(Value))
{
throw new ArgumentException(“Order number cannot be empty.”);
}
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

شما باید کلاس Value Object (EmailAddress و OrderNumber) خود را از یک پایه به ارث ببرید ValueOf کلاس و ارائه 2 نوع عمومی:

یک نوع ابتدایی زیرخط دار
خود یک نوع Object مقدار

ValueOf از تاپل های سی شارپ پشتیبانی کنید، اگر نیاز دارید که شی مقدار خود را به عنوان چندین ویژگی نشان دهید، به عنوان مثال، آدرس:

using ValueOf;

public class Address : ValueOfstring Street, string City, string Zip), Address>
{
protected override void Validate()
{
if (string.IsNullOrWhiteSpace(Value.Street))
{
throw new ArgumentException(“Street cannot be empty.”);
}

if (string.IsNullOrWhiteSpace(Value.City))
{
throw new ArgumentException(“City cannot be empty.”);
}

if (string.IsNullOrWhiteSpace(Value.Zip))
{
throw new ArgumentException(“Zip code cannot be empty.”);
}
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا نحوه ایجاد این اشیاء ارزشی آمده است:

var email = EmailAddress.From(“anton@test.com”);
var orderNumber = OrderNumber.From(“ORD-12345”);
var address = Address.From((“123 Main St”, “Springfield”, “12345”));

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

می توانید از a استفاده کنید Value ویژگی برای بازیابی مقدار پنهان در اشیاء ارزش:

string emailValue = email.Value;
string orderNumberValue = orderNumber.Value;
(string street, string city, string zip) = address.Value;

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ایجاد اشیاء ارزش با رکوردها

برخی از توسعه دهندگان با استفاده از Value Objects پیاده سازی می کنند ValueOf کتابخانه، حتی بیشتر پیاده سازی های خود را ایجاد می کنند.چه می شود اگر به شما بگویم که سی شارپ رکوردها در حال حاضر تمام چیزهایی که برای اشیاء ارزش نیاز دارید را دارید.

رکوردها یک روش واقعا مدرن برای ایجاد Value Object در دات نت هستند.

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

در اینجا نحوه تعریف اشیاء یکسان با استفاده از رکوردها آمده است:

public record EmailAddress
{
public string Value { get; }

public EmailAddress(string value)
{
if (string.IsNullOrWhiteSpace(value) || !value.Contains(“@”))
{
throw new ArgumentException(“Invalid email address.”, nameof(value));
}

Value = value;
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر نیازی به تأیید اعتبار در داخل ندارید EmailAddress و سایر اشیاء ارزش، می توانید این را به صورت زیر ساده کنید:

public record EmailAddress(string Value);
public record OrderNumber(string Value);
public record Address(string Street, string City, string Zip);

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تک خط کد، باشکوه.

ایجاد اشیاء ارزش با ساختارهای رکورد

رکوردها انتخاب فوق العاده ای برای اشیاء ارزشی هستند، اما انواع مرجع هستند.اگر به تخصیص حافظه اهمیت می دهید، می توانید استفاده کنید readonly record structs برای اشیاء ارزشی.آنها همان رفتار را دارند records اما آنها انواع ارزش هستند و در پشته تخصیص داده نمی شوند.

این انتخاب شخصی من برای ایجاد اشیاء ارزشی است.

در اینجا نحوه تعریف اشیاء ارزش با آن آمده است record structs:

public readonly record struct EmailAddress
{
public string Value { get; }

public EmailAddress(string value)
{
if (string.IsNullOrWhiteSpace(value) || !value.Contains(“@”))
{
throw new ArgumentException(“Invalid email address.”, nameof(value));
}

Value = value;
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

یا به شکل مختصرتر:

public readonly record struct EmailAddress(string Value);
public readonly record struct OrderNumber(string Value);
public readonly record struct Address(string Street, string City, string Zip);

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا نحوه Customer موجودیت با Value Objects به نظر می رسد:

public class Customer
{
public Guid Id { get; private set; }
public FirstName FirstName { get; private set; }
public LastName LastName { get; private set; }
public EmailAddress Email { get; private set; }
public PhoneNumber PhoneNumber { get; private set; }
public IReadOnlyListOrder> Orders => _orders.AsReadOnly();

private readonly ListOrder> _orders = [];

private Customer() { }

public static Customer Create(
FirstName firstName,
LastName lastName,
EmailAddress email,
PhoneNumber phoneNumber)
{
return new Customer
{
Id = Guid.NewGuid(),
FirstName = firstName,
LastName = lastName,
Email = email,
PhoneNumber = phoneNumber
};
}

public void AddOrder(Order order)
{
_orders.Add(order);
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نگاشت اشیاء ارزش در EF Core

پس از معرفی Value Objects در مدل های موجودیت خود، باید EF Core Mapping خود را اصلاح کنید.دیگر نمی‌توانید از نقشه‌برداری معمول مانند زیر استفاده کنید:

builder.Property(x => x.FirstName).IsRequired();
builder.Property(x => x.LastName).IsRequired();
builder.Property(x => x.Email).IsRequired();
builder.Property(x => x.PhoneNumber).IsRequired();

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

باید از تبدیل استفاده کنید تا به EF Core بگویید چگونه Value Object را به پایگاه داده نگاشت کند و چگونه مقادیر پایگاه داده را به ValueObjects نگاشت کند:

builder.Property(x => x.Email)
.HasConversion(
email => email.Value,
value => new EmailAddress(value)
)
.IsRequired();

builder.Property(x => x.PhoneNumber)
.HasConversion(
phoneNumber => phoneNumber.Value,
value => new PhoneNumber(value)
)
.IsRequired();

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

به کد بیشتری نیاز دارد، اما Value Objects مزایای زیادی به شما می دهد.

ارزش اشیاء و مدل های درخواست/پاسخ/DTO

Value Object مدل های دامنه خاص شما هستند، دنیای خارج نباید در مورد آنها بداند.و علاوه بر این، مدل‌های درخواست/پاسخ/DTO عمومی شما باید تا حد امکان ساده باشند.

داشتن انواع اولیه ساده در مدل های درخواست/پاسخ/DTO و نگاشت آنها به دامنه Value Object و بالعکس، تمرین خوبی است.

برای مثال، من نقشه‌برداری را در مورد استفاده «ایجاد مشتری» انجام می‌دهم:

var customer = Customer.Create(
firstName: new FirstName(request.FirstName),
lastName: new LastName(request.LastName),
email: new EmailAddress(request.Email),
phoneNumber: new PhoneNumber(request.PhoneNumber)
);

await customerRepository.AddAsync(customer, cancellationToken);
await unitOfWork.SaveChangesAsync(cancellationToken);

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و نقشه برداری معکوس به CustomerResponse از اشیاء ارزش:

internal static class MappingExtensions
{
public static CustomerResponse MapToResponse(this Customer customer)
{
return new CustomerResponse(
CustomerId: customer.Id,
FirstName: customer.FirstName.Value,
LastName: customer.LastName.Value,
Email: customer.Email.Value,
PhoneNumber: customer.PhoneNumber.Value
);
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

خلاصه

اگر تا به حال با اشکالاتی در کد خود مواجه شده اید که مقادیر اشتباه به پایگاه داده (یا هر منبع دیگری) رسیده است – در پروژه های خود از Value Objects استفاده کنید.سوابق سی شارپ و ساختارهای رکورد فقط خواندنی راهی زیبا، آسان و سریع برای پیاده سازی اشیاء ارزش بدون کد دیگ بخار ارائه می کنند.

در وب سایت من: antondevtips.com من بهترین شیوه های دات نت و معماری را به اشتراک می گذارم.برای تبدیل شدن به یک توسعه دهنده بهتر مشترک شوید.کد منبع این پست وبلاگ را به صورت رایگان دانلود کنید.

وسواس بدوی تمایل به استفاده از انواع داده های پایه برای نمایش مفاهیم پیچیده تر است.
این یک ضد الگوی رایج است که می‌تواند منجر به کد نامشخص و سیستم‌های سخت‌تر برای نگهداری شود.

در این پست وبلاگ، به شما توضیح خواهم داد که چرا وسواس اولیه می تواند منجر به مشکلاتی در برنامه های شما شود و چگونه با استفاده از Value Objects در دات نت به این موضوع رسیدگی کنید.

در وب سایت من: antondevtips.com من بهترین شیوه های دات نت و معماری را به اشتراک می گذارم.
برای تبدیل شدن به یک توسعه دهنده بهتر مشترک شوید.
کد منبع این پست وبلاگ را به صورت رایگان دانلود کنید.

چرا وسواس بدوی یک مشکل است؟

وسواس بدوی زمانی اتفاق می‌افتد که از انواع داده‌های پایه (مانند int، string یا DateTime) برای نمایش مفاهیم پیچیده در دامنه شما بیش از حد استفاده شود.
این عمل می تواند منجر به کد نامشخص، اشکالات و مشکل در نگهداری و گسترش سیستم شما شود.

چرا وسواس بدوی یک مشکل است؟

  • عدم بیان: انواع ابتدایی مانند int یا string معنای داده هایی را که نشان می دهند روشن نمی کنند. به عنوان مثال، یک رشته می تواند یک آدرس ایمیل، یک نام کاربری یا یک URL باشد، اما هیچ راهی برای تشخیص فقط با نگاه کردن به نوع آن وجود ندارد.
  • افزایش خطر خطا: هنگامی که از یک نوع اولیه برای مفاهیم مختلف استفاده می کنید، انتقال داده های نادرست آسان است. به عنوان مثال، استفاده از یک رشته برای نام کاربری و آدرس ایمیل می تواند منجر به ارسال اشتباه ایمیلی شود که در آن نام کاربری مورد انتظار است.
  • منطق اعتبارسنجی پراکنده: اعتبارسنجی برای انواع ابتدایی اغلب در سراسر پایگاه کد پراکنده می شود. هر بار که باید بررسی کنید که یک رشته یک آدرس ایمیل معتبر است، باید منطق اعتبار سنجی را بنویسید، که می تواند منجر به تکرار و ناسازگاری شود.
  • مشکل در تدوین کد: همانطور که برنامه شما رشد می کند، نیازها ممکن است تغییر کنند. اگر در همه جا از انواع اولیه استفاده کرده باشید، ایجاد این تغییرات به روشی ثابت و بدون شکست دشوار می شود.

نمونه هایی از وسواس اولیه

بیایید مثالی را بررسی کنیم که در آن a User موجودیت دارد Email دارایی
هنگام ایجاد یک کاربر، ممکن است هنگام ایجاد یک اعتبارسنجی را پیاده سازی کنید User شی

ممکن است استدلال کنید که چرا به چنین اعتبارسنجی نیاز دارید زیرا برای درخواست‌های webapi خود اعتبارسنجی ورودی دارید.
اما حقیقت این است که اعتبارسنجی برای درخواست‌های ورودی، شما را از یک اشکال در کد که در آن پارامترهای اشتباه را در سایر اجزای برنامه خود ارسال می‌کنید یا نقشه‌برداری می‌کنید، نجات نمی‌دهد.
چنین اعتبارسنجی روشی محبوب در طراحی Domain Driven است که در آن هنگام ایجاد اشیاء خود یک محافظ ایمنی نهایی ایجاد می کنید.

بیایید یک مثال را بررسی کنیم (با اعتبار سنجی ایمیل ساده، غیر آماده تولید):

public class User
{
    public string Email { get; set; }

    public User(string email)
    {
        if (string.IsNullOrWhiteSpace(email) || !email.Contains("@"))
        {
            throw new ArgumentException("Invalid email address", nameof(email));
        }

        Email = email;
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

چرا این یک مشکل است؟

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

بیایید مثال دیگری را بررسی کنیم:

public class User
{
    public string Email { get; }
    public string Username { get; }

    public User(string email, string username)
    {
        if (string.IsNullOrWhiteSpace(email))
        {
            throw new ArgumentException("Email is required", nameof(email));
        }

        if (string.IsNullOrWhiteSpace(username))
        {
            throw new ArgumentException("Username is required", nameof(username));
        }

        Email = email;
        Username = username;
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بیایید سعی کنیم یک کاربر ایجاد کنیم:

var user = new User("anton@test.com", "anton");
var user = new User("anton", "anton@test.com");
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این کد با موفقیت کامپایل و اجرا می شود. آیا متوجه مشکلی شدید؟

همانطور که استفاده می کنیم string ایمیل و نام کاربری را تایپ کنید – می توانید پارامترها را به ترتیب اشتباهی ارسال کنید.
و اگر در هنگام ساخت اشیا اعتبارسنجی نداشته باشید، همانطور که در بالا به شما نشان دادم، در نهایت با داده های اشتباه در پایگاه داده مواجه خواهید شد.
این می تواند منجر به مشکلات جدی شود.

راه حل این مشکل استفاده است اشیاء ارزشی، که داده ها و رفتار مرتبط را در یک واحد واحد و معنی دار محصور می کند.

اشیاء ارزشی چیست؟

اشیاء ارزشی نشان دهنده یک مقدار در دامنه شما است که هویت ندارد اما با ویژگی های آن تعریف می شود.
به عنوان مثال، یک Address ممکن است یک شی ارزش باشد، همانطور که با ویژگی های آن (خیابان، شهر، پست) تعریف می شود.

اشیاء ارزش یک مفهوم کلیدی در طراحی دامنه محور (DDD) هستند.

ویژگی های کلیدی اشیاء ارزشی:

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

مزایای استفاده از اشیاء ارزشی:

  • بیانگر بودن: کد خواناتر و خود توضیحی تر می شود.
  • کپسولاسیون: قوانین کسب و کار در داخل شئ ارزش گنجانده شده اند و از ثبات در هر مکانی که استفاده می شوند اطمینان حاصل می کنند.
  • کاهش اشکالات: با محدود کردن دامنه انواع اولیه، خطر انتقال داده های نادرست را کاهش می دهید.
  • قابلیت استفاده مجدد: ارزش اشیاء را می توان در بخش های مختلف برنامه مورد استفاده مجدد قرار داد و اصل DRY (خودت را تکرار نکن) ترویج می کند.

حالا بیایید بررسی کنیم که چه گزینه هایی برای ایجاد Value Objects در NET داریم.

یک برنامه کاربردی

امروز به شما نحوه پیاده سازی را نشان خواهم داد اشیاء ارزشی برای یک برنامه حمل و نقل که مسئول ایجاد و به روز رسانی مشتریان، سفارشات و محموله های محصولات سفارش داده شده است.

این نرم افزار دارای موجودیت های زیر است:

  • مشتریان
  • سفارشات، موارد سفارش
  • محموله ها، اقلام حمل و نقل

من از شیوه های Domain Driven Design برای نهادهایم استفاده می کنم.
بیایید a را بررسی کنیم Customer و Shipment موجودیت هایی که از انواع اولیه برای همه ویژگی ها استفاده می کنند:

public class Customer
{
    public Guid Id { get; private set; }
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public string Email { get; private set; }
    public string PhoneNumber { get; private set; }
    public IReadOnlyListOrder> Orders => _orders.AsReadOnly();

    private readonly ListOrder> _orders = [];

    private Customer() { }

    public static Customer Create(
        string firstName,
        string lastName,
        string email,
        string phoneNumber)
    {
        return new Customer
        {
            Id = Guid.NewGuid(),
            FirstName = firstName,
            LastName = lastName,
            Email = email,
            PhoneNumber = phoneNumber
        };
    }

    public void AddOrder(Order order)
    {
        _orders.Add(order);
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

public class Shipment
{
    private readonly ListShipmentItem> _items = [];

    public Guid Id { get; private set; }

    public string Number { get; private set; }

    public Guid OrderId { get; private set; }

    public string Address { get; private set; }

    public string Carrier { get; private set; }

    public string ReceiverEmail { get; private set; }

    public ShipmentStatus Status { get; private set; }

    public IReadOnlyListShipmentItem> Items => _items.AsReadOnly();

    public DateTime CreatedAt { get; private set; }

    public DateTime? UpdatedAt { get; private set; }

    private Shipment()
    {
    }

    public static Shipment Create(
        string number,
        Guid orderId,
        string address,
        string carrier,
        string receiverEmail,
        ListShipmentItem> items)
    {
        var shipment = new Shipment
        {
            Id = Guid.NewGuid(),
            Number = number,
            OrderId = orderId,
            Address = address,
            Carrier = carrier,
            ReceiverEmail = receiverEmail,
            Status = ShipmentStatus.Created,
            CreatedAt = DateTime.UtcNow
        };

        shipment.AddItems(items);

        return shipment;
    }

    public void AddItems(ListShipmentItem> items)
    {
        _items.AddRange(items);
        UpdatedAt = DateTime.UtcNow;
    }

    public void AddItem(ShipmentItem item)
    {
        _items.Add(item);
        UpdatedAt = DateTime.UtcNow;
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این موجودات به انواع اولیه وسواس دارند: شماره محموله، آدرس، آدرس ایمیل، شماره تلفن و غیره.
بیایید نحوه جایگزینی این انواع اولیه با اشیاء ارزش را بررسی کنیم.

ایجاد آبجکت های ارزشی در دات نت

من می توانم محبوب ترین گزینه های زیر را برای ایجاد اشیاء ارزش لیست کنم:

  • با استفاده از کتابخانه ValueOf
  • با استفاده از C# Records
  • با استفاده از C# Record Structs

بیایید هر گزینه را عمیق تر بررسی کنیم.

ایجاد اشیاء ارزش با ValueOf

ValueOf یکی از کتابخانه های محبوب برای ایجاد اشیاء ارزش با ارائه یک کلاس پایه است که بسیاری از کدهای boilerplate را مدیریت می کند.

ابتدا باید بسته را نصب کنید:

dotnet add package ValueOf
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا نحوه استفاده از آن آورده شده است ValueOf کتابخانه برای ایجاد اشیاء ارزش:

using ValueOf;

public class EmailAddress : ValueOfstring, EmailAddress>
{
    protected override void Validate()
    {
        if (string.IsNullOrWhiteSpace(Value) || !Value.Contains("@"))
        {
            throw new ArgumentException("Invalid email address.");
        }
    }
}

public class OrderNumber : ValueOfstring, OrderNumber>
{
    protected override void Validate()
    {
        if (string.IsNullOrWhiteSpace(Value))
        {
            throw new ArgumentException("Order number cannot be empty.");
        }
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

شما باید کلاس Value Object (EmailAddress و OrderNumber) خود را از یک پایه به ارث ببرید ValueOf کلاس و ارائه 2 نوع عمومی:

  • یک نوع ابتدایی زیرخط دار
  • خود یک نوع Object مقدار

ValueOf از تاپل های سی شارپ پشتیبانی کنید، اگر نیاز دارید که شی مقدار خود را به عنوان چندین ویژگی نشان دهید، به عنوان مثال، آدرس:

using ValueOf;

public class Address : ValueOfstring Street, string City, string Zip), Address>
{
    protected override void Validate()
    {
        if (string.IsNullOrWhiteSpace(Value.Street))
        {
            throw new ArgumentException("Street cannot be empty.");
        }

        if (string.IsNullOrWhiteSpace(Value.City))
        {
            throw new ArgumentException("City cannot be empty.");
        }

        if (string.IsNullOrWhiteSpace(Value.Zip))
        {
            throw new ArgumentException("Zip code cannot be empty.");
        }
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا نحوه ایجاد این اشیاء ارزشی آمده است:

var email = EmailAddress.From("anton@test.com");
var orderNumber = OrderNumber.From("ORD-12345");
var address = Address.From(("123 Main St", "Springfield", "12345"));
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

می توانید از a استفاده کنید Value ویژگی برای بازیابی مقدار پنهان در اشیاء ارزش:

string emailValue = email.Value;
string orderNumberValue = orderNumber.Value;
(string street, string city, string zip) = address.Value;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ایجاد اشیاء ارزش با رکوردها

برخی از توسعه دهندگان با استفاده از Value Objects پیاده سازی می کنند ValueOf کتابخانه، حتی بیشتر پیاده سازی های خود را ایجاد می کنند.
چه می شود اگر به شما بگویم که سی شارپ رکوردها در حال حاضر تمام چیزهایی که برای اشیاء ارزش نیاز دارید را دارید.

رکوردها یک روش واقعا مدرن برای ایجاد Value Object در دات نت هستند.

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

در اینجا نحوه تعریف اشیاء یکسان با استفاده از رکوردها آمده است:

public record EmailAddress
{
    public string Value { get; }

    public EmailAddress(string value)
    {
        if (string.IsNullOrWhiteSpace(value) || !value.Contains("@"))
        {
            throw new ArgumentException("Invalid email address.", nameof(value));
        }

        Value = value;
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر نیازی به تأیید اعتبار در داخل ندارید EmailAddress و سایر اشیاء ارزش، می توانید این را به صورت زیر ساده کنید:

public record EmailAddress(string Value);
public record OrderNumber(string Value);
public record Address(string Street, string City, string Zip);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تک خط کد، باشکوه.

ایجاد اشیاء ارزش با ساختارهای رکورد

رکوردها انتخاب فوق العاده ای برای اشیاء ارزشی هستند، اما انواع مرجع هستند.
اگر به تخصیص حافظه اهمیت می دهید، می توانید استفاده کنید readonly record structs برای اشیاء ارزشی.
آنها همان رفتار را دارند records اما آنها انواع ارزش هستند و در پشته تخصیص داده نمی شوند.

این انتخاب شخصی من برای ایجاد اشیاء ارزشی است.

در اینجا نحوه تعریف اشیاء ارزش با آن آمده است record structs:

public readonly record struct EmailAddress
{
    public string Value { get; }

    public EmailAddress(string value)
    {
        if (string.IsNullOrWhiteSpace(value) || !value.Contains("@"))
        {
            throw new ArgumentException("Invalid email address.", nameof(value));
        }

        Value = value;
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

یا به شکل مختصرتر:

public readonly record struct EmailAddress(string Value);
public readonly record struct OrderNumber(string Value);
public readonly record struct Address(string Street, string City, string Zip);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا نحوه Customer موجودیت با Value Objects به نظر می رسد:

public class Customer
{
    public Guid Id { get; private set; }
    public FirstName FirstName { get; private set; }
    public LastName LastName { get; private set; }
    public EmailAddress Email { get; private set; }
    public PhoneNumber PhoneNumber { get; private set; }
    public IReadOnlyListOrder> Orders => _orders.AsReadOnly();

    private readonly ListOrder> _orders = [];

    private Customer() { }

    public static Customer Create(
        FirstName firstName,
        LastName lastName,
        EmailAddress email,
        PhoneNumber phoneNumber)
    {
        return new Customer
        {
            Id = Guid.NewGuid(),
            FirstName = firstName,
            LastName = lastName,
            Email = email,
            PhoneNumber = phoneNumber
        };
    }

    public void AddOrder(Order order)
    {
        _orders.Add(order);
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نگاشت اشیاء ارزش در EF Core

پس از معرفی Value Objects در مدل های موجودیت خود، باید EF Core Mapping خود را اصلاح کنید.
دیگر نمی‌توانید از نقشه‌برداری معمول مانند زیر استفاده کنید:

builder.Property(x => x.FirstName).IsRequired();
builder.Property(x => x.LastName).IsRequired();
builder.Property(x => x.Email).IsRequired();
builder.Property(x => x.PhoneNumber).IsRequired();
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

باید از تبدیل استفاده کنید تا به EF Core بگویید چگونه Value Object را به پایگاه داده نگاشت کند و چگونه مقادیر پایگاه داده را به ValueObjects نگاشت کند:

builder.Property(x => x.Email)
    .HasConversion(
        email => email.Value,
        value => new EmailAddress(value)
    )
    .IsRequired();

builder.Property(x => x.PhoneNumber)
    .HasConversion(
        phoneNumber => phoneNumber.Value,
        value => new PhoneNumber(value)
    )
    .IsRequired();
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

به کد بیشتری نیاز دارد، اما Value Objects مزایای زیادی به شما می دهد.

ارزش اشیاء و مدل های درخواست/پاسخ/DTO

Value Object مدل های دامنه خاص شما هستند، دنیای خارج نباید در مورد آنها بداند.
و علاوه بر این، مدل‌های درخواست/پاسخ/DTO عمومی شما باید تا حد امکان ساده باشند.

داشتن انواع اولیه ساده در مدل های درخواست/پاسخ/DTO و نگاشت آنها به دامنه Value Object و بالعکس، تمرین خوبی است.

برای مثال، من نقشه‌برداری را در مورد استفاده «ایجاد مشتری» انجام می‌دهم:

var customer = Customer.Create(
    firstName: new FirstName(request.FirstName),
    lastName: new LastName(request.LastName),
    email: new EmailAddress(request.Email),
    phoneNumber: new PhoneNumber(request.PhoneNumber)
);

await customerRepository.AddAsync(customer, cancellationToken);
await unitOfWork.SaveChangesAsync(cancellationToken);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و نقشه برداری معکوس به CustomerResponse از اشیاء ارزش:

internal static class MappingExtensions
{
    public static CustomerResponse MapToResponse(this Customer customer)
    {
        return new CustomerResponse(
            CustomerId: customer.Id,
            FirstName: customer.FirstName.Value,
            LastName: customer.LastName.Value,
            Email: customer.Email.Value,
            PhoneNumber: customer.PhoneNumber.Value
        );
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

خلاصه

اگر تا به حال با اشکالاتی در کد خود مواجه شده اید که مقادیر اشتباه به پایگاه داده (یا هر منبع دیگری) رسیده است – در پروژه های خود از Value Objects استفاده کنید.
سوابق سی شارپ و ساختارهای رکورد فقط خواندنی راهی زیبا، آسان و سریع برای پیاده سازی اشیاء ارزش بدون کد دیگ بخار ارائه می کنند.

در وب سایت من: antondevtips.com من بهترین شیوه های دات نت و معماری را به اشتراک می گذارم.
برای تبدیل شدن به یک توسعه دهنده بهتر مشترک شوید.
کد منبع این پست وبلاگ را به صورت رایگان دانلود کنید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا