Skip to content

仲灏小栈

仲灏约 1 分钟

前言

在上一章的末尾提到了目前我们的工程已经成为了一个非常大的应用,它分别有网关 Core、用户、物料三大模块组成,即使目前模块的功能都还是最简单的情况下,都已经达到了 40+ 接口的程度,后期再复杂一点的情况下,那么整个项目的迭代都会变得很复杂。

为了避免后期的开发与维护的麻烦,可以提前将工程拆解为 3 个独立的项目。

项目拆分

拆分方式

在物料系统中提到了一般项目管理方式有如下两种:

  • multirepo 分散式管理

将项目分化成为多个模块,每一个模块都有一个独立的 Reporsitory 来管理。

优点

  1. 对于每个项目来说,不再限定开发语言与规范,开发人员可以选择擅长的框架来开发功能;
  2. 单项目的功能将更加聚焦,只关注某一个具体模块的开发,开发人员在需求分配上会更为合理;
  3. 可以有自己的分支管理规范与开发节奏,单需求开发效率更高。

缺点

  1. 同步上线会比较困难,一个大型的项目可能存在十几或者更多服务模块,一次上线可能需要同步构建多个服务;
  2. 由于多个仓库管理,同步需求中相互依赖性上升,开发联调效率会降低;
  3. 存在不同语言、框架实现的情况,会造成总体项目维护成本上升。
  • monorepo 集中式管理

将所有的模块统一的放在同一个 Reporsitory 中管理。

优点

  1. 统一的规范、语言、框架,项目整体结构完整性远超 multirepo 方式;
  2. 标准化的开发流程,规避很多不必要的冲突与错误,包括整体架构升级等;
  3. 所有模块都在一个项目中方便调试与总体工程级别的迭代与维护。

缺点

  1. 项目过大的情况下,整体代码过于臃肿;
  2. 单仓库中对分支管理要求较高,修改和开发可能变得繁琐,降低效率。

综上所述,两种模式都有利有弊,分布式管理比较简单也是大家常用的开发模式,所以接下来我们将着重展示 monorepo 的拆分步骤。

Monorepo 拆分

由于我们的项目是基于 Nest CLI 搭建的,所以可以直接使用 Nest CLI 提供的 monorepo 的拆分功能。

首先执行,CLIgenerate app 脚本,分别创建对应的 materialsuserCenter 工程。

shell
$  nest generate app materials
$  nest generate app userCenter

image.png

如上图所示,Nest CLI 已经帮我们创建好了对应的工程目录,同时大家也可以观察一下 nest-cli.json 里面的参数配置区别:

diff
{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "apps/fast-gateway/src",
  "monorepo": fasle,
-  "sourceRoot": "src"
+  "root": "apps/fast-gateway",
+  "compilerOptions": {
+    "webpack": true,
+    "tsConfigPath": "apps/fast-gateway/tsconfig.app.json"
+  },
+  "projects": {
+    "fast-gateway": {
+      "type": "application",
+      "root": "apps/fast-gateway",
+      "entryFile": "main",
+      "sourceRoot": "apps/fast-gateway/src",
+      "compilerOptions": {
+        "tsConfigPath": "apps/fast-gateway/tsconfig.app.json"
+      }
+    },
+    "materials": {
+      "type": "application",
+      "root": "apps/materials",
+      "entryFile": "main",
+      "sourceRoot": "apps/materials/src",
+      "compilerOptions": {
+        "tsConfigPath": "apps/materials/tsconfig.app.json"
+      }
+    },
+    "user-center": {
+      "type": "application",
+      "root": "apps/user-center",
+      "entryFile": "main",
+      "sourceRoot": "apps/user-center/src",
+      "compilerOptions": {
+        "tsConfigPath": "apps/user-center/tsconfig.app.json"
+      }
+    }
+  }
}

默认情况下,启动了 monorepo 模式就会默认打开 webpack 的配置项,但如果不想自己导入实体类或者其他静态路径的话,可以设置为 false

与之前我们项目中使用的 nest-cli.json 配置不同的,多了 monorepocompilerOptionsprojects 等参数,它们是之前介绍过的在 NestJS 中使用 monorepo 模式开发的必备参数,但这些已经有 CLI 帮我们创建好了,对于规范化的工程来说,CLI 能做的事情还是非常多的。

接着修改启动脚本,由于我们默认的项目是 fast-gateway,所以直接使用 nest start:dev 启动的就是 fast-gateway 的项目,其他的启动脚本修改如下:

json
// package.json
"start:gateway": "cross-env RUNNING_ENV=dev nest start --watch",
"start:user": "cross-env RUNNING_ENV=dev nest start --watch user-center",
"start:materials": "cross-env RUNNING_ENV=dev nest start --watch materials",

其中 user-centermaterials 分别对应启动配置文件中的子项目,如果填错的话,则会默认启动主项目。

由于之前我们使用了别名配置,所以要修改对应的 tsconfig.app.json 的配置才能正常启动项目:

json
// apps/fast-gateway/tsconfig.app.json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "paths": {
      "@/*": [
        "src/*"
      ],
      "types/*": [
        "../../types/*"
      ],
    },
    "baseUrl": ".",
    "outDir": "../../dist/apps/fast-gateway"
  },
  "include": [
    "src/**/*",
    "../../types/*.d.ts"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "test",
    "**/*spec.ts"
  ]
}

主要是修改了别名路径跟全局依赖这些配置,再修改完 tsconfig 配置之后,基本上不需要改动代码,即可正常启动项目。

接下来,我们分别运行 yarn start:gatewayyarn start:user,即可看到两个项目已经可以分别运行起来了。

image.png

公共模块 library

任何适合复用的功能都可以作为库来管理,也就是提取重复的模块,然后在每个项目中直接引用,如果需要修改的话,则只需要库的代码即可。

NestJSmonorepos 的模式下,提供了 library 的配置,可以让项目以轻量级的方式来使用这些公共的模块,而在 multirepo 的模式下,大部分则是采用 npm 包的方式来处理公共模块。

在之前的项目开发中,我们有一个 comm 的文件夹来处理公共的逻辑部分,之前良好的编码规范此时就派上了用场,接下来可以使用 library 来讲 comm 中的模块进行封装。

第一步:创建 comm library

shell
$ nest g library common

image.png

第二步:将 fast-gateway 工程中 comm 的模块全部移植到 lib 中,并在 index.ts 中导出

image.png

第三步:修改工程中的引用路径

image.png

如果怕修改不彻底的话,可以直接删除 comm 目录,然后根据控制台错误修改对应的引用路径即可。

注意:comm 中的 database 模块也被复制了,移动了路径所以要记得修改加载实体路径,否则启动的时候并不会报错,但运行的时候会报找不到实体类,因为我们的三个项目的数据库都是共用的,所以这一块的代码也被我抽取出来使用。所以 database 模块的抽取需要根据自己的实际情况来使用。

拆解业务

再完成了之前所有的步骤之后,就可以开始拆分具体的业务代码了,与 comm 转成 library 一样,公共的代码我们也是按照目录来划分的,所以拆解业务的过程也会非常的顺利(一点都不顺利,改引用路径改的快死了)

image.png

为了快速的分割项目,有些路径我使用了相对路径,有兴趣的同学可以将引用路径优化的更好一点。

如果有同学不习惯使用 monorepo 的开发方式,而是 multirepo 来管理项目,那么拆分的过程相对来说会比较顺利,路径问题应该比较容易解决。

如果想使用 multirepo 来管理项目的话,则需要使用 nest build common 命令将 library 打包之后上传到私有或者公有源以 npm 包的方式引入即可,但要注意这种方式引入之后数据库的实体类引用路径可能也需要修改。

image.png

写在最后

本章的示例代码在 feat/monorepo,后续会进行持续的迭代,有需要的同学自取。

本章主要介绍了将一个完成的工程拆分为多个项目的过程,借助 NestJs CLI 提供的 monorepolibrary 的功能,总体拆分的过程还是非常的顺利,基本上只需要修改简单的引用路径与 tsconfig 的别名即可。

在项目拆分之后,除去公共模块的引用之外,每个系统的功能都保持了最单一的模块,但系统之间有些服务还是需要相互关联:比如用户系统需要提供给物料、网关系统登录、鉴权的功能、物料系统需要提供给网关资源消费的数据,此时就需要使用到微服务来将我们各个系统之间的功能进行打通。

所以在下一章节,我们将一起学习如何使用微服务将各个系统之间的服务关联起来。

如果你有什么疑问,欢迎在评论区提出或者加群沟通。 👏

上次更新: