Domain-Driven Design (DDD)؛ بخش اول: مقدمه و آشنایی با مفاهیم پایه DDD
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۵/۲۷ ۰۸:۱۰
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
Products، Customers و Orders را ایجاد میکنید و سپس منطق برنامهنویسی را بر روی این ساختار دادهای میسازید. این رویکرد، در نگاه اول ساده و منطقی به نظر میرسد، اما به تدریج با اضافه شدن نیازمندیهای پیچیده کسبوکار، با مشکلاتی مواجه میشوید. این مدل، که به آن Anemic Domain Model (مدل دامنه بیجان) میگویند، عموماً شامل کلاسهایی است که فقط دادهها را نگهداری میکنند و هیچ منطق تجاری (Business Logic) در خود ندارند. تمام منطق در Service های بیرونی قرار میگیرد، که باعث پراکندگی کد و دشواری در نگهداری میشود.Order فقط شامل ویژگیهایی مثل OrderDate, CustomerId و TotalAmount است و منطق مربوط به آن، مثل CalculateTotalPrice(), CancelOrder() و ApplyDiscount()، در یک کلاس جداگانه به نام OrderService قرار میگیرد.public class Order
{
public int OrderId { get; set; }
public int CustomerId { get; set; }
public decimal TotalAmount { get; set; }
public DateTime OrderDate { get; set; }
}
public class OrderService
{
public void CancelOrder(Order order)
{
// Business logic for cancellation
}
}Order شامل تمام منطق مربوط به خودش است.public class Order
{
public int OrderId { get; private set; }
public int CustomerId { get; private set; }
public decimal TotalAmount { get; private set; }
public DateTime OrderDate { get; private set; }
public OrderStatus Status { get; private set; }
public void Cancel()
{
if (this.Status == OrderStatus.Shipped)
{
throw new InvalidOperationException("Cannot cancel a shipped order.");
}
this.Status = OrderStatus.Canceled;
// Raise a Domain Event to notify other parts of the system
AddDomainEvent(new OrderCanceledEvent(this.OrderId));
}
}Orderمسئول رفتار خود است. این یعنی منطق مربوط به لغو سفارش به صورت کپسولهشده در داخل خود کلاس قرار دارد و نمیتوان از بیرون آن را به صورت ناخواسته تغییر داد. این باعث میشود کد قابل فهمتر، قابل تستتر و در برابر تغییرات مقاومتر باشد.ReturnItem یا UndoPurchase استفاده کنیم. این زبان مشترک، سوتفاهمها را به حداقل میرساند و ارتباط بین تیم فنی و تیم کسبوکار را تسهیل میکند.Order در پروژه .NET شما. این کلاس ممکن است چیزی شبیه به این باشد:public class Order
{
public int Id { get; set; }
public DateTime OrderDate { get; set; }
public decimal TotalAmount { get; set; }
public List<OrderItem> Items { get; set; }
}Order فقط یک کیسه داده است – propertyهایی برای نگهداری اطلاعات، بدون هیچ متدی که رفتار واقعی سفارش را توصیف کند. حالا، منطق کسبوکار کجاست؟ معمولاً در لایههای دیگر، مثل سرویسها: مثلاً یک OrderService که متدی مانند CalculateTotal(Order order) دارد. با EF-Core، این مدل به راحتی به جدول دیتابیس نقشهبرداری میشود، چون ساده است و تمرکز روی CRUD (Create, Read, Update, Delete) عملیات است.Order را تصور کنید، اما این بار زندهتر:public class Order
{
public int Id { private set; get; }
public DateTime OrderDate { private set; get; }
private List<OrderItem> _items = new List<OrderItem>();
public IReadOnlyList<OrderItem> Items => _items.AsReadOnly();
public decimal TotalAmount => CalculateTotal();
public Order(DateTime orderDate)
{
OrderDate = orderDate;
}
public void AddItem(OrderItem item)
{
if (item == null) throw new ArgumentNullException(nameof(item));
_items.Add(item);
}
private decimal CalculateTotal()
{
return _items.Sum(item => item.Price * item.Quantity);
}
public void ApplyDiscount(decimal percentage)
{
if (percentage < 0 || percentage > 100) throw new ArgumentOutOfRangeException(nameof(percentage));
// منطق اعمال تخفیف
}
}Order نه تنها داده دارد، بلکه متدهایی برای افزودن آیتم، محاسبه مجموع، یا اعمال تخفیف – همه در جایی که منطقاً باید باشد: درون خود مدل دامنه. با EF-Core، این کار کمی پیچیدهتر است؛ مثلاً باید از private setters استفاده کنید یا constructorهای خاص تعریف کنید تا ORM بتواند کار کند. اما پاداشش چیست؟ کدی که بیشتر به زبان کسبوکار نزدیک است، آسانتر برای نگهداری، و کمتر مستعد خطا. آیا این شما را به فکر نمیاندازد که چقدر مدلهای غنی میتوانند سیستم را انعطافپذیرتر کنند، به خصوص وقتی قوانین کسبوکار تغییر میکنند؟