فرشید عزیزی
فرشید عزیزی
خواندن ۲۵ دقیقه·۳ سال پیش

طراحی و پیاده سازی Domain events

پیشنهاد : مطالعه مطلب مرتبط با این موضوع : پياده سازي Aggregate در Domin Layer - DDD

رویداد دامنه/Domain events چیست؟

رویداد چیزی است که در گذشته اتفاق افتاده است. رویداد دامنه، چیزی است که در دامنه اتفاق افتاده است که می‌خواهید سایر بخش‌های همان دامنه (در حال پردازش) از آن آگاه شوند. بخش‌های اطلاع‌رسانی شده معمولاً به نحوی به رویدادها واکنش نشان می‌دهند.

این رویدادها از Domain Model منشأ می گیرند و در یک Bounded Context پخش می شوند.

یکی از مزایای مهم رویدادهای دامنه این است که می توان اثرات جانبی آن(عملکردهایی که پس از آن آغاز می شوند) را به صراحت بیان کرد.

استفاده از رویدادهای دامنه یک مفهوم صریح است، زیرا همیشه یک DomainEvent و حداقل یک DomainEventHandler وجود خواهد داشت. یکی تعریف خود رویداد را بیان می کند (یعنی اتفاقی که اخیراً افتاده است) و دیگری وظیفه ای را طبق قوانین کسب و کار انجام می دهد.
زمانی که کتاب اصلی DDD اریک ایوان منتشر شد به عنوان یک الگوی طراحی دامنه محور (DDD) تعریف نشد، اما اکنون یک عنصر تاکتیکی در DDD و عضو کامل مدل‌های دامنه هستند.
هر زمان که دامنه ما هنگام به روز رسانی وضعیت خود اثرات جانبی(Side Effects) ایجاد کند، از رویدادهای دامنه استفاده خواهیم کرد.
اما گاهی اوقات ما تصمیم می‌گیریم که آنها را به روش‌های «خطی‌تر» پیاده‌سازی کنیم.
منظور من از پیاده سازی خطی مثال زیر است:

فرض کنید دامنه ما یک Product است که دارای n ویژگی یا property است، اما business rule این است که اگر محصول جدیدی در سیستم ایجاد شود، به شخص خاصی باید اطلاع داده شود.

برنامه‌ریزی ایجاد محصول و برنامه‌ریزی یک اعلان فوری بسیار آسان است، اما اگر همیشه به این روش برنامه‌ریزی شود، چندین اشکال دارد.
مشکلات زمانی ایجاد می‌شوند که می‌خواهید آن قانون را تغییر دهید و شاید اعلان دیگری را بر اساس پارامترهای دیگر اضافه کنید، کاری که باید انجام دهید این است که همان روالی که یک محصول را ایجاد می‌کند تغییر دهید و درخواست جدید را اضافه کنید. باعث می شود عملکردهای مختلف به شدت با هم درگیر و در هم تنیده شوند.
این اصل مسئولیت واحد/SRP را نقض می کند. من فکر می‌کنم کار درست این است که یک تابع/کلاس داشته باشیم که یک محصول را ایجاد کند، اما فقط همین. هر گونه اثرات جانبی یا business rule ناشی از به روز رسانی دامنه باید در جای دیگری کپسوله شود.

این مزایای خود را دارد، مانند تعریف صریح business rule و تفویض مسئولیت ها. از آنجایی که به‌روزرسانی یک محصول یک کار است، ارسال اعلان‌ها یا سایر اقدامات پس از آن، وظایف متفاوتی هستند.
داشتن مسئولیت‌های تفویض‌شده باعث می‌شود کد برای تغییر ایمن‌تر شود، زیرا شما همیشه کد را در قطعات کوچک تغییر می‌دهید، که در تئوری، به طور مستقل تست می‌شوند.

هنگامی که از رویکرد Domain Driven Design در برنامه خود استفاده می‌کنیم، گاهی اوقات مجبور می‌شویم متدی را در چندین نمونه از Aggregate از یک نوع فراخوانی کنیم.2 راه برای اجرای این وجود دارد:

  • استفاده از transactional consistency
  • استفاده از eventual consistency

اما قبل از ادامه بحث مهم است که تفاوت آن دو را بخوبی درک کنید. در این لینک مفصل به تفاوت میان transactional consistency و eventual consistency پرداخته شده است.

به صورت اختیاری، برای مقیاس پذیری بهتر و تأثیر کمتر در قفل های پایگاه داده، از eventual consistency بین Aggregate ها در همان دامنه استفاده کنید.


سناریوهایی وجود دارد که اهمیت transactional consistency را نشان می دهد.

مجموعه‌ای از Aggregateها به‌خوبی طراحی‌شده در شکل زير نشان داده شده‌اند. این Aggregateها بر اساس قوانین business واقعی هستند که نیازمند داده‌های خاصی هستند تا در پایان یک تراکنش موفق پایگاه داده به‌روز شوند. اینها از قوانین Aggregate پیروی می کنند، از جمله طراحی Aggregateهاي کوچک.

اگر فرض BacklogItem و Product وابستگی‌های داده‌ای داشته باشند، این سوال پیش می‌آید که چگونه هر دوی آنها را به‌روزرسانی کنیم. این به قانون دیگر طراحی Aggregate اشاره می کند، استفاده از سازگاری نهایی(eventual consistency) همانطور که در شکل زير نشان داده شده است.

When two or more Aggregates have at least some dependencies on updates, use eventual consistency
When two or more Aggregates have at least some dependencies on updates, use eventual consistency


فرض کنید دو عملیات به نام های A و B داریم و بیایید دو سناریو زیر را در نظر بگیرید:

  • عملیات B بر روی یک aggregate متفاوت از عملیات A اما در یک bounded context انجام می شود.
  • عملیات B در یک bounded context متفاوت از عملیات A انجام می شود.

این سناریوها به طراحی دامنه محور (DDD) است و برای این دو، انتخاب transactional consistency منجر به طراحی بهتر در مقایسه با کار با تراکنش‌های سنتی می‌شود. اینجاست که رویدادهای دامنه می توانند مفید باشند. آنها نمایشی از اتفاقی هستند که در دامنه ای رخ داده است و با عمل به عنوان محرک هایی که مرحله بعدی را در work flow شروع می کنند و اطلاعات مورد نیاز دامنه را حمل می کنند، transactional consistency را تسهیل می کنند.


به عنوان مثال در سیستم فروشگاه اینترنتی، هنگامی که یک سفارش ایجاد می‌شود، کاربر به خریدار تبدیل می‌شود، بنابراین یک OrderStartedDomainEvent مطرح می‌شود و در ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler مدیریت می‌شود، بنابراین مفهوم اساسی آشکار است.

به طور خلاصه، رویدادهای دامنه به شما کمک می کند تا قوانین دامنه را به طور واضح بیان کنید، بر اساس زبانی که کارشناسان دامنه ارائه می دهند. رویدادهای دامنه همچنین تفکیک بهتر نگرانی ها(SoC) را در بین طبقات در همان دامنه امکان پذیر می کند.

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

به عنوان مثال، اگر ما یک محصول داریم و قوانین business طبق نظر کارشناس دامنه این است که هر بار که یک محصول ثبت می شود، باید به انبار اطلاع داده شود (مثلا) و همچنین می تواند به بخش خرید اطلاع داده شود یا ثبت شود. سفارش با یک تامین کننده، ثبت رکورد در گزارش و غیره.

رویدادهای دامنه و اثرات جانبی از آنها (عملکردهایی که پس از آن آغاز می شوند و توسط مدیریت کننده رویداد/event handlers مدیریت می شوند) باید تقریباً بلافاصله، معمولاً در حین فرآیند و در همان دامنه رخ دهند. بنابراین، رویدادهای دامنه می توانند همزمان یا ناهمزمان باشند.

رویدادهای دامنه فقط پیام هایی هستند که به یک توزیع کننده رویداد دامنه(domain event dispatcher) ارسال(pushed) می شوند، که می توانند به عنوان یک واسطه در حافظه(in-memory mediator) بر اساس یک IoC container یا هر روش دیگری پیاده سازی شوند.

رویدادهای دامنه به عنوان یک روش برای ایجاد اثرات جانبی تغییرات در چندین aggregate در یک دامنه

اگر اجرای دستور مربوط به یک نمونه aggregate نیازمند پیاده سازی اضافی قوانین دامنه بر روی یک یا چند aggregate دیگر است، باید آن اثرات جانبی از آن را طراحی و پیاده سازی کنید تا توسط رویدادهای دامنه اجرا شوند. همانطور که در شکل زیر نشان داده شده است، یک رویداد دامنه باید برای انتشار تغییرات حالت در چندین aggregate در یک مدل دامنه استفاده شود.

Domain events to enforce consistency between multiple aggregates within the same domain
Domain events to enforce consistency between multiple aggregates within the same domain


شکل بالا نشان می دهد که چگونه consistency بین aggregateها توسط رویدادهای دامنه به دست می آید. هنگامی که کاربر سفارشی را آغاز می کند، Order Aggregate یک رویداد دامنه OrderStarted را ارسال می کند. رویداد دامنه OrderStarted توسط Buyer Aggregate برای ایجاد یک شی خریدار، بر اساس اطلاعات کاربر اصلی مدیریت می‌شود.

متناوباً، می‌توانید aggregate root را برای رویدادهایی که توسط اعضای aggregate آن (موجودات فرزند) ایجاد می‌شوند، استفاده کنید. به عنوان مثال، هر موجودیت فرزند OrderItem می تواند زمانی که قیمت itemی بالاتر از یک مقدار خاص است یا زمانی که مقدار محصول مورد نظر بیش از حد مجاز است، رویدادی را مطرح کند. سپس aggregate root می تواند آن رویدادها را دریافت کند و یک محاسبه یا اقدامی انجام دهد.

درک این نکته مهم است که این ارتباط مبتنی بر رویداد مستقیماً در aggregate ها پیاده سازی نمی شود. شما باید domain event handlers را پیاده سازی کنید.

مدیریت رویدادهای دامنه یک نگرانی در لایه application است. لایه domain model فقط باید بر منطق دامنه تمرکز کند. بنابراین، لایه application جایی است که باید domain event handlers ، اقداماتی را در هنگام رخداد یک رویداد دامنه انجام دهند.

رویدادهای دامنه همچنین می توانند برای راه اندازی هر تعداد از اقدامات برنامه مورد استفاده قرار گیرند، و آنچه مهمتر است، باید باز باشد تا این تعداد در آینده به صورت جدا شده افزایش یابد. به عنوان مثال، هنگامی که سفارش شروع می شود، ممکن است بخواهید یک رویداد دامنه منتشر کنید تا آن اطلاعات را در aggregate های دیگر منتشر کنید یا حتی اقدامات برنامه مانند اعلان ها را افزایش دهید.(اصول Open/Closed principle و Single Responsibility Principle را بخاطر داشته باشید)

شما می‌توانید با استفاده از این رویکرد، یک پیاده‌سازی دقیق و جداشده با تفکیک مسئولیت‌ها
(segregating responsibilities ) ایجاد کنید:

  • یک دستور/command (به عنوان مثال، CreateOrder) ارسال کنید.
  • دستور را در یک command handler دریافت کنید

1.یک تراکنش aggregate را اجرا کنید.

2.(اختیاری) رویدادهای دامنه را برای اثرات جانبی از تغییرات ادامه دهید (به عنوان مثال،
OrderStartedDomainEvent).

  • رویدادهای دامنه را مدیریت کنید/Handle domain events (در فرآیند فعلی) که تعداد باز، اثرات جانبی از تغییرات را در چندین aggregate یا اقدامات برنامه اجرا می کند. مثلا:

1.خریدار و روش پرداخت را تأیید یا ایجاد کنید.

2.راه اندازی اقدامات خارجی مانند ارسال ایمیل به خریدار.

3. مدیریت سایر اثرات جانبی ناشی از تغییرات

Handling multiple actions
Handling multiple actions

همانطور که در شکل بالا نشان داده شده است، با شروع از یک رویداد دامنه، می توانید چندین عملکرد مربوط به سایر aggregate ها در دامنه یا اقدامات اضافی را انجام دهید.

داشتن تعداد باز کنترل کننده/handlers در هر رویداد دامنه به شما این امکان را می دهد که به تعداد مورد نیاز قوانین دامنه را اضافه کنید، بدون اینکه روی کد فعلی تأثیر بگذارد(Open/Closed principle). به عنوان مثال، اجرای business rule زیر :

هنگامی که کل مبلغ خریداری شده توسط مشتری در فروشگاه، در هر تعداد سفارش، از 6000 دلار فراتر رفت، برای هر سفارش جدید تخفیف 10 درصدی اعمال کنید و از طریق ایمیل به مشتری در مورد این تخفیف برای سفارشات بعدی اطلاع دهید.


پیاده سازی Domain events

چگونه می توانید یک رویداد را از یک Entity منتشر کنید؟ چگونه شنوندگان را بهم متصل می‌کنید، و شنوندگان در کجای این معماری زندگی می‌کنند؟ موجودیت های ما اغلب در یک core assembly هستند که هیچ وابستگی ندارند. هیچ مفهومی از UnitOfWork/Transaction در این سطح وجود ندارد، و مطمئناً آنها به هیچ چیز جالبی مانند پایگاه داده یا یک Application Layer دسترسی ندارند.

موجودیت/Entity باید در مورد چیزی «انتشار/Publish» را فراخوانی کند. یکی از ساده‌ترین پیاده‌سازی‌ها از دیدگاه موجودیت، این است که Entity از یک کلاس پایه که حاوی منطق انتشار است مشتق شود. در این پیاده سازی، Entity در واقع انتشار رویداد را انجام نمی دهد، بلکه فقط مجموعه ای از رویدادها را نگهداری
می کند که یک توزیع کننده/dispatcher بعداً آنها را بررسی و توزیع خواهد کرد.

در سی شارپ، یک رویداد دامنه صرفاً یک ساختار یا کلاس نگهدارنده داده است، مانند یک DTO، با تمام اطلاعات مربوط به اتفاقاتی که اخیراً در دامنه رخ داده است، همانطور که در مثال زیر نشان داده شده است:

public class OrderStartedDomainEvent : INotification { public string UserId { get; } public string UserName { get; } public int CardTypeId { get; } public string CardNumber { get; } public string CardSecurityNumber { get; } public string CardHolderName { get; } public DateTime CardExpiration { get; } public Order Order { get; } public OrderStartedDomainEvent(Order order, string userId, string userName, int cardTypeId, string cardNumber, string cardSecurityNumber, string cardHolderName, DateTime cardExpiration) { Order = order; UserId = userId; UserName = userName; CardTypeId = cardTypeId; CardNumber = cardNumber; CardSecurityNumber = cardSecurityNumber; CardHolderName = cardHolderName; CardExpiration = cardExpiration; } }

این در اصل کلاسی است که تمام داده های مربوط به رویداد OrderStarted را در خود نگه می دارد.

از نظر زبان فراگیر دامنه/ubiquitous language، از آنجایی که یک رویداد چیزی است که در گذشته اتفاق افتاده است، نام کلاس رویداد باید به عنوان یک فعل زمان گذشته نمایش داده شود، مانند OrderStartedDomainEvent یا OrderShippedDomainEvent.

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

در اینجا مهم است که تاکید کنیم که اگر قرار باشد رویدادهای دامنه به صورت ناهمزمان/async مدیریت شوند، با استفاده از صفی که نیاز به serializing و deserializing اشیاء رویداد دارد، ویژگی‌ها باید به جای فقط خواندنی، "private set" باشند.

اما اگر رویدادهای دامنه به صورت همزمان/sync مدیریت شوند این موضوع دیگر وجود نخواهد داشت و
می توان آن را با استفاده از MediatR پیاده سازی نمود.


ارسال/Raise رویدادهای دامنه

سوال بعدی این است که چگونه یک رویداد دامنه را Raise کنیم تا به event handlers مرتبط با آن برسد.
می توانید از دو روش استفاده کنید.

  • رویکرد ارسال فوری برای Raise کردن و ارسال رویدادها

از یک static class برای مدیریت و Rasie کردن رویدادها استفاده می کند. این ممکن است شامل یک static class به نام DomainEvents باشد که با استفاده از نحوی مانند DomainEvents.Raise (Event myEvent) بلافاصله رویدادهای دامنه را هنگام فراخوانی Rasie می کند.

با این حال، هنگامی که کلاس رویدادهای دامنه static است، بلافاصله به event handlers ها نیز ارسال می شود. این امر آزمایش و اشکال زدایی را دشوارتر می کند، زیرا event handlers با منطق اثرات جانبی بلافاصله پس از مطرح شدن رویداد اجرا می شوند. هنگامی که در حال آزمایش و اشکال زدایی هستید، فقط می خواهید روی آنچه در کلاس های Aggregate فعلی اتفاق می افتد تمرکز کنید. شما نمی خواهید ناگهان به دلیل اثرات جانبی مربوط به سایر Aggregate ها یا منطق برنامه، به مدیریت رویدادهای دیگر هدایت شوید. به همین دلیل است که رویکرد با تاخیر/deferred برای Raise کردن و ارسال رویدادها تکامل یافته تر هستند.

  • رویکرد با تاخیر/deferred برای Raise کردن و ارسال رویدادها

به جای ارسال فوری یک رویداد دامنه به یک event handlers دامنه، یک رویکرد بهتر این است که رویدادهای دامنه را به یک collection اضافه کنید و سپس آن رویدادهای دامنه را درست قبل یا بلافاصله پس از انجام تراکنش ارسال کنید (مانند SaveChanges در EF).

تصمیم گیری در مورد ارسال رویدادهای دامنه درست قبل یا بلافاصله پس از انجام تراکنش مهم است، زیرا تعیین می کند که آیا اثرات جانبی را به عنوان بخشی از همان تراکنش یا در تراکنش های مختلف لحاظ می کنید. در مورد دوم، شما با eventual consistency و مدیریت آن در بین چندین Aggregate روبرو خواهید بود.

خوب در اینجا ما از رویکرد با تاخیر/deferred برای Raise کردن و ارسال رویدادها استفاده خواهیم کرد

بنابراین ابتدا، رویدادهایی را که در موجودیت‌هایتان اتفاق می‌افتند، به مجموعه یا فهرستی از رویدادها در هر موجودیت اضافه می‌کنید. همانطور که در مثال زیر از کلاس پایه Entity نشان داده شده است، آن لیست باید بخشی از شی موجودیت یا حتی بهتر از آن، بخشی از کلاس موجودیت پایه شما باشد:

public abstract class BaseEntity { //... private List<INotification> _domainEvents; public List<INotification> DomainEvents => _domainEvents; public void AddDomainEvent(INotification eventItem) { _domainEvents = _domainEvents ?? new List<INotification>(); _domainEvents.Add(eventItem); } public void RemoveDomainEvent(INotification eventItem) { _domainEvents?.Remove(eventItem); } //... Additional code }

هنگامی که می خواهید رویدادی را Raise کنید، فقط آن را از طریق کد در هر متد aggregate-root به event collection اضافه می کنید.

var orderStartedDomainEvent = new OrderStartedDomainEvent(this, //Order object cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); this.AddDomainEvent(orderStartedDomainEvent);

توجه داشته باشید که تنها کاری که متد AddDomainEvent انجام می دهد اضافه کردن یک رویداد به لیست است. هنوز هیچ رویدادی ارسال نشده است و هیچ کنترل کننده رویدادی/event handler هنوز فراخوانی/invoke نشده است.

شما در واقع می خواهید رویدادها را بعداً، هنگامی که تراکنش را به پایگاه داده انجام می دهید، ارسال کنید. اگر از Entity Framework Core استفاده می کنید، به این معنی است که در متد SaveChanges EF DbContext شما، مانند کد زیر خواهد بود:

// EF Core DbContext public class OrderingContext : DbContext, IUnitOfWork { // ... public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken)) { // Dispatch Domain Events collection. // Choices: // A) Right BEFORE committing data (EF SaveChanges) into the DB. This makes // a single transaction including side effects from the domain event // handlers that are using the same DbContext with Scope lifetime // B) Right AFTER committing data (EF SaveChanges) into the DB. This makes // multiple transactions. You will need to handle eventual consistency and // compensatory actions in case of failures. await _mediator.DispatchDomainEventsAsync(this); // After this line runs, all the changes (from the Command Handler and Domain // event handlers) performed through the DbContext will be committed var result = await base.SaveChangesAsync(); } }

با این کد، رویدادهای موجودیت را به event handler مربوطه ارسال می کنید.

نتیجه کلی این است که شما Rasie کردن یک رویداد دامنه (یک افزودن ساده به یک لیست در حافظه) را از ارسال آن به یک event handler جدا کرده اید. علاوه بر این، بسته به نوع توزیع کننده ای/dispatcherی که استفاده می کنید، می توانید رویدادها را به صورت همزمان یا ناهمزمان ارسال کنید.

توجه داشته باشید اگر صرفا از EF Core و یک پایگاه داده رابطه‌ای استفاده می کنید و رویدادهای دامنه را درست قبل از انجام تراکنش اصلی ارسال کنید، به این دلیل است که می خواهید اثرات جانبی آن رویدادها در همان تراکنش گنجانده شود. به عنوان مثال، اگر متد EF DbContext SaveChanges با شکست مواجه شود، تراکنش تمام تغییرات، از جمله نتیجه هر گونه عملیات جانبی اجرا شده توسط کنترل کننده رویداد دامنه مربوطه را برمی گرداند. این به این دلیل است که دامنه زندگی DbContext به طور پیش فرض به عنوان "scoped" تعریف شده است. در عیر اینصورت شما باید مراحل بیشتری را برای دستیابی به consistency اجرا کنید. این بستگی به سیستم ذخیره سازی مورد استفاده شما دارد.


انتخاب بین transactional consistency در برابر eventual consistency

این سؤال که انتخاب single transaction در بین Aggregate ها در مقابل تکیه بر eventual consistency در بین آن Aggregate ها، بحث برانگیز است.

اگر اجرای یک فرمان روی یک نمونه Aggregate مستلزم اجرای قوانین اضافی بر روی یک یا چند Aggregate دیگر باشد، از eventual consistency استفاده کنید.

پذیرفتن این واقعیت که برنامه های کاربردی بسیار مقیاس پذیر نیازی به transactional consistency فوری بین چندین Aggregate ندارند، به پذیرش مفهوم eventual consistency کمک می کند. در هر صورت این مسئولیت بر عهده کارشناسان دامنه است که بگویند آیا عملیات خاصی نیاز به تراکنش های Atomic دارد یا خیر. اگر یک عملیات همیشه به یک تراکنش Atomic بین چندین Aggregate نیاز دارد، ممکن است بپرسید که آیا Aggregate شما باید بزرگتر(اما در واقع نباید خیلی در خوشه های خیلی بزرگ هم باشد این برخلاف اصول طراحی یک Aggregate است) باشد یا به درستی طراحی نشده است.

در واقع، هر دو رویکرد (single atomic transaction و eventual consistency) می توانند درست باشند. این واقعاً به نیازهای دامنه یا کسب و کار شما و آنچه کارشناسان دامنه به شما می گویند بستگی دارد. همچنین به میزان مقیاس پذیر بودن برنامه شما بستگی دارد. و این بستگی به میزان سرمایه گذاری شما برای انجام کد خود دارد، زیرا eventual consistency به کد پیچیده تری نیاز دارد تا ناهماهنگی های احتمالی در کل Aggregate ها و نیاز به اجرای اقدامات جبرانی را شناسایی کند. در نظر بگیرید که اگر تغییراتی را در Aggregate اصلی انجام دهید و پس از آن، هنگام ارسال رویدادها، اگر مشکلی وجود داشته باشد و event handlerها نتوانند اثرات جانبی خود را مرتکب شوند، ناهماهنگی بین Aggregate ها خواهید داشت.

اقدامات جبرانی بخشی از یک موضوع پیچیده است که نیاز به تجزیه و تحلیل عمیق از طرف شما دارد، که شامل بحث در مورد آن با کارشناسان دامنه است.در هر صورت، شما می توانید رویکرد مورد نیاز خود را انتخاب کنید.

در رویکرد با تاخیر/deferred برای Raise کردن و ارسال رویدادها، شما از single transaction استفاده می کنید این ساده ترین رویکرد هنگام استفاده از EF Core و پایگاه داده رابطه ای است. در بسیاری از business ها قابل اجرا و معتبر است.


اما چگونه می‌توانید آن رویدادها را به event handlerها مربوطه ارسال کنید؟ شی _mediator که در مثال قبلی می بینید چیست؟


توزیع کننده domain event یا mapping from events to event handlers

هنگامی که بتوانید رویدادها را ارسال/dispatch یا منتشر/publish کنید، به نوعی مصنوع نیاز دارید که رویداد را منتشر کند تا هر event handler مرتبط بتواند آن را دریافت کند و اثرات جانبی را بر اساس آن رویداد پردازش کند.

برای نگاشت رویدادها به چندین event handler، استفاده از types registration در یک IoC container است تا بتوانید به صورت پویا استنباط کنید که رویدادها کجا ارسال شوند. به عبارت دیگر، شما باید بدانید که
event handlerها برای دریافت یک رویداد خاص به چه چیزی نیاز دارند. شکل زیر یک رویکرد ساده شده را نشان می دهد.

Domain event dispatcher using IoC
Domain event dispatcher using IoC
شما می‌توانید از کتابخانه‌های موجود مانند MediatR برای ارسال و انتشار رویدادها استفاده کنید.

ابتدا باید انواع event handler را در IoC container خود ثبت کنید، همانطور که در مثال زیر نشان داده شده است.

public class MediatorModule : Autofac.Module { protected override void Load(ContainerBuilder builder) { // Other registrations ... // Register the DomainEventHandler classes (they implement IAsyncNotificationHandler<>) // in assembly holding the Domain Events builder.RegisterAssemblyTypes(typeof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler) .GetTypeInfo().Assembly) .AsClosedTypesOf(typeof(IAsyncNotificationHandler<>)); // Other registrations ... } }


نحوه اشتراک/subscribe در رویدادهای دامنه

هنگامی که از MediatR استفاده می کنید، هر event handler باید از یک نوع رویداد استفاده کند که در پارامتر generic رابط INotificationHandler ارائه شده است، همانطور که در کد زیر مشاهده می کنید:

public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler : IAsyncNotificationHandler<OrderStartedDomainEvent>

بر اساس رابطه بین رویداد و event handler، که می‌توان آن را اشتراک در نظر گرفت، MediatR می‌تواند همه event handlerهای رویداد را برای هر رویداد کشف کند و هر یک از آن event handler را فعال کند.


نحوه مدیریت/handle رویدادهای دامنه

در نهایت، event handler معمولاً کد application layer را پیاده سازی می کند که از
infrastructure repositories برای به دست آوردن aggregate های اضافی مورد نیاز و برای اجرای منطق دامنه اثرات جانبی استفاده می کند. کد زیر یک مثال پیاده سازی را نشان می دهد.

public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler : INotificationHandler<OrderStartedDomainEvent> { private readonly ILoggerFactory _logger; private readonly IBuyerRepository<Buyer> _buyerRepository; private readonly IIdentityService _identityService; public ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler( ILoggerFactory logger, IBuyerRepository<Buyer> buyerRepository IIdentityService identityService) { // ...Parameter validations... } public async Task Handle(OrderStartedDomainEvent orderStartedEvent) { var cardTypeId = (orderStartedEvent.CardTypeId != 0) ? orderStartedEvent.CardTypeId : 1; var userGuid = _identityService.GetUserIdentity(); var buyer = await _buyerRepository.FindAsync(userGuid); bool buyerOriginallyExisted = (buyer == null) ? false : true; if (!buyerOriginallyExisted) { buyer = new Buyer(userGuid); } buyer.VerifyOrAddPaymentMethod(cardTypeId, $&quotPayment Method on {DateTime.UtcNow}&quot, orderStartedEvent.CardNumber, orderStartedEvent.CardSecurityNumber, orderStartedEvent.CardHolderName, orderStartedEvent.CardExpiration, orderStartedEvent.Order.Id); var buyerUpdated = buyerOriginallyExisted ? _buyerRepository.Update(buyer) : _buyerRepository.Add(buyer); await _buyerRepository.UnitOfWork .SaveEntitiesAsync(); // Logging code using buyerUpdated info, etc. } }


نتیجه گیری در مورد رویدادهای دامنه

همانطور که گفته شد، از رویدادهای دامنه برای اجرای صریح اثرات جانبی تغییرات در دامنه خود استفاده کنید. از رویدادهای دامنه استفاده کنید تا به صراحت اثرات جانبی را در یک یا چند aggregate اجرا کنید. علاوه بر این و برای مقیاس‌پذیری بهتر و تأثیر کمتر بر قفل‌های پایگاه داده، از eventual consistency بین aggregate ها در همان دامنه استفاده کنید.

در اینجا از MediatR برای انتشار همزمان رویدادهای دامنه در aggregate ، در یک single transaction استفاده کردیم. با این حال، می‌توانید از برخی پیاده‌سازی‌های AMQP مانند RabbitMQ یا Azure Service Bus برای انتشار رویدادهای دامنه به صورت ناهمزمان، با استفاده از eventual consistency استفاده کنید.


بیشتر بخوانید: Entities, Value Objects, Aggregates and Roots

بیشتر بخوانید : Implementing DDD - Clean Architecture

بیشتر بخوانید : لایه Domain در طراحی دامنه گرا DDD

بیشتر بخوانید : نقشه راه توسعه دهندگان Asp.NET Core


https://zarinp.al/farshidazizi

Domain eventstransactional consistencyeventual consistencyddd
Software Engineer
شاید از این پست‌ها خوشتان بیاید