شرایطی را در نظر بگیرید که نیاز است از تغییرات یک Entity در سیستم آگاه شویم. برای مثلا در زمان ثبت سفارش جدید در فروشگاه، ایمیلی به مدیر فروشگاه ارسال شود، یک Business Rule نیز چک شود و همچنین بنابر نیاز مشتری، تعداد آنها روز به روز ممکن است افزایش یابد و چه بسا در اعمال این Ruleها، موجودیتهای مختلفی درگیر باشند. در این صورت است که خواسته یا ناخواسته اتصال بین کلاسها خیلی افزایش خواهد یافت. یکی از راه حلهای رهایی از این پیچیدگی و اتصال بالا، استفاده از Event میباشد.
هدف طراحی و پیاده سازی زیرساختی برای استفاده از DomainEventها میباشد. کدهای کامل این مطلب را میتوانید از اینجا دریافت کنید.
Domain Event چیست؟
چیزی که در یک Domain خاصی رخ داده است و هدف از آن آگاه کردن سایر بخشهای آن Domain میباشد تا بتوانند واکنش مناسبی را نشان دهند. با بهره گیری از این نوع رویدادها، میتوان Separation Of Concerns خوبی را بین کلاسهای موجود در آن Domain اعمال کرد و به طراحی ای با Coupling پایین رسید. این رویدادها عموما داخل پروسه Raise میشوند.
namespace DomainEventsSample.Framework.Eventing.DomainEvents { public interface IDomainEvent : ITransientDependency { } }
namespace DomainEventsSample.Framework.Eventing.DomainEvents { public interface IDomainEventHandler<in T> : ITransientDependency where T : IDomainEvent { bool IsAdvisable { get; } void Handle(T domainEvent); } }
- متد Raise مربوط به Engine برای رویداد خاصی فراخوانی میشود.
- با استفاده از یک IOC Container، تمام هندلرهای مربوط به رویداد جمع آوری میشود.
- متد Handle مربوط به تک تک هندلرها، فراخوانی خواهد شد.
namespace DomainEventsSample.Framework.Eventing.DomainEvents { public interface IDomainEventEngine : ISingletonDependency { void Raise<T>(T domainEvent) where T : IDomainEvent; } } namespace DomainEventsSample.Framework.Eventing.DomainEvents { public class DomainEventEngine : IDomainEventEngine { private readonly IContainer _container; public DomainEventEngine(IContainer container) { _container = container; } public void Raise<T>(T domainEvent) where T : IDomainEvent { foreach (var handler in _container.GetAllInstances<IDomainEventHandler<T>>()) try { handler.Handle(domainEvent); } catch (Exception) { if (domainEvent.IsAdvisable && handler.IsAdvisable) throw; } } } }
namespace DomainEventsSample.Framework.Domain.Events { public abstract class EntityDomainEvent<TEntity> : IDomainEvent where TEntity : Entity { protected EntityDomainEvent(TEntity entity) { Entity = entity; } public TEntity Entity { get; } } }
کلاس بالا به عنوان کلاس پایه یکسری رویداد مشترک مابین Entityهای سیستم در نظر گرفته شده است.
namespace DomainEventsSample.Framework.Domain.Events { public class EntityCreatingEvent<TEntity> : EntityDomainEvent<TEntity> where TEntity : Entity { public EntityCreatingEvent(TEntity entity) : base(entity) { } } } namespace DomainEventsSample.Framework.Domain.Events { public class EntityCreatedEvent<TEntity> : EntityDomainEvent<TEntity> where TEntity : Entity { public EntityCreatedEvent(TEntity entity) : base(entity) { } } }
این رویدادها مربوط به زمان قبل و بعد از ایجاد یک Entity میباشند.
namespace DomainEventsSample.Framework.Domain.Events { public class EntityEditingEvent<TEntity> : EntityDomainEvent<TEntity> where TEntity : Entity { public EntityEditingEvent(TEntity entity) : base(entity) { } } } namespace DomainEventsSample.Framework.Domain.Events { public class EntityEditedEvent<TEntity> : EntityDomainEvent<TEntity> where TEntity : Entity { public EntityEditedEvent(TEntity entity) : base(entity) { } } }
این رویدادها مربوط به زمان قبل و بعد از ویرایش یک Entity میباشند.
namespace DomainEventsSample.Framework.Domain.Events { public class EntityDeletingEvent<TEntity> : EntityDomainEvent<TEntity> where TEntity : Entity { public EntityDeletingEvent(TEntity entity) : base(entity) { } } } namespace DomainEventsSample.Framework.Domain.Events { public class EntityDeletedEvent<TEntity> : EntityDomainEvent<TEntity> where TEntity : Entity { public EntityDeletedEvent(TEntity entity) : base(entity) { } } }
این رویدادها مربوط به زمان قبل و بعد از حذف یک Entity میباشند.
namespace DomainEventsSample.Framework.Domain.Events { public class EntitySavingEvent<TEntity> : EntityDomainEvent<TEntity> where TEntity : Entity { public EntitySavingEvent(TEntity entity) : base(entity) { } } } namespace DomainEventsSample.Framework.Domain.Events { public class EntitySavedEvent<TEntity> : EntityDomainEvent<TEntity> where TEntity : Entity { public EntitySavedEvent(TEntity entity) : base(entity) { } } }
این رویدادها مربوط به زمان قبل و بعد از ذخیره (ایجاد و ویرایش) یک Entity میباشند.
نکته: برای اسکن کردن تمام هندلرها لازم است کد زیر را به تنظیمات StructureMap اضافه کنید:
Scan(scan => { scan.ConnectImplementationsToTypesClosing(typeof(IDomainEventHandler<>)); });
public class ProductCreatedEventHandler : IDomainEventHandler<EntityCreatedEvent<Product>> { public bool IsAdvisable => false; public void Handle(EntityCreatedEvent<Product> domainEvent) { //todo: notify users } }
در متد Create مربوط به ProductApplicationService و بعد از عملیات ذخیره سازی به شکل زیر میبایست عمل کرد:
public class ProductApplicationService : IProductApplicationService { private readonly IDomainEventEngine _eventEngine; private readonly IUnitOfWork _unitOfWork; private readonly IMapper _mapper; public ProductApplicationService(IDomainEventEngine eventEngine,IMapper mapper,IUnitOfWork unitOfWork) { _eventEngine = eventEngine; _mapper=mapper; _unitOfWork=unitOfWork; } [Transactional] public void Create(ProductCreateViewModel model) { var entity=_mapper.Map<Product>(model); _unitOfWork.Set<Product>().Add(entity); _unitOfWork.SaveChanges(); _eventEngine.Raise(new EntityCreatedEvent<Product>(entity)); } }
البته بهتر است برای Raise کردن این نوع رویدادها از مکانیزم Hook استفاده کرد و در زمان ذخیره سازی و فراخوانی متد SaveChange، این عملیات به صورت خودکار صورت گیرند.
در مقاله بعدی با استفاده از Hookها این عملیات را انجام خواهیم داد.
کدهای این قسمت را میتوانید از اینجا دریافت کنید.