DDD Доменные события


Сегодня я узнал 06/05/2022

  • Доменные события
  • Когда использовать
  • Компоненты
  • Динамика
  • Реализация

Доменные события

Суть события домена заключается в том, что вы используете его для захвата вещей, которые могут вызвать изменение состояния разрабатываемого вами приложения. Эти объекты событий обрабатываются, чтобы вызвать изменения в системе, и сохраняются для создания журнала аудита (AuditLog).
Фаулер, Мартин

То есть когда в системе что-то происходит, я могу на основе этого события выполнить операцию и сохранить событие для будущего случая аудита.

Каждое событие должно быть представлено в виде действия, представленного в прошлом, пример UserCreated


Когда использовать

Обычно событие домена используется, когда мы хотим уведомить другие ограниченные контексты об изменении состояния.

пример: в системе электронной коммерции при создании заказа мы можем выдать событие OrderPlaced, и тогда выставляющий счет boundedContext будет слушать это событие и выставлять счет для этого заказа


Компоненты

  • СобытиеВыбирается дата и время и содержит информацию о том, что произошло в эту дату и время Например: продукт с названием X изменился на Y
  • ОбработчикВыполняет обработку при вызове события Например: после создания пользователя и отправки письма часть отправки письма является обработчиком
  • Диспетчер событийОтветственность за хранение и выполнение обработчиков события при его возникновении

Динамический

  • Создание диспетчера событий
  • Создайте «Событие
  • Создайте обработчик для события
  • Зарегистрируйте событие вместе с обработчиком в «Диспетчере событий

И когда мы передадим notify диспетчеру Event dispatcher, передающему событие, будут выполнены все handlers.


Реализация

сначала создадим наши интерфейсы, которые будут реализованы диспетчером, событиями и обработчиками

export interface EventInterface<T=any> {
  dateTimeOcurred: Date;
  eventData: T;
}

export interface EventHandlerInterface<T extends EventInterface=EventInterface>{
  handle(event: T): void;
}

export interface EventDispatcherInterface{
  notify(event: EventInterface): void;
  register(eventName: string, eventHandler: EventHandlerInterface): void;
  unregister(eventName: string, eventHandler: EventHandlerInterface): void;
  unregisterAll(): void;
}
Войдите в полноэкранный режим Выход из полноэкранного режима

а затем давайте создадим наш класс диспетчера событий

export class EventDispatcher implements EventDispatcherInterface{
  private eventHandlers: { [eventName: string]: EventHandlerInterface[] } = {};


  get getEventsHandler(): { [eventName: string]: EventHandlerInterface[] }{
    return this.eventHandlers;
  } 

  notify(event: EventInterface): void {
    const eventName = event.constructor.name;
    if(this.eventHandlers[eventName]){
      this.eventHandlers[eventName].forEach(eventHandler => {
        eventHandler.handle(event);
      })
    }
  }

  register(eventName: string, eventHandler: EventHandlerInterface<EventInterface>): void {
        if (!this.eventHandlers[eventName]) {
        this.eventHandlers[eventName] = [];
      }

      this.eventHandlers[eventName].push(eventHandler);
  }

  unregister(eventName: string, eventHandler: EventHandlerInterface<EventInterface>): void {
    if (!this.eventHandlers[eventName]) {
      throw new Error(`${eventName} event does not exist.`);
    }

    const index = this.eventHandlers[eventName].indexOf(eventHandler);
    if (index !== -1){
      this.eventHandlers[eventName].splice(index, 1);
    }else{
      throw new Error(`${eventName} event handler not registered.`);
    }
  }
  unregisterAll(): void {
    this.eventHandlers = {};
  }

}
Войдите в полноэкранный режим Выход из полноэкранного режима

В этом классе у нас будет объект с обработчиками событий, и мы будем проверять, существует ли уже событие, на которое нужно зарегистрировать обработчик, если оно уже существует, мы просто добавим обработчик к событию, если не существует, мы создадим событие, а затем добавим обработчик. Также в этом классе есть метод notify, который получает событие и проходит через все обработчики, связанные с ним, выполняя метод handle

А затем давайте создадим наше событие

export class ProductCreatedEvent implements EventInterface{
  dateTimeOcurred: Date;
  eventData: any;

  constructor(eventData: any){
    this.dateTimeOcurred = new Date();
    this.eventData = eventData;
  }
}
Войдите в полноэкранный режим Выход из полноэкранного режима

и наш обработчик для этого события, который в данном случае будет отправлять электронное письмо при создании продукта

export class SendEmailWhenProductIsCreatedHandler implements EventHandlerInterface<ProductCreatedEvent>{
  handle(event: ProductCreatedEvent): void {
    console.log("Sending email to user...")
  }
}
Войдите в полноэкранный режим Выход из полноэкранного режима

и для того, чтобы это работало, нам нужна следующая реализация

    const eventDispatcher = new EventDispatcher();
    const eventHandler = new SendEmailWhenProductIsCreatedHandler();
    const spyEventHandler = jest.spyOn(eventHandler, "handle");

    eventDispatcher.register("ProductCreatedEvent", eventHandler)


    const productCreatedEvent = new ProductCreatedEvent({
      name: "Product 1",
      description: "Product 1 description",
      price: 100
    });

    eventDispatcher.notify(productCreatedEvent)
Войдите в полноэкранный режим Выход из полноэкранного режима

поэтому каждый раз, когда создается новый продукт, нам просто нужно вызывать функцию notify, которая выполняет все обработчики, связанные с полученным событием.

Я все еще изучаю DDD, и если вы хотите увидеть код лучше, вы можете проверить этот репозиторий

Оцените статью
Procodings.ru
Добавить комментарий