手记

用NestJS、TCP和TypeScript构建微服务的实战教程

微服务间的互动方式

照片由Yifu Wu来自Unsplash

随着我们的项目规模越来越大,我们需要越来越先进的架构来支持我们的项目。因此,作为一名软件工程师,我想向您介绍一种现代流行的微服务架构,它遵循面向服务的架构(SOA)的理念。

在这篇文章里,我想谈谈monolithic architecture和微服务架构之间的区别,并展示如何使用这些技术来构建这两种架构。首先,让我们了解一下什么是微服务。

什么是微服务呢?

微服务是一种软件开发的架构方法,其中软件由小型且独立的服务组成,这些服务通过定义明确的API进行通信。每个服务负责特定的任务或业务目标,并通过API与其他模块和服务进行通信。这使得扩展和开发应用程序变得更加容易和快捷,从而促进了创新,加快了新功能推向市场的速度。

什么是单体架构和微服务架构之间的关键区别?如果采用单体架构的话,那么应用程序中的所有功能和服务都会被结合在一起作为一个单一的整体运行。但微服务将底层逻辑拆分为不同的服务,每个服务都可以独立开发、部署并通过API公开。

为了更清楚地理解,我们将一起用NestJS开发一个微服务项目。

创建项目:

在我们开始之前,我想强调我们项目中的两个重要要点。

  • auth-microservice — 负责管理用户的权限的认证服务
  • API Gateway — 位于客户端和微服务之间的网关,将HTTP API端点的事件转发给微服务

简而言之,当用户通过 /api/login 端点使用凭证登录系统时,他们就会与 API 网关连接。API 网关将通过请求-响应模式与认证微服务进行消息的发送和接收。这大致就是我们的应用将如何运作。

因为我们将会构建多个服务,最好采用单仓库项目,它是一个包含多种应用程序和库的单一代码仓库。因此,我们将使用 [Nx](https://nx.dev) 工具来进行单仓库管理,它允许你在单仓库中构建和扩展 web 应用和服务,使其更灵活和强大。

首先,让我们用下面的命令创建一个单存储库项目:

运行以下命令来创建一个新的Nx工作空间,使用Nest模板:

npx create-nx-workspace nestjs-microservices --preset=nest

指定应用名为 api-gateway

项目 创建 nestjs-microservice,

接下来我们运行以下命令来安装项目所需的依赖项:

    cd nestjs-microservices  
    npm i @nestjs/microservices class-validator class-transformer
添加授权模块

自从我们的项目启动以来,nx 已经为我们创建了一个 API Gateway 服务应用程序。我们现在在这个 API Gateway 应用中创建一个 auth 模块,用于处理认证相关的请求。

当用户向我们的应用发起请求时,API Gateway 会接收该请求并将其转发至微服务。因此,它们将使用相同的数据格式,创建一个共享库以避免在单个代码库中重复相同代码是有意义的。可以通过以下命令:

nx g @nx/nest:lib shared

我们现在来创建一个dto文件夹(夹子),并添加一个create-user.dto.ts文件。

// shared/src/lib/dto/create-user.dto.ts

import { IsNotEmpty, IsString } from 'class-validator';

export class CreateUserDto {
  // 创建用户数据传输对象
  // 必须是字符串
  // 不可为空
  @IsString()
  @IsNotEmpty()
  username: string;

  // 不可为空
  @IsNotEmpty()
  password: string;
}

我们还可以在 tsconfig.base.json 文件中添加路径条目,并使用绝对路径导入它们:

{
  ...
  "compilerOptions": {
    ...
    "paths": {
      "@nestjs-microservices/shared": ["shared/src/index.ts"] // 指向nestjs-microservices共享模块的路径
    }
  },
  ...
}

NestJS 使用默认的 TCP 传输层在不同微服务实例之间传递消息。NestJS 提供了一个 ClientsModule,它提供了静态的 register() 方法,该方法接收描述微服务传输方式的对象数组作为参数。让我们添加 auth.service.ts 并使用以下代码行注册 AUTH_MICROSERVICE,如下所示:

    // apps/api-gateway/src/auth/auth.module.ts  

    import { Module } from '@nestjs/common';  
    import { ClientsModule, Transport } from '@nestjs/microservices';  

    import { AuthController } from './auth.controller';  
    import { AuthService } from './auth.service';  

    @Module({  
      imports: [  
        ClientsModule.register([  
          {  
            name: 'AUTH_MICROSERVICE',  
            transport: Transport.TCP,  
            options: {  
              host: 'localhost',  
              port: 3001,  
            },  
          },  
        ]),  
      ],  
      providers: [AuthService],  
      controllers: [AuthController],  
    })  
    export class AuthModule {}

以上,每个传输器都有一个名字属性 name,一个可选的 transport 属性,默认为 TCP(即 Transport.TCP),以及一个可选的特定传输器选项 options 属性。

引入模块后,我们就可以使用 AUTH_MICROSERVICE 传输参数配置好的 ClientProxy 实例,通过 @Inject() 装饰器在 auth.service.ts 中注入,直接参考如下代码示例:

// apps/api-gateway/src/auth/auth.service.ts
// 一个用于认证服务的文件

import { Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';

import { CreateUserDto, User } from '@nestjs-microservices/shared';

@Injectable()
export class AuthService {
  constructor(
    @Inject('AUTH_MICROSERVICE') private readonly authClient: ClientProxy
  ) {}

  getUser(createUserDto: CreateUserDto) {
    return this.authClient.send<用户, 创建用户DTO>('获取用户', createUserDto);
  }

  createUser(createUserDto: CreateUserDto) {
    return this.authClient.send<用户, 创建用户DTO>('创建用户', createUserDto);
  }
}

如上所示,我们可以使用 get_usercreate_user 模式名向认证微服务发送一条消息。当用户登录、注册时,我们将用它们。

send 方法用于调用一个微服务,并返回一个 Observable 作为响应。它接受两个参数。

  • 模式(pattern) — 定义在 @MessagePattern() 装饰器中的
  • 消息负载 — 我们想要发送给微服务的消息

我们将创建一个 AuthController 类,这个类包含两个 API 端点,分别是 loginsignup

    // apps/api-gateway/src/auth/auth.controller.ts  

    import { Body, Controller, Post, BadRequestException } from '@nestjs/common';  
    import { lastValueFrom } from 'rxjs';  

    import { CreateUserDto, User } from '@nestjs-microservices/shared';  

    import { AuthService } from './auth.service';  

    @Controller('auth')  
    export class AuthController {  
      constructor(private readonly authService: AuthService) {}  

      @Post('login')  
      async login(@Body() createUserDto: CreateUserDto) {  
        const user: User = await lastValueFrom(this.authService.getUser(createUserDto), {  
          defaultValue: undefined,  
        });  
        if (!user) {  
          throw new BadRequestException('无效的登录信息');  
        }  

        const isMatch = user.password === createUserDto.password;  
        if (!isMatch) {  
          throw new BadRequestException('密码错误');  
        }  

        console.log(`用户 ${user.username} 已成功登录。`);  

        return user;  
      }  

      @Post('signup')  
      async signup(@Body() createUserDto: CreateUserDto) {  
        const user: User = await lastValueFrom(this.authService.getUser(createUserDto), {  
          defaultValue: undefined,  
        });  
        if (user) {  
          throw new BadRequestException(`用户名 ${createUserDto.username} 已经存在!`);  
        }  

        return this.authService.createUser(createUserDto);  
      }  
    }

如前所述,getUsercreateUser 认证客户端方法返回一个 Observable,这意味着你需要显式地订阅它,以确保请求被发送。你可以使用从 rxjs 导入的 lastValueFrom 方法将其转换为 Promise

搭建一个身份验证微服务:

接下来,我们将通过运行以下命令来创建我们的第一个身份验证微服务:

    nx g @nx/nest:app auth-microservice

生成一个基于 Nest 的应用程序,用于认证微服务 (shēngchénɡ yīɡè jīyú Nest de chǎnɡchēnyíng, yùnɡyú zhēnɡrèn wēi shìfù)

让我们在 bootstrap() 函数中更新样板代码,使用 NestFactory.createMicroservice() 方法,并在 auth-microservice 应用的 main.ts 文件中进行更新。

    // apps/auth-microservice/src/main.ts  

    import { Logger } from '@nestjs/common';  
    import { NestFactory } from '@nestjs/core';  
    import { Transport, MicroserviceOptions } from '@nestjs/microservices';  

    import { AppModule } from './app/app.module';  

    async function bootstrap() {  
      const app = await NestFactory.createMicroservice<MicroserviceOptions>(  
        AppModule,  
        {  
          transport: Transport.TCP,  
          options: {  
            host: 'localhost',  
            port: 3001,  
          },  
        }  
      );  

      await app.listen();  

      Logger.log('🚀 认证微服务正在监听');  
    }  

    bootstrap();

NestFactory 类的 createMicroservice() 方法就是用来创建微服务实例的。

然后,我们将在一个shared库中创建一个名为User的实体,用于在UsersRepository类中存储用户信息和检索用户。

// shared/src/lib/entities/user.entity.ts

// 定义用户类,包含用户ID、用户名和密码
export class User {  
  id?: number;  
  username: string;  
  password: string;  
}

我们不会使用任何数据库,并为了简洁,我们将在内存中存储数据。让我们创建一个简单的 user.repository.ts 文件,并包含 UserRepository 类:

    // apps/auth-microservice/src/app/user.repository.ts  

    import { Injectable } from '@nestjs/common';  

    import { CreateUserDto, User } from '@nestjs-microservices/shared';  

    @Injectable()  
    export class UserRepository {  
      private users: User[] = [];  

      save(user: CreateUserDto): User {  
        const newUser = new User();  
        newUser.id = this.users.length + 1;  
        newUser.username = user.username;  
        newUser.password = user.password;  
        this.users.push(newUser);  
        return newUser;  
        // 保存用户信息
      }  

      findOne(username: string): User | undefined {  
        return this.users.find((user) => user.username === username);  
        // 查找具有指定用户名的用户
      }  
    }

我们现在要在 app.service.ts 文件中添加 createUser()getUser() 方法,这两者分别用于创建和查找用户,利用 UserRepository 中的方法。

    // apps/auth-microservice/src/app/app.service.ts  

    import { Injectable } from '@nestjs/common';  

    import { CreateUserDto, User } from '@nestjs-microservices/shared';  

    import { UserRepository } from './user.repository';  

    @Injectable()  
    export class AppService {  
      constructor(private readonly userRepository: UserRepository) {}  

      createUser(newUser: CreateUserDto): User {  
        return this.userRepository.save(newUser);  
      }  

      getUser(username: string): User | undefined {  
        return this.userRepository.findOne(username);  
      }  
    }

最后,我们将基于请求-响应模式,使用从 @nestjs/microservices 包导入的 @MessagePattern() 装饰器来创建消息处理方法。

    // apps/auth-microservice/src/app/app.controller.ts  

    @Controller()  
    export class AppController {  
      constructor(private readonly appService: AppService) {}  

      @MessagePattern('get_user') // 监听 'get_user' 消息模式  
      handleGetUser(user: CreateUserDto) {  
        返回 this.appService.getUser(user.username);  
      }  

      @MessagePattern('create_user') // 监听 'create_user' 消息模式  
      handleCreateUser(newUser: CreateUserDto) {  
        返回 this.appService.createUser(newUser);  
      }  
    }

在上述代码中,handleGetUser() 消息处理器监听匹配 get_user 模式的消息。该处理器接收一个参数,作为来自客户端的 user 参数,其类型为 CreateUserDto,该参数由客户端传递。

运行并测试服务:

要测试所有服务,我们需要在不同的终端分别运行以下命令,并依次进行。

运行api-gateway服务  
启动auth-microservice服务

测试这个应用时,我们可以使用 Postman 或任何其他 API 测试工具。

无效登录

使用无效的凭证尝试登录

注册

点击注册

登录

登入

用相同的用户名注册

用已有用户名注册

结论

如果你想查看全部代码,这里有个链接到GitHub

感谢您的阅读,希望您觉得这篇文章有用。祝您编程顺利!

资源
使用 NestJS、Kafka 和 TypeScript 构建微服务 —— LogRocket 博客本指南演示了如何使用 NestJS、Kafka 和 TypeScript 构建可扩展且可靠的微服务架构…blog.logrocket.com
使用 Nest.js 打造微服务原来这么简单!Nest.js 是一个用于打造高效、可靠的服务器端应用的现代化 Node.js 框架—— 标签:…dev.to
文档 | NestJS - 一个渐进式的 Node.js 框架Nest 是一个用于构建高效且可扩展的 Node.js 服务器应用的框架。它使用渐进式的 JavaScript……docs.nestjs.com
0人推荐
随时随地看视频
慕课网APP