در این بخش، Domain Event که یکی از اجزای کلیدی DDD می باشد را بررسی می کنیم.
به طور کلی، domain event یک رویداد است که در یک domain اتفاق می افتد و این اتفاق از نگاه business مهم و تاثیر گذار می باشد. بنابراین از domain eventها، به عنوان یک ابزار کارآمد برای modeling استفاده می کنیم.
در مفهوم Event Storming، از eventها به عنوان نقطه شروع اتفاقاتی که در یک domain می افتد، استفاده می شود. بنابراین domain eventها می توانند یکی از راه های شناخت سیستم باشند.
یک event از نتیجه اتفاق افتادن یک command بوجود می آید. برای مثال از نتیجه PlaceOrder، یک event به نام OrderPlaced بوجود می آید. این به معنای رابطه یک به یک بین commandها و eventها نیست و ممکن است یک command باعث بوجود آمدن چند event شود.
یک event دارای دو بخش raising و handling می باشد و در DDD برای آن ها class می سازیم و آن ها را model می کنیم.
public class DeliveryGuaranteeFailed { public DeliveryGuaranteeFailed(OrderForDelivery order) { Order = order; } public OrderForDelivery Order { get; private set; } }
موضوع دیگری که در مورد eventها وجود دارد، مفهوم event sourcing است. ایده آن این است که بجای نگهداری aggregateها به شکل last state در دیتابیس، یک سری از eventها را ذخیره می کنیم. می توانید در اینجا بیشتر در مورد آن مطالعه فرمایید.
در مورد eventها می تونیم دو رویکرد Sync و Async داشته باشیم.
در eventهای sync، در نظر بگیرید که یک command وارد سیستم می شود، یک event اتفاق می افتد و در همان درخواست، event دریافت می شود و یک reaction در مورد آن شکل می گیرد.
در eventهای async نیز در نظر بگیرید که از طریق یک command، یک event اتفاق می افتد و پاسخ بر می گردد. در نهایت از طریق مکانیزم های دیگر، یک reaction نسبت به آن event شکل می گیرد.
در پیاده سازی رویکرد Sync، از روش هایی مثل استفاده از eventهای موجود در dotnet یا الگویی که به اسم Event Aggregator معروف است می توانیم استفاده کنیم. در الگوی Event Aggregator دو واحد تشکیل شده از publisherها و listenerها موجود است که از طریق event aggregator به هم مرتبط هستند. این کار در memory و در یک transaction انجام می شود.
public class OrderForDelivery { ... private IBus Bus { get; set; } ... public void ConfirmReceipt(DateTime timeThatPizzaWasDelivered) { if (Status != FoodDeliveryOrderSteps.Delivered) { TimeThatPizzaWasDelivered = timeThatPizzaWasDelivered; Status = FoodDeliveryOrderSteps.Delivered; if (DeliveryGuaranteeOffer.IsNotSatisfiedBy(TimeOfOrderBeingPlaced, TimeThatPizzaWasDelivered)) { Bus.InMemory.Raise(new DeliveryGuaranteeFailed(this)); } } } ... }
در این مثال، در کلاسی تحت عنوان OrderForDelivery و در متد Confirm، یک raise ،event شده است.
در بخشی دیگری، یک نفر به این event گوش می دهد و زمانی که event اتفاق می افتد، یک reaction به آن نشان می دهد.
public void Confirm(DateTime timeThatPizzaWasDelivered, Guid orderId) { using(DomainEvents.Register<DeliveryGuaranteeFailed>(onDeliveryFailure)) { var order = orderRepository.FindBy(orderId); order.ConfirmReceipt(timeThatPizzaWasDelivered); } } private void onDeliveryFailure(DeliveryGuaranteeFailed evnt) { // handle internal event and publish external event bus.Send(new RefundDueToLateDelivery() { OrderId = evnt.Order.Id }); }
در این مثال یک event داخلی دریافت شده و تبدیل به یک event خارجی شده است.
دو موضوع کلی در مورد domain eventها وجود دارد. یکی Technical Detailهایی که بر روی domain eventها ممکن است قرار بگیرد. برای مثال، یک aggregate یا value object بر روی domain event قرار بگیرد. و موضوع بعدی domain eventهایی هستند که نمی خواهیم به همان شکل در اختیار دیگران قرار دهیم. برای مثال ممکن است نخواهیم به کاربر اطلاع دهیم که پسوردش عوض شده است و یا سه domain event را در قالب یک مجموعه اطلاع دهیم.
اگر objectهای domain را روی domain eventها قرار می دهیم، محدوده این domain eventها نباید از این BC خارج شود. در مقابل external eventها وجود دارند که کاربری آن ها برای BCهای دیگر می باشد.