大家晚上好。
我有一个关于实体/架构/模块配置和依赖关系的问题。
通常是简单/简单的教程和示例,公平地说,我在工作中处理的大多数基本微服务主要是像 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 } 这样的消息,然后让我的控制器调用这个额外的层来决定是否需要抛出异常
提前致谢!我也将感谢任何有关考虑这个问题的概念/书籍/文章/想法的指示。
您可以创建一个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)