microservicesnestjs

nestjs microservices - have one clientProxy to publish message to any microService


Sometimes, you want to say, "I have this message, who can handle it?"
In nestjs a client proxy is bounded directly to a single microservice.

So, as an example, let say that I have the following micro-services: CleaningService, FixingService.
Both of the above can handle the message car, but only CleaningService can handle the message glass.

So, I want to have something like:

this.generalProxy.emit('car', {id: 2});

In this case, I want 2 different microservices to handle the car: CleaningService and FixingService.

in this case:

this.generalProxy.emit('glass', {id: 5});

I want only CleaningService to handle it.

How is that possible? how can I create clientProxy that is not bonded directly to a specific microservice.


Solution

  • The underlying transport layer matters because despite the fact that there is an abstraction in front of the different transports each underlying one has completely different characteristics and capabilities. The type of messaging pattern you're talking about is simple to accomplish with RabbitMQ because it has the notion of exchanges, queues, publisher, subscribers etc while a TCP based microservice requires a connection from one service to another. Likewise, the Redis transport layer uses simple channels without the necessary underlying implementation to be able to support some messages being fanned out to multiple subscribers and some going directly to specific subscribers.

    This might not be the most popular opinion but I've been using NestJS professionally for over 3 years and I can definitely say that the official microservices packages are not sufficient for most actual production applications. They work great as a proof of concept but quickly fall apart because of exactly these types of issues.

    Luckily, NestJS provides great building blocks and primitives in the form of the Module and DI system to allow for much more feature rich plugins to be built. I created one specifically for RabbitMQ to be able to support the exact type of scenario you are describing.

    I highly recommend that since you're using RabbitMQ already that you check out @golevelup/nestjs-rabbitmq which can easily support what you want to accomplish using native RMQ concepts like Exchanges and Routing Keys. (Disclaimer: I am the author). It also allows you to manage as many exchanges and queues as you like (instead of being forced to try to push all things through a single queue) and has native support for multiple messaging patterns including PubSub and RPC.

    You simply decorate your methods that you want to act as microservice message handlers with the appropriate metadata and messaging will just work as expected. For example:

    @Injectable()
    export class CleaningService {
      @RabbitSubscribe({
        exchange: 'app',
        routingKey: 'cars',
        queue: 'cleaning-cars',
      })
      public async cleanCar(msg: {}) {
        console.log(`Received message: ${JSON.stringify(msg)}`);
      }
    
      @RabbitSubscribe({
        exchange: 'app',
        routingKey: 'glass',
        queue: 'cleaning-glass',
      })
      public async cleanGlass(msg: {}) {
        console.log(`Received message: ${JSON.stringify(msg)}`);
      }
    }
    
    @Injectable()
    export class FixingService {
      @RabbitSubscribe({
        exchange: 'app',
        routingKey: 'cars',
        queue: 'fixing-cars',
      })
      public async fixCar(msg: {}) {
        console.log(`Received message: ${JSON.stringify(msg)}`);
      }
    }
    

    With this setup both the cleaning service and the fixing service will receive the car message to their individual handlers (since they use the same routing key) and only the cleaning service will receive the glass message

    Publishing message is simple. You just include the exchange and routing key and the right handlers will receive it based on their configuration:

    amqpConnection.publish('app', 'cars', { year: 2020, make: 'toyota' });