操控回转(IoC)和 依靠注入(DI)
操控回转是一种设计原则,用于将组件的创立和办理的操控权从组件本身转移到外部容器或结构。在传统的编程实践中,组件(如类或函数)一般会自行创立和办理它们的依靠项。IoC 经过将这些依靠项的创立和绑定移交给结构来颠倒这种操控。
在 Nest.js 中,IoC 容器负责办理方针的生命周期和依靠联系。它自动创立方针(称为 providers),解析它们的依靠联系,并在需求时将它们注入到其他方针中。这样做的优点是可以完成更高的模块化和更简略的单元测验。
依靠注入是完成 IoC 的一种技术。DI 答应类的依靠联系在运行时动态地传递给它,而不是在类内部硬编码。这样,类不需求知道怎么创立它们的依靠项,只需求知道依靠项的接口。这使得类更加松耦合,而且更简略测验和保护。
在 Nest.js 中,DI 首要经过结构函数注入完成。Nest.js 运用 TypeScript 的类型体系和装修器来指定一个类的依靠联系,并在创立类的实例时自动注入这些依靠项。
Nest.js 中的 DI 完成
- Providers: 在 Nest.js 中,任何可以注入到其他类中的东西都被称为 provider。这包括服务、库房、工厂等。
- Modules: Nest.js 运用模块来组织代码。每个模块界说了一组供给者、操控器和其他类,这些类是模块的一部分。模块体系答应 Nest.js 知道怎么组织和加载运用程序的不同部分。
- Decorators: Nest.js 运用 TypeScript 装修器来标记类和特点,以便 IoC 容器可以识别和注入依靠项。例如,@Injectable() 装修器用于标记一个类作为 provider,而 @Inject() 装修器用于注入特定的依靠项。
以下是一个简略的 Nest.js 服务和模块的比方,展现了依靠注入:
import { Injectable } from '@nestjs/common';
@Injectable()
export class MyService {
// MyService 的逻辑...
}
import { Module } from '@nestjs/common';
import { MyService } from './my.service';
@Module({
providers: [MyService],
exports: [MyService]
})
export class MyModule {}
在这个比方中,MyService 是一个 provider,它经过 @Injectable() 装修器标记。MyModule 是一个模块,它经过 @Module 装修器声明 MyService 为其供给者之一。
当 Nest.js 运用程序启动时,IoC 容器会根据模块和供给者的界说来解析依靠联系,并自动创立和注入实例。
经过这种办法,Nest.js 运用 IoC 和 DI 供给了一个强壮的架构,使得运用程序更加灵活和可保护。
MVC
MVC 是一种将运用程序分为三个中心组件的软件设计形式,即模型(Model)、视图(View)和操控器(Controller),旨在分离内部事务逻辑和用户界面,然后使得运用程序的开发、保护和扩展更为高效和有组织。
模型(Model)
在 Nest.js 中,模型一般代表与数据相关的部分,它可以是一个简略的类,也可以是运用 ORM(如 TypeORM 或 Sequelize)界说的数据模型。模型负责数据的存储、检索、更新和删除 — 这一般涉及到数据库的交互。
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
email: string;
}
在上面的示例中,咱们界说了一个用户模型,它经过装修器界说了怎么在数据库中创立表和字段。
视图(View)
Nest.js 作为一个后端结构,一般不直接处理视图,而是将数据发送给客户端(如浏览器或移动运用),由客户端结构(如 Angular、React 或 Vue.js)来处理视图相关的作业。不过,Nest.js 也支撑模板引擎(如 Handlebars、EJS 或 Pug),然后可以直接从服务器烘托视图。
假如你挑选在 Nest.js 中运用模板引擎,你的视图或许看起来是这样的:
<!DOCTYPE html>
<html>
<head>
<title>User Profile</title>
</head>
<body>
<h1>{{ user.name }}</h1>
<p>Email: {{ user.email }}</p>
</body>
</html>
此视图运用 Handlebars 语法显示用户信息。
操控器(Controller)
操控器是 MVC 架构中的指挥中心。在 Nest.js 中,操控器负责处理传入的恳求,并回来呼应给客户端。操控器运用装修器来界说路由,并将恳求路由到对应的处理函数。
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { UserService } from './user.service';
import { User } from './user.entity';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
async findAll(): Promise<User[]> {
return this.userService.findAll();
}
@Get(':id')
async findOne(@Param('id') id: string): Promise<User> {
return this.userService.findOne(id);
}
@Post()
async create(@Body() user: User): Promise<void> {
await this.userService.create(user);
}
}
在这个比方中,咱们界说了一个 UserController,它有三个办法:获取所有用户、获取一个特定用户和创立一个新用户。这些办法分别与 HTTP 恳求的 GET 和 POST 办法对应。
模型界说了运用程序的数据结构,视图负责展现数据(尽管在 Nest.js 中一般是由前端结构处理),而操控器则作为模型和视图之间的桥梁,处理事务逻辑和用户输入。
Nest.js 的 MVC 架构为开发人员供给了一种明晰、模块化的办法来构建运用程序。
拜访到一个恳求办法后,或许会经过 Controller(操控器)、Service(服务)、Repository(数据库拜访) 的逻辑。
假如我想在调用 Controller 之前和之后参加一段通用逻辑呢?
AOP 的优点是可以把一些通用逻辑分离到切面中,坚持事务逻辑的纯粹性,这样切面逻辑可以复用,还可以动态的增删。
AOP 基础知识
在现代的软件开发实践中,面向划面编程(AOP)是一种编程范式,它答应开发者将横切关注点(cross-cutting concerns)从事务逻辑中分离出来。这有助于增强模块化,使得代码更易于了解、保护和扩展。
Nest.js 作为一个强壮的 Node.js 结构,供给了一套丰富的装修器和协助函数来支撑 AOP 的完成。AOP 经过封装横切关注点的逻辑,答应开发者在不同的运用程序履行点动态地注入附加行为。
在 Nest.js 中,AOP 一般是经过运用 MiddleWare(中间件)、 Interceptors(拦截器)、Guards(护卫)、Filters(过滤器)、 **管道(Pipes)**和 Custom Decorators(自界说装修器)来完成的。接下来,咱们将详细了解这些组件和它们在 AOP 中的作用。
中间件(Middleware)
在 Nest.js 中,中间件是处于恳求和呼应周期中的一个关键点,它们类似于 Express 或 Koa 中的中间件。
中间件的首要作用是在恳求被路由处理程序处理之前,履行一些代码,可以用来履行比方日志记载、恳求验证、设置恳求头或许结束恳求/呼应周期等使命。
中间件函数可以拜访恳求方针(req)、呼应方针(res)以及运用的 next 函数,next 函数是一个当被调用时将操控权交给下一个中间件的函数。
假如当时中间件没有结束恳求/呼应循环,则必须调用 next(),以将操控权传递给下一个中间件,不然恳求将被挂起。
在 Nest.js 中,中间件需求完成 NestMiddleware 接口,并界说 use 办法。以下是一个中间件的简略示例,用于记载恳求信息:
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`${req.method} ${req.originalUrl}`);
next();
}
}
在上述代码中,LoggerMiddleware 类完成了 use 办法,该办法记载了恳求的 HTTP 办法和 URL,然后调用 next() 以持续处理恳求。
要将中间件运用到 Nest.js 运用程序中,你需求在模块中运用 configure 办法来设置它们。一般,你会在 configure 办法中指定中间件应该运用到的路由。例如:
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
@Module({
// ...
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('*'); // 运用到所有的路由
}
}
在 AppModule 类中,咱们完成了 NestModule 接口的 configure 办法,并经过 MiddlewareConsumer 的 apply 和 forRoutes 办法指定了中间件和中间件运用的路由。
中间件是处理恳求流程中不可或缺的一个环节,在 Nest.js 中,中间件供给了一种弹性极高的办法来介入恳求和呼应的过程。经过合理地运用中间件,可以大大增强运用程序的功能性和灵活性。
护卫(Guards)
在 Nest.js 中,护卫首要负责处理恳求的权限操控。一般是在路由处理程序履行之前,来决定是否答应恳求持续进行。这使得护卫成为完成例如认证和授权这类横切关注点的抱负挑选。
护卫是经过完成 CanActivate 接口创立的。每个护卫都必须供给一个 canActivate 函数,它回来一个布尔值或一个回来布尔值的 Promise 或 Observable,以确认是否可以持续当时恳求。
下面是一个简略的护卫示例,它查看恳求是否包括有用的认证令牌:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return this.validateRequest(request);
}
private validateRequest(request: Request): boolean {
// 这儿应该有一个实在的验证逻辑,这儿仅作为示例
return request.headers.authorization === 'some-secret-token';
}
}
在上面的代码中,AuthGuard 查看传入恳求的 authorization 头是否契合预期。
在实践运用中,validateRequest 办法会包括更复杂的逻辑,或许会调用一个服务来验证令牌的有用性。
要在 Nest.js 运用程序中运用护卫,你可以将其绑定到操控器或路由处理程序等级:
// 绑定到操控器
@Controller('users')
@UseGuards(AuthGuard)
export class UsersController {
// ...
}
// 绑定到路由处理程序
@Controller('users')
export class UsersController {
@Get(':id')
@UseGuards(AuthGuard)
getUserById(@Param('id') id: string) {
// ...
}
}
经过运用护卫,你可以保证只需经过验证和授权的恳求才干拜访特定的处理程序或操控器,然后增加了运用程序的安全性。
也可以绑定到大局模块,这样经过 provider 的办法声明的 Guard 是在 IoC 容器里的,可以注入其他 provider:
// app.module.ts
@Module({
providers: [
{
provide: APP_GUARD, // 运用 APP_GUARD 令牌
useClass: AuthGuard, // 将 AuthGuard 声明为大局的 Guard
},
AuthService,
],
})
export class AppModule {}
// auth.guard.ts
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService) {}
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
// 在这儿可以运用注入的 AuthService 进行身份验证逻辑
return this.authService.isAuthenticated();
}
}
拦截器(Interceptors)
拦截器是 Nest.js 中完成 AOP 的中心概念之一。它们答应你在办法履行之前或之后插入额定的逻辑。
拦截器十分合适处理日志记载、反常映射、事务处理等横切关注点。
在 Nest.js 中,拦截器是经过完成 NestInterceptor 接口创立的。拦截器有一个 intercept 办法,该办法接纳两个参数:
ExecutionContext 和 CallHandler。ExecutionContext 供给了当时恳求的详细信息,而 CallHandler 是一个可调用的流,代表了处理的下一个过程(一般是路由处理程序)。
让咱们经过一个日志记载的示例来看看拦截器是怎么作业的:
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(`After... ${Date.now() - now}ms`))
);
}
}
在上面的代码中,咱们创立了一个简略的 LoggingInterceptor,它在恳求处理之前和之后打印日志,并记载处理时刻。
咱们运用 tap 操作符来履行日志记载,它是一个不影响流中数据的 RxJS 操作符。
要在 Nest.js 中运用这个拦截器,你可以将其绑定到模块等级或操控器等级,乃至可以绑定到单个路由处理程序上:
// 绑定到模块等级
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
},
],
})
export class AppModule {}
// 绑定到操控器或路由处理程序
@Controller()
@UseInterceptors(LoggingInterceptor)
export class SomeController {
@Get()
@UseInterceptors(LoggingInterceptor)
findAll() {
// ...
}
}
拦截器供给了一种强壮而灵活的办法来运用通用的处理逻辑,而不会污染咱们的事务逻辑代码。
Interceptor 和 Middleware 的差异首要在于参数不同。
Interceptor 可以拿到调用的 controller 和 handler。
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
const controller = context.getClass(); // 获取当时调用的操控器
const handler = context.getHandler(); // 获取当时调用的处理器
// 履行下一个中间件或处理器
return next.handle().pipe(
tap(() => console.log('After...'))
);
}
}
管道(Pipes)
在Nest.js中,管道(Pipes)是另一个中心概念,用于处理恳求中的输入数据,例如验证或转化数据。管道可以在数据抵达路由处理程序之前履行一些操作,保证路由处理程序接纳到的数据是正确和预期的格式。
管道首要有两种用处:
- 验证(Validation):保证传入的数据契合必定的标准,假如数据无效,可以抛出反常。
- 转化(Transformation):将输入数据转化成期望的形式,例如从字符串转化为整数,或许从用户输入的日期字符串转化为Date方针。
管道经过完成 PipeTransform 接口来创立,该接口包括一个 transform 办法。这个办法接纳两个参数:当时处理的值和一个 ArgumentMetadata 方针,后者包括关于当时参数的元数据。
下面是一个简略的管道示例,用于验证字符串长度:
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class ValidateStringLengthPipe implements PipeTransform<string> {
constructor(private readonly minLength: number) {}
transform(value: string, metadata: ArgumentMetadata): string {
if (value.length < this.minLength) {
throw new BadRequestException(`The string is too short. Should be at least ${this.minLength} characters long.`);
}
return value;
}
}
在上述比方中,ValidateStringLengthPipe保证一个字符串至少有指定的最小长度。假如不是,它会抛出BadRequestException。
运用管道的示例如下:
@Controller('cats')
export class CatsController {
@Post()
async create(@Body(new ValidateStringLengthPipe(10)) body: string) {
// ...
}
}
在 CatsController 的 create 办法中,咱们经过 @Body 装修器和管道结合运用,以保证传入的 body 至少有 10 个字符长。假如客户端发送了一个长度小于10 的字符串,恳求将会失败,而且会回来一个 BadRequestException。
Nest.js 内置了一些常用的管道,例如 ValidationPipe,它可以与类验证器(class-validator)库结合运用,供给强壮的数据验证功能。
运用管道可以极大提高运用程序的健壮性,保证运用程序的事务逻辑处理之前,输入数据的正确性和有用性。
Nest 内置了一些 Pipe:
- ValidationPipe
- ParseIntPipe
- ParseBoolPipe
- ParseArrayPipe
- ParseUUIDPipe
- DefaultValuePipe
- ParseEnumPipe
- ParseFloatPipe
- ParseFilePipe
管道不只支撑上面临某个参数等级收效,也能对整个 Controller 、办法、大局都收效。
// 参数等级的 Pipes
@Get(':id')
getUserById(@Param('id', ParseIntPipe) id: number) {
// ...
}
// Controller 收效
@Controller('users')
@UsePipes(ValidationPipe)
export class UsersController {
// ...
}
// 办法等级的 Pipes
@Controller('users')
export class UsersController {
@Post()
@UsePipes(ValidationPipe)
createUser(@Body() createUserDto: CreateUserDto) {
// ...
}
}
// 大局 Pipes
import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';
import { ValidationPipe } from './pipes/validation.pipe';
@Module({
providers: [
{
provide: APP_PIPE,
useClass: ValidationPipe,
},
],
})
export class AppModule {}
自界说装修器(Custom Decorators)
自界说装修器在 Nest.js 中供给了一种强壮的办法来封装和重用注解逻辑。它们可以用来提取恳求中的特定数据,或许注入依靠项等。创立自界说装修器一般涉及到界说一个函数,它回来一个装修器工厂的成果。
让咱们看一个常见的比方:创立一个自界说装修器来提取 JWT(JSON Web Token)中的用户信息。
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const GetUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user; // 假定恳求已经经过某个中间件添加了`user`特点
},
);
在上面的代码中,GetUser 装修器答应咱们直接在路由处理程序的参数中提取用户方针,这样可以十分方便地拜访当时恳求的用户信息。
运用这个装修器的示例如下:
@Controller('profile')
export class ProfileController {
@Get()
getProfile(@GetUser() user) {
return user;
}
}
在 ProfileController 的 getProfile 办法中,咱们运用了 @GetUser() 装修器来获取当时恳求的用户方针。这样的封装使得操控器的代码更加明晰,也更简略测验。
自界说装修器可以用于多种情况,比方权限验证、日志记载、事务逻辑等,它们为你的 Nest.js 运用程序供给了更高的灵活性和模块化。
相同自界说装修器界说后,运用的办法也有多种,有类装修器、办法装修器、参数装修器、特点装修器。
反常过滤器(ExceptionFilter)
反常过滤器在 Nest.js 中是处理反常的引荐办法。它们供给了一种办法来捕获操控器中抛出的反常,并根据需求对其进行处理,例如记载日志、转化反常方针,或许发送自界说的呼应给客户端。这样可以保证你的错误处理逻辑集中化,易于办理。
反常过滤器经过完成 ExceptionFilter 接口并界说 catch 办法来创立。在 catch 办法中,你可以处理反常,并调用 HttpArgumentsHost 来操控回来给客户端的呼应。
下面是一个简略的反常过滤器示例:
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
@Catch(HttpException)
export class HttpErrorFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception.getStatus();
const errorResponse = {
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
method: request.method,
message: exception.message || null,
};
response
.status(status)
.json(errorResponse);
}
}
在这个比方中,HttpErrorFilter 捕获 HttpException 及其子类的反常。当反常发生时,它结构了一个包括错误信息和其他相关信息的呼应体,并将其回来给客户端。
要在你的 Nest.js 运用中运用此过滤器,你可以绑定它到整个运用、某个操控器或许详细的路由处理程序上,如下所示:
// 绑定到大局
import { Module } from '@nestjs/common';
@Module({
// ...
providers: [
{
provide: APP_FILTER,
useClass: HttpErrorFilter,
},
],
})
export class AppModule {}
// 绑定到操控器
@Controller('some-route')
@UseFilters(HttpErrorFilter)
export class SomeController {
// ...
}
// 绑定到路由处理程序
@Controller('some-route')
export class SomeController {
@Get()
@UseFilters(HttpErrorFilter)
findSomeRoute() {
// ...
}
}
Nest 内置了很多 http 相关的反常,都是 HttpException 的子类:
- BadRequestException
- UnauthorizedException
- NotFoundException
- ForbiddenException
- NotAcceptableException
- RequestTimeoutException
- ConflictException
- GoneException
- PayloadTooLargeException
- UnsupportedMediaTypeException
- UnprocessableException
- InternalServerErrorException
- NotImplementedException
- BadGatewayException
- ServiceUnavailableException
- GatewayTimeoutException
当然,也可以 extends HttpException 自己扩展。
Nest 经过这样的办法完成了反常到呼应的对应联系,代码里只需抛出不同的反常,就会回来对应的呼应。
几种 AOP 办法的次序
次序如下:
- 中间件 (Middleware): 在路由处理程序之前履行,一般用于履行一些大局的使命,比方日志记载、恳求预处理等。
- 护卫 (Guards): 在中间件之后、路由处理程序和拦截器之前履行,首要用于权限验证和授权。
- 拦截器 (Interceptors): 在护卫之后、路由处理程序之前履行,可以用于绑定额定的逻辑,如转化回来成果、绑定额定的逻辑到办法的履行之前或之后、扩展基本办法行为等。
- 管道 (Pipes): 可以在参数处理时履行,如数据转化和验证。
- 路由处理程序 (Route Handler): 实践的操控器办法,在这儿履行恳求的首要逻辑。
- 反常过滤器 (Exception Filters): 在路由处理程序之后履行,用于捕获和处理反常。
留意:自界说装修器一般是用来注入额定的元数据到类、办法或许参数中,它们并不直接参与恳求处理流程的次序。
假如你的自界说装修器是在管道、护卫、拦截器中运用的,那么它们的作用将会在这些特定组件的履行阶段表现出来。例如:
- 假如你在参数上运用自界说装修器来提取或转化恳求中的某些数据,那么这个装修器的作用会在管道处理这个参数时表现。
- 假如你在护卫或拦截器中运用自界说装修器来获取办法或类上的元数据,那么这个装修器的作用会在护卫或拦截器履行时表现。
总的来说,自界说装修器的作用是在它们被运用的当地表现的,而且它们的履行次序取决于它们被用于哪个恳求处理阶段的组件中。
拦截器可以被组织成一个链条,每个拦截器都可以在恳求抵达方针处理程序之前或之后履行某些逻辑。当有多个拦截器运用于一个路由时,它们的履行次序将依照它们在代码中的声明次序顺次履行。
例如,假定有三个拦截器:拦截器A、拦截器B 和 拦截器C。当恳求抵达方针处理程序之前,首先会履行拦截器A 的逻辑,然后是拦截器B 的逻辑,最后是拦截器C 的逻辑。类似地,在呼应回来之前,拦截器C 的逻辑会先履行,然后是拦截器B 的逻辑,最后是拦截器A 的逻辑。
最佳实践
经过前面的介绍,咱们了解到 Nest.js 经过供给拦截器、护卫、过滤器和自界说装修器等强壮的特性,使得面向切面编程(AOP)成为或许。AOP 在 Nest.js 运用程序中的运用提高了代码的模块化,使得横切关注点的办理变得更为集中和一致。
在实践开发中,要充分运用 AOP 带来的优点,以下是一些最佳实践:
- 明确划分责任:保证 AOP 的完成专心于单一职责,例如日志记载应只关注日志处理,认证护卫应只处理认证逻辑。
- 适度运用:尽管 AOP 供给了强壮的东西来解耦代码,但过度运用可以使代码变得难以了解和保护。在考虑运用 AOP 之前,应评估是否真的需求。
- 一致处理反常:经过大局反常过滤器一致处理反常,可以避免在事务逻辑中处处编写错误处理代码。
- 重用逻辑:在或许的情况下,应该创立可重用的拦截器、护卫和装修器,以减少重复代码并提高效率。
- 编写可测验的代码:AOP 完成应该易于单元测验,保证拦截器、护卫等可以在阻隔环境中测验其逻辑。
- 文档化:对于自界说装修器、拦截器等 AOP 元素,应该供给明晰的文档说明其用处和运用办法,以便团队成员了解和正确运用。
总结来说,Nest.js 的 AOP 功能为构建可保护、可扩展和高效的 Node.js 运用程序供给了强有力的支撑。经过本文的介绍,期望你可以深入了解这些概念,并在你的下一个 Nest.js 项目中运用这些强壮的东西。
