Skip to main content

Event Types

Event Drive Architecture overlaps with Domain Driven Design with the concept for an event. In DDD, an internal domain event is a notification that a domain object has changed. In EDA, an event is a message that is produced by a service and consumed by another service.

While these two event types are appear similar, they are different in their purpose and usage, and as such, they are treated differently in the codebase.

Domain Events

Domain Events are internal events that are raised by domain aggregates. They are used to notify other parts of that domain that a domain aggregate has changed. Event listeners are used to react to these events.

Domain Event Example

Domain Layer

// domain/Event/ItemBonusAdded.ts
class ItemBonusAdded {
constructor(
public readonly itemId: number,
public readonly bonusId: number,
) {}
}

// domain/Entity/Item.ts
class Item extends AggregateRoot {
public addItemBonusId(bonusId: number) {
// Validate
if (bonusId < 0) {
throw new BonusIdMustBePositiveException();
}

// Update state
this.bonusIds.push(bonusId);

// Raise a new domain event
this.record(new ItemBonusAdded(this.id, bonusId));
}
}

Application or Infrastructure Layer

Depending on the use case, an event listener can be created in the application or infrastructure to react to the domain event. In Nest.js, this is done using the @Saga decorator.

// application/EventListeners/ItemBonusAddedListener.ts
class ItemBonusAddedListener {
@Saga()
public handle$ = (events$: Observable<any>): Observable<ICommand> => {
return events$.pipe(
ofType(ItemBonusAdded),
map((event) => new AddBonusToItem(event.itemId, event.bonusId)),
);
};

public constructor(@Inject(ItemRepository) private readonly itemRepository: ItemRepositoryInterface) {}
}

Event Stream and Message Stream Events

Event Stream Events are messages that are produced by a service and consumed by another service. They are used to notify other services that an event has occurred. Within a domain service, an event stream event must be emitted to the event stream. In Nest.js, this is done using the @Saga decorator.

Event Stream and Message Stream events differ only in their intended recipients and longevity. Event Stream events are intended for other services to consume and store, while Message Stream events are intended for other services to consume and react to. As Event Stream events sit on the Event Stream, they are stored for a longer period of time in the log so they can be replayed, while Message Stream events are consumed and then discarded.

Event Stream and Message Stream Example

// infrastructure/IntegrationEvents/Sagas/ItemDomainEvents.ts
import { EMPTY, forkJoin, Observable, switchMap } from 'rxjs';

class ItemDomainEvents {
@Saga()
public handle$ = (events$: Observable<any>): Observable<ICommand> => {
return events$.pipe(
ofType(ItemBonusAdded),
switchMap((domainEvent) => {
const eventMessage = this.createItemEvent(ItemEventNames.ItemUpdated, domainEvent);

return forkJoin([
this.eventStream.emit(this.topic, domainEvent, eventMessage),
this.messageStream.emit(this.topic, domainEvent, eventMessage),
]);
}),
switchMap(() => EMPTY),
);
};

public constructor(
private readonly eventStream: EventStream,
private readonly messageStream: MessageStream,
) {}
}