پاسخ: Domain-Driven Design (DDD) یک رویکرد توسعه نرمافزار است که بر مدلسازی دامنه کسبوکار (business domain) تمرکز دارد. هدف اصلی DDD این است که پیچیدگیهای ذاتی یک دامنه کسبوکار را درک کرده و آنها را در مدل نرمافزاری منعکس کند. این رویکرد توسط Eric Evans در کتاب “Domain-Driven Design: Tackling Complexity in the Heart of Software” معرفی شد.
هدف اصلی DDD:
هدف اصلی DDD ایجاد نرمافزاری است که:
DDD بر این ایده استوار است که قلب نرمافزار (The Heart of Software) در مدل دامنه آن نهفته است. بنابراین، باید زمان و تلاش زیادی را صرف درک عمیق دامنه و مدلسازی دقیق آن کرد.
پاسخ: Ubiquitous Language (زبان فراگیر) یک زبان مشترک و دقیق است که توسط تمام اعضای تیم پروژه، شامل متخصصان دامنه (Domain Experts) و توسعهدهندگان (Developers)، برای بحث و مدلسازی دامنه کسبوکار استفاده میشود. این زبان باید در تمام جنبههای پروژه، از مکالمات روزمره و مستندات گرفته تا کد منبع، به طور مداوم و بدون ابهام به کار گرفته شود.
اهمیت Ubiquitous Language:
مثال:
فرض کنید در یک سیستم بانکی، متخصصان دامنه از اصطلاح “حساب مشتری” (Customer Account) استفاده میکنند. اگر توسعهدهندگان در کد خود از “User Profile” یا “Client Record” استفاده کنند، این باعث سردرگمی و عدم همسویی میشود. در یک پروژه DDD، همه باید از “Customer Account” استفاده کنند و این اصطلاح باید در کد نیز به همین شکل (مثلاً کلاس CustomerAccount) ظاهر شود.
Ubiquitous Language یک ابزار زنده است که با تکامل درک تیم از دامنه، تکامل مییابد.
پاسخ: Bounded Context (محدوده بافت) یک مفهوم کلیدی در Domain-Driven Design است که به مدیریت پیچیدگی در دامنههای بزرگ کمک میکند. یک Bounded Context یک مرز منطقی و مفهومی است که در داخل آن، یک مدل دامنه خاص و Ubiquitous Language مربوط به آن، معنای دقیق و بدون ابهام خود را حفظ میکند.
چرا به Bounded Context نیاز داریم؟
ویژگیهای Bounded Context:
به طور خلاصه، Bounded Context به ما کمک میکند تا دامنههای بزرگ را به بخشهای کوچکتر و قابل مدیریتتر تقسیم کنیم، که هر کدام مدل دامنه و زبان مشترک خاص خود را دارند و این امر به مدیریت پیچیدگی و توسعه مؤثرتر نرمافزار کمک میکند.
پاسخ: در Domain-Driven Design (DDD)، یک دامنه بزرگ به زیردامنهها (Subdomains) تقسیم میشود. این زیردامنهها بر اساس اهمیت استراتژیک و پیچیدگی کسبوکار به سه دسته اصلی تقسیم میشوند:
اهمیت تفکیک زیردامنهها:
تفکیک زیردامنهها به تیمها کمک میکند تا منابع و تلاش خود را به طور مؤثرتری متمرکز کنند. بیشترین سرمایهگذاری باید روی Core Domain انجام شود، در حالی که برای Generic Subdomain ها میتوان از راهحلهای آماده استفاده کرد تا زمان و هزینه توسعه کاهش یابد.
پاسخ: Aggregate (تجمیع) یک مفهوم حیاتی در Domain-Driven Design است که به مدیریت یکپارچگی دادهها و حفظ consistency (سازگاری) در مدل دامنه کمک میکند. یک Aggregate مجموعهای از Entity ها و Value Object ها است که به عنوان یک واحد منطقی در نظر گرفته میشوند و توسط یک Entity خاص به نام Aggregate Root (ریشه تجمیع) محافظت میشوند.
ویژگیهای اصلی Aggregate:
چرا از Aggregate استفاده میکنیم؟
مثال:
public class Order : AggregateRoot // Order به عنوان Aggregate Root
{
public Guid Id { get; private set; }
public int CustomerId { get; private set; }
public DateTime OrderDate { get; private set; }
public OrderStatus Status { get; private set; }
private readonly List<OrderLineItem> _lineItems; // OrderLineItem ها Entity های داخلی
public IReadOnlyList<OrderLineItem> LineItems => _lineItems.AsReadOnly();
// سازنده
private Order() { _lineItems = new List<OrderLineItem>(); }
public static Order Create(Guid id, int customerId)
{
var order = new Order { Id = id, CustomerId = customerId, OrderDate = DateTime.UtcNow, Status = OrderStatus.Pending };
// افزودن رویداد دامنه
return order;
}
public void AddLineItem(Guid productId, int quantity, decimal unitPrice)
{
// منطق کسبوکار و حفظ invariants
if (quantity <= 0) throw new ArgumentException("Quantity must be positive.");
var lineItem = new OrderLineItem(Guid.NewGuid(), productId, quantity, unitPrice);
_lineItems.Add(lineItem);
// افزودن رویداد دامنه
}
public void ConfirmOrder()
{
if (Status != OrderStatus.Pending) throw new InvalidOperationException("Order cannot be confirmed.");
Status = OrderStatus.Confirmed;
// افزودن رویداد دامنه
}
// ... سایر متدهای عملیاتی که invariants را حفظ میکنند
}
public class OrderLineItem : Entity // OrderLineItem یک Entity داخلی است
{
public Guid Id { get; private set; }
public Guid ProductId { get; private set; }
public int Quantity { get; private set; }
public decimal UnitPrice { get; private set; }
public OrderLineItem(Guid id, Guid productId, int quantity, decimal unitPrice)
{
Id = id;
ProductId = productId;
Quantity = quantity;
UnitPrice = unitPrice;
}
}
public enum OrderStatus { Pending, Confirmed, Shipped, Cancelled }
در این مثال، Order Aggregate Root است. تمام عملیات بر روی OrderLineItem ها باید از طریق متدهای Order (مانند AddLineItem) انجام شود تا Order بتواند قوانین کسبوکار مربوط به سفارش را حفظ کند.
پاسخ: Entity (موجودیت) یک مفهوم اساسی در Domain-Driven Design (DDD) است که یک شیء در مدل دامنه را نشان میدهد که دارای یک هویت منحصر به فرد (Unique Identity) است و این هویت در طول زمان و تغییرات حالت آن شیء، ثابت باقی میماند. Entity ها معمولاً شامل منطق کسبوکار (Behavior) و دادهها (State) هستند.
ویژگیهای اصلی Entity:
چرا از Entity استفاده میکنیم؟
مثال:
public class Customer
{
public Guid Id { get; private set; } // هویت منحصر به فرد
public string Name { get; private set; }
public string Email { get; private set; }
public Customer(Guid id, string name, string email)
{
Id = id;
Name = name;
Email = email;
}
public void ChangeEmail(string newEmail)
{
// منطق کسبوکار برای تغییر ایمیل
if (string.IsNullOrWhiteSpace(newEmail)) throw new ArgumentException("Email cannot be empty.");
Email = newEmail;
}
}
در این مثال، Customer یک Entity است. حتی اگر نام و ایمیل مشتری تغییر کند، Id آن ثابت میماند و مشتری را به صورت منحصر به فرد شناسایی میکند.
پاسخ: Value Object (شیء ارزشی) یک مفهوم اساسی دیگر در Domain-Driven Design (DDD) است که یک شیء در مدل دامنه را نشان میدهد که فاقد هویت منحصر به فرد است و تنها با ویژگیهایش تعریف میشود. Value Object ها معمولاً برای مدلسازی مفاهیمی استفاده میشوند که ماهیت آنها با مقادیرشان تعیین میشود، نه با یک شناسه خاص.
ویژگیهای اصلی Value Object:
چرا از Value Object استفاده میکنیم؟
مثال:
public class Money : ValueObject
{
public decimal Amount { get; }
public string Currency { get; }
public Money(decimal amount, string currency)
{
if (amount < 0) throw new ArgumentException("Amount cannot be negative.");
if (string.IsNullOrWhiteSpace(currency)) throw new ArgumentException("Currency cannot be empty.");
Amount = amount;
Currency = currency;
}
public Money Add(Money other)
{
if (Currency != other.Currency) throw new InvalidOperationException("Cannot add money of different currencies.");
return new Money(Amount + other.Amount, Currency);
}
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Amount;
yield return Currency;
}
}
در این مثال، Money یک Value Object است. دو شیء Money با Amount و Currency یکسان، برابر محسوب میشوند. متد Add یک Money جدید را برمیگرداند، زیرا Money تغییرناپذیر است.
پاسخ: تفاوت اصلی بین Entity و Value Object در Domain-Driven Design (DDD) در مفهوم هویت (Identity) و تغییرپذیری (Mutability) آنها نهفته است. درک این تفاوت برای مدلسازی صحیح دامنه بسیار حیاتی است.
ویژگی | Entity | Value Object |
---|---|---|
هویت | دارای هویت منحصر به فرد (Unique Identity) است که در طول زمان ثابت میماند. | فاقد هویت منحصر به فرد است؛ با مقادیرش تعریف میشود. |
برابری | برابری بر اساس هویت (مرجع) تعیین میشود. | برابری بر اساس مقادیر تمام ویژگیها تعیین میشود. |
تغییرپذیری | تغییرپذیر (Mutable) است؛ حالت آن میتواند در طول زمان تغییر کند. | تغییرناپذیر (Immutable) است؛ پس از ایجاد، حالت آن قابل تغییر نیست. |
چرخه حیات | دارای چرخه حیات (ایجاد، تغییر، حذف) است. | فاقد چرخه حیات مستقل؛ وجود آن به Entity ای که آن را شامل میشود، وابسته است. |
هدف | مدلسازی اشیائی که نیاز به ردیابی و تمایز بر اساس هویت دارند. | مدلسازی مفاهیمی که ماهیت آنها با مقادیرشان تعیین میشود و برای توصیف ویژگیهای Entity ها استفاده میشوند. |
مثال | Customer, Order, Product (در بافت کاتالوگ) | Address, Money, DateRange, Color |
خلاصه:
اگر شیء شما نیاز به ردیابی منحصر به فرد در طول زمان دارد و حالت آن ممکن است تغییر کند، آن یک Entity است. اگر شیء شما تنها با مقادیرش تعریف میشود و پس از ایجاد تغییر نمیکند، آن یک Value Object است. استفاده صحیح از این دو مفهوم به ایجاد یک مدل دامنه قویتر، خواناتر و قابل نگهداریتر کمک میکند.
پاسخ: Domain Service (سرویس دامنه) یک مفهوم در Domain-Driven Design (DDD) است که برای کپسولهسازی منطق کسبوکاری استفاده میشود که به طور طبیعی به یک Entity یا Value Object خاص تعلق ندارد. این منطق معمولاً شامل هماهنگی بین چندین Aggregate یا انجام عملیاتی است که ماهیت فرایندی دارند و نه ماهیت موجودیتی.
ویژگیهای اصلی Domain Service:
چه زمانی از Domain Service استفاده میکنیم؟
از Domain Service زمانی استفاده میکنیم که:
مثال:
فرض کنید در یک سیستم بانکی، نیاز به انتقال وجه از یک حساب به حساب دیگر داریم. این عملیات شامل دو Aggregate مجزا (SourceAccount و DestinationAccount) است. منطق انتقال وجه به طور طبیعی به هیچ یک از این حسابها تعلق ندارد، بلکه یک فرآیند بین آنهاست. بنابراین، میتوانیم یک Domain Service به نام TransferMoneyService ایجاد کنیم:
public class TransferMoneyService
{
private readonly IAccountRepository _accountRepository;
public TransferMoneyService(IAccountRepository accountRepository)
{
_accountRepository = accountRepository;
}
public void Transfer(Guid sourceAccountId, Guid destinationAccountId, decimal amount)
{
// 1. دریافت حسابها از Repository
var sourceAccount = _accountRepository.GetById(sourceAccountId);
var destinationAccount = _accountRepository.GetById(destinationAccountId);
if (sourceAccount == null || destinationAccount == null)
{
throw new ArgumentException("One or both accounts not found.");
}
// 2. اعمال منطق کسبوکار (برداشت و واریز)
sourceAccount.Withdraw(amount);
destinationAccount.Deposit(amount);
// 3. ذخیره تغییرات (معمولاً در لایه Application Service یا Unit of Work مدیریت میشود)
_accountRepository.Save(sourceAccount);
_accountRepository.Save(destinationAccount);
// افزودن رویداد دامنه (مثلاً MoneyTransferredEvent)
}
}
در این مثال، TransferMoneyService مسئول هماهنگی عملیات برداشت و واریز بین دو حساب است و منطق مربوط به این فرآیند را کپسوله میکند.
پاسخ: Application Service (سرویس کاربرد) در Domain-Driven Design (DDD) یک لایه نازک است که وظیفه هماهنگی عملیات بین لایه رابط کاربری (UI) و لایه دامنه را بر عهده دارد. Application Service ها منطق کسبوکار را شامل نمیشوند، بلکه جریان کار (workflow) را مدیریت میکنند و مسئولیتهای زیرساختی مانند مدیریت تراکنشها، امنیت و ارسال رویدادها را بر عهده دارند.
ویژگیهای اصلی Application Service:
چه زمانی از Application Service استفاده میکنیم؟
از Application Service زمانی استفاده میکنیم که:
مثال:
public class OrderApplicationService
{
private readonly IOrderRepository _orderRepository;
private readonly IProductRepository _productRepository;
private readonly IDomainEventDispatcher _eventDispatcher;
public OrderApplicationService(IOrderRepository orderRepository, IProductRepository productRepository, IDomainEventDispatcher eventDispatcher)
{
_orderRepository = orderRepository;
_productRepository = productRepository;
_eventDispatcher = eventDispatcher;
}
// یک Command Handler برای ایجاد سفارش جدید
public void CreateOrder(CreateOrderCommand command)
{
// 1. اعتبارسنجی ورودی (اختیاری، میتواند در لایه بالاتر هم انجام شود)
if (command.Quantity <= 0) throw new ArgumentException("Quantity must be positive.");
// 2. دریافت Aggregate Root از Repository
var product = _productRepository.GetById(command.ProductId);
if (product == null) throw new ArgumentException("Product not found.");
// 3. فراخوانی منطق دامنه
var order = Order.Create(Guid.NewGuid(), command.CustomerId);
order.AddLineItem(product.Id, command.Quantity, product.Price);
// 4. ذخیره Aggregate Root
_orderRepository.Add(order);
// 5. انتشار رویدادهای دامنه (مثلاً OrderCreatedEvent)
_eventDispatcher.Dispatch(order.DomainEvents);
}
// یک Command Handler برای تایید سفارش
public void ConfirmOrder(Guid orderId)
{
var order = _orderRepository.GetById(orderId);
if (order == null) throw new ArgumentException("Order not found.");
order.ConfirmOrder(); // فراخوانی منطق دامنه
_orderRepository.Save(order);
_eventDispatcher.Dispatch(order.DomainEvents);
}
}
در این مثال، OrderApplicationService درخواستهای CreateOrderCommand و ConfirmOrderCommand را دریافت میکند، Aggregate Order را از Repository بازیابی میکند، متدهای دامنه را فراخوانی میکند و سپس تغییرات را ذخیره کرده و رویدادها را منتشر میکند. این سرویس هیچ منطق کسبوکار مستقیمی ندارد.
پاسخ: Repository (مخزن) یک مفهوم طراحی در Domain-Driven Design (DDD) است که به عنوان یک واسط بین لایه دامنه و لایه زیرساخت (مانند دیتابیس) عمل میکند. Repository ها مسئولیت بازیابی و ذخیرهسازی Aggregate Root ها را بر عهده دارند.
نقش Repository:
ویژگیهای Repository:
مثال:
public interface IOrderRepository
{
Order GetById(Guid id);
void Add(Order order);
void Update(Order order);
void Remove(Order order);
IEnumerable<Order> GetOrdersByCustomerId(int customerId);
}
این واسط در لایه دامنه تعریف میشود و پیادهسازی آن (مثلاً EfCoreOrderRepository) در لایه زیرساخت قرار میگیرد.
پاسخ: Domain Event (رویداد دامنه) چیزی است که در دامنه کسبوکار اتفاق افتاده و سایر بخشهای سیستم یا سیستمهای خارجی ممکن است به آن علاقهمند باشند. Domain Event ها به ما کمک میکنند تا کوپلینگ (coupling) بین بخشهای مختلف دامنه را کاهش دهیم و سیستمهای مقیاسپذیرتر و قابل نگهداریتری بسازیم.
ویژگیهای اصلی Domain Event:
چه زمانی از Domain Event استفاده میکنیم؟
از Domain Event زمانی استفاده میکنیم که:
مثال:
public class OrderCreatedEvent : IDomainEvent
{
public Guid OrderId { get; }
public int CustomerId { get; }
public DateTime OrderDate { get; }
public OrderCreatedEvent(Guid orderId, int customerId, DateTime orderDate)
{
OrderId = orderId;
CustomerId = customerId;
OrderDate = orderDate;
}
}
// در Aggregate Root Order
public class Order : AggregateRoot
{
// ...
public static Order Create(Guid id, int customerId)
{
var order = new Order { Id = id, CustomerId = customerId, OrderDate = DateTime.UtcNow, Status = OrderStatus.Pending };
order.AddDomainEvent(new OrderCreatedEvent(order.Id, order.CustomerId, order.OrderDate));
return order;
}
// ...
}
پس از انتشار OrderCreatedEvent، سرویسهای دیگر میتوانند به آن گوش دهند و عملیات مربوطه را انجام دهند (مثلاً سرویس پرداخت میتواند پرداخت را آغاز کند، سرویس انبارداری میتواند موجودی را کاهش دهد).
پاسخ: Domain Service و Application Service هر دو نوعی از سرویسها در Domain-Driven Design (DDD) هستند، اما نقشها و مسئولیتهای کاملاً متفاوتی دارند. درک این تفاوت برای طراحی صحیح معماری سیستم بسیار مهم است.
ویژگی | Domain Service | Application Service |
---|---|---|
مکان در معماری | لایه دامنه (Domain Layer) | لایه کاربرد (Application Layer) |
هدف اصلی | کپسولهسازی منطق کسبوکار که به یک Entity یا Value Object خاص تعلق ندارد. | هماهنگی جریان کار (workflow) بین UI و لایه دامنه؛ مدیریت تراکنشها و امنیت. |
شامل منطق کسبوکار؟ | بله، شامل منطق کسبوکار است. | خیر، نباید شامل منطق کسبوکار باشد. |
حالت (State) | معمولاً Stateless (فاقد حالت) | Stateless |
نامگذاری | بر اساس یک فعالیت یا فرآیند دامنه (مثلاً TransferMoneyService) | بر اساس یک مورد استفاده یا فرمان (مثلاً OrderService, CreateOrderCommand) |
وابستگیها | وابسته به Entities, Value Objects, Repositories | وابسته به Domain Services, Repositories |
مثال | CurrencyConverter, FraudDetectionService | ProductManagementService, UserRegistrationService |
خلاصه:
Domain Service ها منطق کسبوکار را اجرا میکنند، در حالی که Application Service ها جریان کار را هماهنگ میکنند. Domain Service ها بخشی از مدل دامنه هستند و قوانین کسبوکار را اعمال میکنند، در حالی که Application Service ها یک لایه نازک بین رابط کاربری و دامنه هستند که مسئولیتهای فنی و هماهنگی را بر عهده دارند.
پاسخ: Factory (کارخانه) در Domain-Driven Design (DDD) یک الگوی طراحی است که مسئولیت ایجاد اشیاء پیچیده دامنه، به ویژه Aggregate Root ها و Entity ها را بر عهده دارد. هدف اصلی Factory کپسولهسازی منطق پیچیده ساخت اشیاء و اطمینان از ایجاد اشیاء در یک حالت معتبر است.
چرا از Factory استفاده میکنیم؟
انواع Factory:
چه زمانی از Factory استفاده نمیکنیم؟
اگر ساخت یک شیء دامنه ساده است و نیازی به منطق پیچیده یا اعتبارسنجی خاصی ندارد، استفاده از سازنده (constructor) مستقیم کافی است و نیازی به Factory نیست.
مثال (متد استاتیک در Aggregate Root):
public class Order : AggregateRoot
{
// ... (سایر ویژگیها و متدها)
public static Order Create(Guid id, int customerId, IEnumerable<ProductItem> products)
{
if (id == Guid.Empty) throw new ArgumentException("Order ID cannot be empty.");
if (customerId <= 0) throw new ArgumentException("Customer ID must be positive.");
if (products == null || !products.Any()) throw new ArgumentException("Order must have at least one product.");
var order = new Order
{
Id = id,
CustomerId = customerId,
OrderDate = DateTime.UtcNow,
Status = OrderStatus.Pending
};
foreach (var productItem in products)
{
order.AddLineItem(productItem.ProductId, productItem.Quantity, productItem.UnitPrice);
}
order.AddDomainEvent(new OrderCreatedEvent(order.Id, order.CustomerId, order.OrderDate));
return order;
}
}
در این مثال، متد استاتیک Create در کلاس Order به عنوان یک Factory عمل میکند. این متد مسئولیت اعتبارسنجی ورودیها و ایجاد یک شیء Order در یک حالت معتبر را بر عهده دارد.
پاسخ: Bounded Context و Subdomain مفاهیم مرتبط اما متفاوتی در Domain-Driven Design (DDD) هستند. درک تفاوت آنها برای طراحی معماریهای مقیاسپذیر و قابل نگهداری ضروری است.
ویژگی | Subdomain (زیردامنه) | Bounded Context (محدوده بافت) |
---|---|---|
ماهیت | یک بخش منطقی از دامنه کسبوکار. | یک مرز صریح در نرمافزار که در آن یک مدل دامنه خاص و Ubiquitous Language آن معنای دقیق و بدون ابهام خود را حفظ میکند. |
مکان | در فضای مسئله (Problem Space)؛ مربوط به کسبوکار. | در فضای راهحل (Solution Space)؛ مربوط به طراحی نرمافزار. |
هدف | تقسیم دامنه بزرگ به بخشهای قابل مدیریت بر اساس منطق کسبوکار. | مدیریت پیچیدگی مدلسازی و توسعه نرمافزار با ایجاد مرزهای واضح برای مدلهای دامنه. |
زبان | هر Subdomain میتواند یک Ubiquitous Language خاص خود را داشته باشد. | هر Bounded Context یک Ubiquitous Language خاص خود را دارد که در داخل آن مرزها معتبر است. |
ارتباط | یک Subdomain میتواند به یک یا چند Bounded Context نگاشت شود. | یک Bounded Context معمولاً به یک Subdomain خاص نگاشت میشود، اما میتواند چندین Subdomain را نیز پوشش دهد یا بخشی از یک Subdomain باشد. |
مثال | Order Management, Customer Support, Billing | OrderContext, CustomerContext, BillingContext |
خلاصه:
Subdomain ها بخشهای مفهومی از دامنه کسبوکار هستند، در حالی که Bounded Context ها مرزهای پیادهسازی نرمافزاری هستند که مدلهای دامنه را در داخل خود کپسوله میکنند. یک Bounded Context یک راهحل نرمافزاری برای یک Subdomain خاص است. این تفکیک به ما کمک میکند تا پیچیدگیهای دامنه را به صورت مؤثرتری مدیریت کنیم و از تداخل مفاهیم در بخشهای مختلف سیستم جلوگیری کنیم.
پاسخ: CQRS (Command Query Responsibility Segregation) یک الگوی معماری است که پیشنهاد میکند عملیات خواندن (Queries) و نوشتن (Commands) در یک سیستم را از یکدیگر جدا کنیم. این جداسازی به ما اجازه میدهد تا مدلهای داده و فناوریهای بهینهسازی شده متفاوتی را برای هر یک از این عملیاتها استفاده کنیم، که منجر به سیستمهای مقیاسپذیرتر، انعطافپذیرتر و با عملکرد بهتر میشود.
اجزای اصلی CQRS:
مزایای CQRS:
معایب CQRS:
چه زمانی از CQRS استفاده میکنیم؟
CQRS برای سیستمهایی مناسب است که:
پاسخ: Event Sourcing (منبع رویداد) یک الگوی معماری است که در آن به جای ذخیره کردن تنها حالت فعلی یک Aggregate، تمام تغییرات حالت آن Aggregate به صورت دنبالهای از Domain Event ها ذخیره میشوند. این رویدادها به ترتیب زمانی ذخیره میشوند و حالت فعلی Aggregate با بازپخش این رویدادها از ابتدا تا انتها بازسازی میشود.
اجزای اصلی Event Sourcing:
مزایای Event Sourcing:
معایب Event Sourcing:
چه زمانی از Event Sourcing استفاده میکنیم؟
Event Sourcing برای سیستمهایی مناسب است که:
پاسخ: Unit of Work (واحد کار) یک الگوی طراحی است که تمام عملیاتهای دیتابیس (افزودن، بهروزرسانی، حذف) را که در یک تراکنش واحد انجام میشوند، ردیابی میکند. هدف اصلی Unit of Work اطمینان از اتمی بودن (Atomicity) و سازگاری (Consistency) تغییرات در دیتابیس است.
نقش Unit of Work:
چرا از Unit of Work استفاده میکنیم؟
رابطه با Repository:
Repository ها مسئولیت بازیابی و ذخیرهسازی Aggregate Root ها را بر عهده دارند، اما خودشان مسئولیت مدیریت تراکنشها را ندارند. اینجاست که Unit of Work وارد میشود. Repository ها معمولاً از یک Unit of Work برای انجام عملیات ذخیرهسازی استفاده میکنند.
مثال:
public interface IUnitOfWork
{
void Commit();
void Rollback();
}
// پیادهسازی در Entity Framework Core
public class EfCoreUnitOfWork : IUnitOfWork
{
private readonly ApplicationDbContext _dbContext;
public EfCoreUnitOfWork(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
public void Commit()
{
_dbContext.SaveChanges();
}
public void Rollback()
{
// در EF Core، تغییرات ردیابی شده تا زمانی که SaveChanges فراخوانی نشود، به دیتابیس اعمال نمیشوند.
// برای Rollback واقعی، باید تغییرات ردیابی شده را پاک کرد یا DbContext را دوباره ایجاد کرد.
// این مثال ساده است و ممکن است نیاز به پیادهسازی پیچیدهتری داشته باشد.
}
}
// استفاده در Application Service
public class OrderApplicationService
{
private readonly IOrderRepository _orderRepository;
private readonly IUnitOfWork _unitOfWork;
public OrderApplicationService(IOrderRepository orderRepository, IUnitOfWork unitOfWork)
{
_orderRepository = orderRepository;
_unitOfWork = unitOfWork;
}
public void CreateOrder(CreateOrderCommand command)
{
var order = Order.Create(Guid.NewGuid(), command.CustomerId);
_orderRepository.Add(order);
_unitOfWork.Commit(); // ذخیره تمام تغییرات ردیابی شده در این تراکنش
}
}
در این مثال، Application Service از Unit of Work برای تضمین اینکه عملیات Add بر روی OrderRepository به صورت اتمی ذخیره میشود، استفاده میکند.
پاسخ: Specification (مشخصه) یک الگوی طراحی در Domain-Driven Design (DDD) است که به ما اجازه میدهد تا منطق انتخاب یا اعتبارسنجی را به صورت کپسولهشده و قابل ترکیب تعریف کنیم. Specification ها به عنوان یک شیء مستقل، یک قانون کسبوکار را بیان میکنند و میتوانند برای فیلتر کردن، اعتبارسنجی یا انتخاب اشیاء دامنه استفاده شوند.
کاربردهای اصلی Specification:
ویژگیهای Specification:
مثال:
public interface ISpecification<T>
{
bool IsSatisfiedBy(T entity);
// برای استفاده با ORM ها مانند EF Core
Expression<Func<T, bool>> ToExpression();
}
public class ActiveCustomersSpecification : ISpecification<Customer>
{
public bool IsSatisfiedBy(Customer customer)
{
return customer.IsActive && customer.LastActivityDate > DateTime.UtcNow.AddMonths(-6);
}
public Expression<Func<Customer, bool>> ToExpression()
{
return customer => customer.IsActive && customer.LastActivityDate > DateTime.UtcNow.AddMonths(-6);
}
}
public class GoldCustomersSpecification : ISpecification<Customer>
{
public bool IsSatisfiedBy(Customer customer)
{
return customer.TotalPurchases > 5000;
}
public Expression<Func<Customer, bool>> ToExpression()
{
return customer => customer.TotalPurchases > 5000;
}
}
// ترکیب Specification ها
public class GoldAndActiveCustomersSpecification : ISpecification<Customer>
{
private readonly ActiveCustomersSpecification _activeSpec = new ActiveCustomersSpecification();
private readonly GoldCustomersSpecification _goldSpec = new GoldCustomersSpecification();
public bool IsSatisfiedBy(Customer customer)
{
return _activeSpec.IsSatisfiedBy(customer) && _goldSpec.IsSatisfiedBy(customer);
}
public Expression<Func<Customer, bool>> ToExpression()
{
return customer => _activeSpec.ToExpression().Compile()(customer) && _goldSpec.ToExpression().Compile()(customer);
}
}
// استفاده در Repository
public class CustomerRepository : ICustomerRepository
{
private readonly ApplicationDbContext _dbContext;
public CustomerRepository(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
public IEnumerable<Customer> Find(ISpecification<Customer> specification)
{
return _dbContext.Customers.Where(specification.ToExpression()).ToList();
}
}
در این مثال، Specification ها برای فیلتر کردن مشتریان فعال و مشتریان طلایی استفاده میشوند و میتوانند با هم ترکیب شوند. این باعث میشود منطق فیلتر کردن از Repository جدا شود و قابل استفاده مجدد باشد.
پاسخ: Anti-Corruption Layer (ACL) یک الگوی طراحی در Domain-Driven Design (DDD) است که به عنوان یک لایه ترجمه بین یک Bounded Context و یک سیستم خارجی (مانند یک سیستم قدیمی، یک سرویس شخص ثالث، یا یک Bounded Context دیگر با مدل دامنه متفاوت) عمل میکند. هدف اصلی ACL محافظت از مدل دامنه تمیز و غنی Bounded Context شما در برابر نفوذ مدلهای خارجی و ناسازگار است.
چرا به ACL نیاز داریم؟
پیادهسازی ACL:
ACL معمولاً به صورت یک لایه از کلاسها و واسطها پیادهسازی میشود که شامل موارد زیر است:
مثال:
فرض کنید یک سیستم جدید مدیریت سفارشات (Order Management Bounded Context) در حال توسعه است و نیاز به تعامل با یک سیستم قدیمی مدیریت مشتریان (Legacy Customer System) دارد. مدل مشتری در سیستم قدیمی ممکن است با مدل مشتری در سیستم جدید متفاوت باشد.
// مدل مشتری در Bounded Context جدید
public class Customer
{
public Guid Id { get; private set; }
public string FullName { get; private set; }
public string EmailAddress { get; private set; }
}
// مدل مشتری در سیستم قدیمی (Legacy System)
public class LegacyCustomerDto
{
public string CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
// Anti-Corruption Layer
public class LegacyCustomerAdapter
{
private readonly ILegacyCustomerService _legacyCustomerService;
public LegacyCustomerAdapter(ILegacyCustomerService legacyCustomerService)
{
_legacyCustomerService = legacyCustomerService;
}
public Customer GetCustomerById(string legacyCustomerId)
{
var legacyCustomerDto = _legacyCustomerService.GetCustomerDetails(legacyCustomerId);
if (legacyCustomerDto == null) return null;
// ترجمه از مدل قدیمی به مدل جدید
return new Customer(
Guid.Parse(legacyCustomerDto.CustomerId), // فرض کنید CustomerId در Legacy System یک GUID است
$"{legacyCustomerDto.FirstName} {legacyCustomerDto.LastName}",
legacyCustomerDto.Email
);
}
// متدهای دیگر برای تبدیل و تعامل
}
در این مثال، LegacyCustomerAdapter به عنوان ACL عمل میکند. این کلاس مسئولیت تبدیل LegacyCustomerDto (مدل سیستم قدیمی) به Customer (مدل دامنه جدید) را بر عهده دارد و از نفوذ مدل قدیمی به مدل دامنه جدید جلوگیری میکند.