Hugo Delatte
Posted on November 13, 2023
What is EDA ?
The bus event refer to event driven architecture ( EDA ). The main purpose of the EDA is to break coupling between services by opposition to the service driven architecture ( SOA ).
In SOA when a consumer need a service, he directly ask to a supplier to provide this service. In this classique composition, we need to know in the consumer, which provider will resolve our issue: We have in SOA a strong coupling between services.
With the EDA, the services emit events when they make particular actions. It is the task of the other services to listen these events and to do their jobs at this moment. In this architecture the emitting service never know and dont needs to know which provider should be called.
In the EDA, we use bus events in order to transmit events. All the services will subscribe to the bus event and only listen the events they need to handle.
Bus event implementation
Our bus event is really simple in fact. We have declared into the core module (provided in all the application) an rxjs Subject.
{ provide: EVENT_MESSAGE, useValue: <EventMessageBus>new Subject<DomainEvent>() }
From this we just need two more things to use this bus. one way to publish and one way to subscribe.
You will retrieve two services that will carry these tasks:
- The
EventHandler
, will just subscribe to the subject and filter the events you need, - The
EventPublisher
, just publish event you give him as input.
Let’s see a small example right now to picture the concept…
Exemple : The Toast service
We actually use a Toast service in our application. When some actions are done by our services or when we receive data from the server we want to display toasts to warn the user that some events occurs.
We have obviously created a Toast service that is capable to display toast when we give him a message, a title and, a status. But the main problem from this point is that, we will have to call this service everywhere in the application in order to use it.
To avoid it, the bus event will clearly help us. Instead of calling the toast service, we will just push events into the bus event. The toast service will subscribe to the relevant event and handle them by displaying a toast.
First of all, inject the event publisher into your service:
constructor(private readonly eventPublisher: EventPublisher) {}
Use the publisher to publish en event into the bus when an action needs to be triggered. Here we try to update a client into the ClientEditionStore
bu we want to handle cases there is an error that come back from the server, so we publish the ClientEditionErrorEvent
readonly updateClient = this.effect((origin$: Observable<Omit<Client, 'contacts' | 'sequenceEnabled'>>) => {
return origin$.pipe(
switchMap((client) => this.clientsHttpService.updateClient(client.id, client)),
catchError(() => {
this.eventPublisher.publish(new ClientEditionErrorEvent());
}),
);
});
Of course you will have to define this event before using it:
export class ClientEditionErrorEvent extends DomainEvent {}
The DomainEvent
is actually the super class of the event we can publish into the bus event . If you check in the first part of this guide you will retrieve this class into the Subject injection.
It is all you need to do in this service but you need to handle this event in the toast service now!
First of all inject the event handler:
constructor(private readonly eventHandler: EventHandler) {}
And you can subscribe / handle to the bus event and display your toast here
initToaster() {
this.eventHandler
.handle(
ClientEditionErrorEvent,
)
.subscribe((event) => this.showToastByEvent(event));
}
When use a bus event ?
In our application we can use bus event in different use cases:
- To allow stores to discuss and exchange information,
- To allow application to scale without adding links between services,
- To allow an easy handling of the toasts,
- To allow an easy handling of the server errors
Mistakes to avoid
Do not emit events that are handled in the same service ❌
readonly deleteStep = this.effect((origin$: Observable<{ sequenceId: string; stepId: string }>) => {
return origin$.pipe(
switchMap(({ sequenceId, stepId }) => this.sequenceService.deleteStep(sequenceId, stepId)),
tapAndRetry(
() => this.eventPublisher.publish(new DeleteStepSuccessEvent()),
() => this.eventPublisher.publish(new DeleteStepErrorEvent()),
),
);
});x
this.eventHandler
.handle(DeleteStepSuccessEvent, SequenceUpdateEnableSuccessEvent, SequenceSettingsUpdateOrCreateSuccessEvent)
.pipe(
untilDestroyed(this),
withLatestFrom(this.sequence$),
map(([_, sequence]) => sequence),
filterUndefined(),
tap((sequence) => this.fetchSequence(sequence.id)),
)
.subscribe();
Posted on November 13, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.