NodeJS Service Layer asynchronous communication

Viewed 213

If I have to invoke ServiceB.action as a result of ServiceA.action being invoked, should I call ServiceB.action from ServiceA.action at the appropriate time, or should I emit an event from ServiceA and let ServiceB react to that event?

I can use an EventEmitter, but it works synchronously, in a sense that the callbacks are invoked sequentially without waiting the previous to resolve, which is ok for me. What isn't, though, is that the $emit will not wait until all of the listeners have resolved.

For a more real world scenario, lets assume that I have a Subscription service, and when a new subscription is created, the Email service needs to know about it so that it can notify somebody by email.

const SubscriptionService = {
  async create(user) {
    await Subscription.create(user)
   
    // Notify email service
  }
}

const EmailService = {
    async notify(user) {...}
}

One way I could do it, is call the EmailService.notify(user) from SubscriptionService.create and pass in the user.

Another way I could do it is with an event emitter

async create(user) {
    await Subscription.create(user)

    Emitter.$emit('subscription', user)
}

The second approach seems clean, because I'm not mixing things together, the notifications happen as a result of the subscription being created and the create method is not polluted with extra logic. Also, adding more side-effects like the notification one would be simpler.

The subscription service's create method, however, will not wait until all of the listeners have resolved, which is an issue. What if the Notification fails? The user will not get the email, in this scenario. What about error handling? In the first scenario I can handle the errors on spot when I invoke the EmailService.notify, but not with the emitter.

Is not meant for similar use-cases or am I looking at the problem in a wrong way?

3 Answers

Since the create method returns a Promise, you can just do something like:

SubscriptionService.create(user).then((user) => EmailService.notify(user));

Note: The above depends on where and how you're calling these methods.

The event pattern is the most appropriate for the scenario you described. Rather than using std event emitters you could use promise-events which should let you do error handling as it seems you described.

Hope this helps.

Both of the approaches are appliable and correct for specific scenarios.

Pub/Sub is great for the applications where there are few subscribers and does various jobs in the background. Publishers(SubscriptionService.create) are loosely coupled to the subscribers-(EmailService.notify)

Service layer exists for applying business logics, and nothing wrong with using function composition for more than one methods which serves for a single operation. If it should notify user after subscription has been created, yes those two methods can work together. Also, if you want to use EmailService.notify method in somewhere else, you have to publish a new event, and create a new subscriber for that too, because you can't just use the previously created subscriber.

Personally, I would prefer, calling ServiceB.action inside ServiceA.action on your scenario. Publishing an event and creating a subscriber in the ServiceB is looks a bit much.

Related