DEV Community

Yan.ts
Yan.ts

Posted on

DDD Domain events

Today I Learned 06/05/2022


Eventos de domínio

A essência de um evento de domínio é que você o usa para capturar coisas que podem desencadear uma mudança no estado do aplicativo que você está desenvolvendo. Esses objetos de evento são processados para causar alterações no sistema e armazenados parar fornecer um AuditLog
Fowler, Martin

Ou seja quando algo acontece no sistema eu posso baseado nesse evento executar uma operação e armazenar o evento para um caso de auditoria no futuro

Todo evento deve ser representado em uma ação representada no passado, exemplo UserCreated


Quando utilizar

Normalmente um domain event deve ser utilizado quando queremos notificar outros Bounded Contexts de uma mudança de estado

exemplo: Um sistema de E-commerce quando um pedido é criado, podemos emitir um evento de OrderPlaced e então o boundedContext de emissão de notas fiscais vai escutar esse evento e emitir a nota para aquele pedido


Componentes

  • Event Tem uma data e hora, e contem o que aconteceu naquela data e hora Ex: produto com nome X mudou para Y
  • Handler Executa o processamento quando o evento é chamado Ex: depois de criar um usuário eviar um email, a parte de enviar email é um handler
  • Event Dispatcher Responsável por armazenar e executar os handlers de um evento quando ele é disparado

Dinâmica

  • Criar um "Event Dispatcher"
  • Criar um "Evento"
  • Criar um "Handler" para o "Evento"
  • Registrar o Evento, juntamente com o Handler no "Event Dispatcher"

E ai quando dermos um notify no Event dispatcher passando o evento, todos os handlers serão executados


Implementação

primeiro vamos criar as nossas interfaces que serão implementadas pelo dispatcher, pelos eventos e pelos 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;
}
Enter fullscreen mode Exit fullscreen mode

e ai vamos criar a nossa classe de event dispatcher

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 = {};
  }

}
Enter fullscreen mode Exit fullscreen mode

Nessa classe vamos ter um objeto com os event handlers e vamos verificar se o evento ao qual o handler quer ser registrado já existe, se já existir vamos somente adicionar o handler ao evento, se não existir vamos criar o evento e adicionar o handler em seguida. Também nessa classe temos o método notify, que recebe o evento e percorre todos os handlers associados a ele executando o método handle

E ai vamos criar o nosso evento

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

  constructor(eventData: any){
    this.dateTimeOcurred = new Date();
    this.eventData = eventData;
  }
}
Enter fullscreen mode Exit fullscreen mode

e o nosso handler para esse evento que nesse caso será o de enviar email quando o produto for criado

export class SendEmailWhenProductIsCreatedHandler implements EventHandlerInterface<ProductCreatedEvent>{
  handle(event: ProductCreatedEvent): void {
    console.log("Sending email to user...")
  }
}
Enter fullscreen mode Exit fullscreen mode

e para fazermos isso funcionar basta a seguinte implementação

    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)
Enter fullscreen mode Exit fullscreen mode

então toda vez que um novo produto for criado basta a chamarmos a função notify que executa todos os handlers associados ao evento que ela recebeu

Ainda estou aprendendo sobre DDD e se quiser ver melhor o código pode dar uma olhada nesse Repositório

Top comments (0)