直接从服务抛出 http 异常是一个坏主意吗?

问题描述 投票:0回答:1

大家晚上好。

我有一个关于实体/架构/模块配置和依赖关系的问题。

通常是简单/简单的教程和示例,公平地说,我在工作中处理的大多数基本微服务主要是像 bff 模块一样构建的。控制器端点接收请求,然后调用服务来完成该请求或抛出 http 异常。如果我确定该方法/类的唯一使用者将是调用 http 端点的客户端,那就可以了。但是,如果我对于给定的方法/类有两个不同的消费者,并且在出现问题时抛出异常,那么会发生什么,这没有多大意义?我的意思是,我试图弄清楚让我的类/方法“与客户端无关”实际上是一件事,或者我是否应该添加几个 try/catch 语句并继续(就像我已经做了一段时间一样)现在哈哈)

我将使用基本和常见的 Nestjs 控制器/可注入服务/可注入存储库模式给出示例

假设我有以下内容

UserService

@Injectable()
export class UserService {
    constructor(private readonly userRepo: UserRepository) {}
    
    public async createUser(dto: CreateUserDto) {   
        /*
        * in here it will perform a variety of business rule logic and validation necessary to create and persist a new user
        * if something goes wrong, throw http exception
        */
        
        if (something goes wrong) {
            throw new BadRequestException() or ConflictException or another one that fits what went wrong
        }
        
        return this.userRepo.create(dto)
    }
}

在我看来,如果此方法静态抛出一个 http 请求,我们假设这是一个将由控制器方法调用访问的功能,因此也是一个 http 请求。现在假设我们在应用程序中有另一个流程,要求我们创建一个用户并将 UserService 作为依赖项。这个

OtherService
然后将直接使用
createUser
方法,而不是控制器方法。

@Injectable()
export class OtherService {
    constructor(private readonly userService: UserService) {}
    
    public async doSomeStuff(payload) { 
        /*
        * long chain of stuff being done
        */
        
        await this.userService.create(userDto)
        
        return something
    }
}

所以一般来说,如果我最终遇到这样的情况,我可以将

await this.userService.create(payload.dto)
调用包装在 try/catch 中并“解析”异常。但这对我来说听起来很奇怪,现在回想一下
UserService
的实现,立即假设
OtherService
实际上会 理解 什么是 BadRequestException。这有道理吗?我可以更深入,但我的问题围绕以下主题:

  • 直接从“UserService”抛出httpexpcetions是一种不好的做法吗?

  • 我考虑在我的应用程序业务逻辑和控制器之间构建一个层,仅用于抛出 http 异常。这是一件事吗?就像为

    UserService
    创建一个返回接口,创建像 { error: boolean, errorMessage?: "", result: any } 这样的消息,然后让我的控制器调用这个额外的层来决定是否需要抛出异常

提前致谢!我也将感谢任何有关考虑这个问题的概念/书籍/文章/想法的指示。

exception model-view-controller architecture nestjs
1个回答
0
投票

您可以创建一个NestJS全局异常。 https://docs.nestjs.com/exception-filters#inheritance 可以全局捕获所有异常并手动处理异常。

无需创建额外层,您就可以使用此全局异常处理程序。

import { DuplicateRecordException } from '@/domain/exceptions/duplicate-record';
import { InvalidOperationException } from '@/domain/exceptions/invalid-operation';
import { EntityNotFoundException } from '@/domain/exceptions/not-found-exception';
import { PermissionNotFoundException } from '@/domain/exceptions/permission-not-found';
import { ValidationException } from '@/domain/exceptions/validation-exception';
import { LoggerBase } from '@/domain/logger/logger.abstract';
import { ArgumentsHost, Catch, HttpServer } from '@nestjs/common';
import {
  BadRequestException,
  ConflictException,
  ForbiddenException,
  HttpException,
  InternalServerErrorException,
  NotFoundException,
} from '@nestjs/common/exceptions';
import { BaseExceptionFilter } from '@nestjs/core';
import { HttpErrorDescriptionConstants } from '../constants/http.error.description.constants';

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  constructor(private readonly logger: LoggerBase, httpServer: HttpServer) {
    super(httpServer);
  }
  catch(exception: unknown, host: ArgumentsHost) {
    console.log(exception);

    let httpException: HttpException;

    if (exception && exception instanceof ValidationException) {
      httpException = new BadRequestException(exception.getValidationFailures().toString(), {
        cause: new Error(),
        description: exception.message
      });
      this.logger.error(exception.message);
    } else if (exception && (exception instanceof 
      InvalidOperationException || exception instanceof 
      DuplicateRecordException)) {
      httpException = new ConflictException(
        exception.getErrorMessage()?.toString(),
        {
          cause: new Error(),
          description: exception.message,
        },
      );
    } else if (exception && exception instanceof PermissionNotFoundException) {
      httpException = new ForbiddenException(
        exception.getErrorMessage()?.toString(),
        {
          cause: new Error(),
          description: exception.message,
        },
      );
    } else if (exception && exception instanceof EntityNotFoundException) {
      httpException = new NotFoundException(
        exception.getErrorMessage()?.toString(),
        {
          cause: new Error(),
          description: exception.message,
        },
      );
    } else {
      httpException =
        exception instanceof HttpException
          ? exception
          : new InternalServerErrorException(
              HttpErrorDescriptionConstants.INTERNAL_SERVER_ERROR,
            );
    }

    super.catch(httpException, host);
  }
}

您还可以创建自定义例外

import { ExceptionBase } from './exception-base';

/**
  * Used to throw an error if duplicate records have been detected / 
  * if a unique key constraint has been violated.
  * @param errorDescription the error description to log to console.
  * @param errorMessage an optional parameter to show a user friendly
  * error message for the end users.
  */

export class DuplicateRecordException extends ExceptionBase {
  private readonly errorMessage?: string;

  constructor(errorDescription: string, errorMessage?: string) {
    super(errorDescription);
    this.errorMessage = errorMessage;
  }

  getErrorMessage(): string | undefined {
    return this.errorMessage;
  }
}
export class ExceptionBase extends Error {
  constructor(message: string) {
    super(message);
  }
}

并且您可以随时抛出此自定义异常

throw new DuplicateRecordException(errorDescription, errorMessage)

© www.soinside.com 2019 - 2024. All rights reserved.