2
votes

I have setup a Microservice Architecture that looks the following:

  • api-gateway (NestFactory.create(AppModule);)
  • service 1 (NestFactory.createMicroservice<MicroserviceOptions>)
  • service 2 (NestFactory.createMicroservice<MicroserviceOptions>) ...

A Service looks like this:

service.controller.ts
service.handler.ts

Where handler is like a Service in a typical Monolith that handles the logic.

Currently, I am catching Exceptions the following way:

  1. The handler makes a call to the database and fails due to a duplicated key (i.e. email).

  2. I catch this exception and convert it to an RpcException

  3. In the ApiGateway I catch the RpcException like so:

        return new Promise<Type>((resolve, reject) => {
           this.clientProxy
            .send<Type>('MessagePattern', { dto: DTO })
            .subscribe(resolve, (err) => {
                logger.error(err);
                reject(err);
            });
        });
    
  4. Again I have to catch the rejected Promise and throw an HttpException to have the ExceptionFilter sending a proper error response. Throwing an Error inside the Promise instead of rejecting it doesn't work)

So basically, I have 3 TryCatch Blocks for 1 Exception. This looks very verbose to me.

Is there any better way or best practice when it comes to NestJS Microservices?

Can we have an Interceptor for the rebound messages received by this.clientProxy.send and pipe it to send send the error response to the client without catching it 2 times explicitly?

1

1 Answers

1
votes

Not a complete answer to your question, but it's better than nothing. :)

I try to avoid the .subscribe, reject(...) approach whenever possible. Even though the send method returns an Observable, in most cases you expect only 1 response. So, in most cases .toPromise() makes sense. Once it's a promise, you can use the async-await syntax, and you don't have callbacks and you can effectively just catch all exceptions (and rethrow if you want to). It helps a bit.

try {
  const payload = { dto: DTO }; 
  const response = await this.clientProxy.send<Type>('MessagePattern', payload).toPromise();
} catch (err) {
  this.logger.error(err);
}

On the server side, you can effectively define Interceptors, which are almost identical to the Interceptors you would use for API controllers.

@MessagePattern(messagePattern)
@UseInterceptors(CatchExceptionInterceptor)
public async someMethod(...) { }

You should implement the NestInterceptor interface:

@Injectable()
export class CatchExceptionInterceptor implements NestInterceptor {
    intercept(context: ExecutionContext, stream$: Observable<any>): Observable<any> {
        return stream$.pipe(
            catchError(...)
        );
    }
}