想使用typeorm-seeding做数据库默认数据导入,没想到遇到了对等依赖问题

使用Seeding遇到对等依赖... 我当时傻了

问题的出现有时候涉及的东西是多样的。这篇我主要聊一聊通过seeding的使用导致我遇到的对等依赖问题。但是我依然会花费篇幅去讲涉及到的其他知识,即使可能会让这篇文章重点偏离。我会通过一些特殊描述来引导理解这个问题主要看哪些东西。

主要我在处理问题的时候,如果时间不紧张我也会采取发散思维的方式,慢一点去解决。因为发散的东西总会是我不太清楚的知识和问题,这是有助于我成长的。

一、前置场景(问题场景)

使用Midway.js开发接口,使用了TypeORM作为服务和数据库的中介进行数据库的读取操作。遇到的需求是:怎么在服务启动的时候可以判断数据库表中有没有默认的数据,如果没有进行补充默认数据。

我的解决方案:①使用seeding。②自己写sql或者在服务启动的某个位置执行脚本来给数据库添加。

最终采取的事第二种方案,原因是第一种没有走通。没走通的原因就是遇到了对等依赖的问题,并且在解决后还是没怎么用明白。因为任务需要及时交付,我就采取了我想到的第二种方法。

二、介绍一下涉及的名词和问题(问题主要看第2点)

1)Seeding,这里要说的是一个typeorm的扩展插件

Seeding 官方解释参考:What is it? | typeorm-extension (tada5hi.net)

**官方解释:**创建一些相同的数据在你的数据库中。Create some sample data for your database

**用我的话说:**正如这个单词的含义,这就是个种子。我们可以在程序里种好种子,程序启动时它就仿佛春风吹过,万物复苏,然后生长出一些东西。在我的这个服务中,它主要是要做在服务启动的时候默认给数据库中添加一些默认的数据。就比如需要有默认的管理账户、默认的展示信息,这些信息是为了帮助用户快速了解我们的功能模块。就好比一些笔记工具像语雀、notion它们都会给你默认的描述文档,这些文档就可以通过seeding默认创建好模板,只要有新用户就给用户通过模板给用户创建一些 Readme 文件什么的。

这么一段废话只是为了让知识偷偷摸摸溜进我的脑子。进不进你的🤔... 我可就管不着了~

有小伙伴就要问了,干嘛那么麻烦,我写好一些SQL不就好了吗?

No,没那么简单。我们可以通过sql默认插入没错,但是①我们编写这个插入信息的文档费时费力,而且需要调试;②我采用了 typeorm 进行数据库操作,我就是为了不写 sql,用 typeorm 很方便;③我了解到很多数据库都有seed的功能支持,并且问了ChatGPT,也告知我使用这个方案是安全且容易的。(我当时看着确实觉得很容易,看这个文章:Seeding | typeorm-extension (tada5hi.net),主要内容我放下面了,主要意思就是可以通过4~5个步骤就能实现默认数据插入,而且很好理解。Entity 本来就要,那我只要关心则呢么配置和写seed就好了)

△ 2)对等依赖(peer dependencies)

我把运行时遇到的几个情况截图放在下面,先看表现:(都是对等依赖)

1、使用 pnpm 添加依赖报的警告

2、使用 npm 添加依赖直接报错

看到这里,或许有的小伙伴就懂了什么是对等依赖。

**"对等依赖"**是指两个或多个组件、系统或实体之间的相互依赖关系。在计算机科学和软件工程中,对等依赖通常指的是系统中的组件或模块之间的相互依赖,这些依赖关系是相互平等的,即彼此之间没有明显的层次结构或控制关系。

对等依赖的特点包括:

  1. 相互依赖:各个组件或模块之间相互依赖,彼此之间需要进行通信、交互或共享资源。
  2. 平等性:对等依赖之间没有主次之分或者层次结构,每个依赖都对另一个依赖具有同等的重要性。
  3. 自主性:每个依赖都有自己的控制权和责任,它们可以独立地运行和进行决策。
  4. 互相影响:一个依赖的变化可能会影响到其他依赖,因此需要谨慎地管理和维护对等依赖关系。

对等依赖在分布式系统、网络通信、软件架构等领域中经常出现,例如对等网络(Peer-to-Peer Network)中的节点之间的相互依赖关系,以及微服务架构中的各个微服务之间的对等依赖关系。有效地管理和设计对等依赖关系对于确保系统的稳定性、可靠性和可扩展性至关重要。

**说人话就是------依赖的库中存在耦合、冲突。**这些冲突可能是版本不同的同一个组件,也有可能是两者的依赖会互相影响。

3)幻影依赖(顺便提一下)

这个涉及到了npm的扁平化。体现就是:我们导入了A包,但由于A包依赖于B包,且通过npm的扁平化处理,让我们可以在项目中直接使用B包内的方法和其他内容而无需在package.json中声明。这种依赖仿佛就是一个影子在悄悄伴随。

可能有的小伙伴又要问了,这不挺好吗?如果确定要用我这不是还省了空间了,不用多下一个包?

想的挺好,但是它不可控啊。假如我们使用的包升级了,或者因为A包本身做了修改导致B包不存在,那我们就陷入了被动的状况了。

来看下更为官方的定义:

**"幻影依赖"**是指在软件开发中的一种现象,通常发生在依赖库或框架版本升级时。当你升级了一个依赖项的主要版本,但是项目中的其他部分仍然使用着旧版本的功能或接口时,就会出现幻影依赖。

这种情况下,尽管你已经更新了依赖库的版本,但在项目中仍然存在对旧版本功能的依赖。这些对旧版本功能的依赖被称为"幻影依赖",因为它们在代码中没有显式声明,但却影响着项目的行为。

幻影依赖可能会导致一些问题,例如:

  1. 功能不一致:项目中的不同部分可能会使用不同版本的功能或接口,导致功能不一致或不可预测的行为。
  2. 性能问题:旧版本的功能可能效率低下或存在性能问题,但由于幻影依赖,项目无法充分利用新版本的改进。
  3. 维护困难:在识别和处理幻影依赖时,可能需要花费大量时间和精力,特别是在大型项目中。

为了避免幻影依赖,开发者应该在升级依赖库的版本时,全面地审查项目中的代码,并确保所有部分都适用于新版本的功能和接口。同时,建议使用自动化测试来验证项目在依赖库版本升级后的行为是否符合预期。

幻影依赖的问题出现在npm对依赖的扁平化处理过程上,所以如果我们采用的是npm做包管理器几乎是难以避免的。要规避它给我们带来麻烦,据我所知有两种方案:(欢迎做补充)

1、避免使用幻影依赖,如果需要使用该库,多执行一下npm install 也无伤大雅。

2、使用 pnpm **做项目的包管理器。**pnpm 除了下包很快、省空间以外,另一个比较好的功能就是解决了幻影依赖的问题。

三、怎么解决?(针对前言场景我可以怎么解决?)

针对前面说的场景中提到的需求,虽然我最终采取的是第二种方式,但是第二种方式的做法灵感却是来源于 Seeding。所以看一下是有价值的。

那我将分析我的解决思路,如果赶时间可以直接跳到后面的总结。

1、运行原理分析。

不管是seeding还是使用sql,其本质还是在服务启动的时候执行一个脚本来增加。其区别在于使用了不同的方式。

在seeding的使用中,会采用类似下面的代码:

typescript 复制代码
// src/seeds/InitialDataSeed.ts

import { Connection } from 'typeorm';
import { Factory, Seeder } from 'typeorm-seeding';
import { User } from '../entities/User';

export default class InitialDataSeed implements Seeder {
  public async run(factory: Factory, connection: Connection): Promise<void> {
    // 检查是否已存在默认数据
    const existingUsersCount = await connection.getRepository(User).count();

    if (existingUsersCount === 0) {
      // 如果不存在默认数据,则插入
      await connection.getRepository(User).insert([
        { username: 'admin', password: 'admin123' },
        { username: 'user', password: 'user123' },
      ]);
    }
  }
}

本质上就是通过获取到的connection来得到Repository对象,Repository对象就是可以执行find、delete、insert等数据库增删改查甚至高阶的数据库查询的对象。上述的代码就是利用了这个对象新增了一些数据到数据库中。然后只要在适当的地方创建 InitialDataSeed 的实例,并且调用 run方法即可。

2、问题所在。

如果按照网络上的教程,结合项目本来也可以使用这种方式实现。但是问题就在于typeorm的使用出现了对等依赖的情况,导致运行会出问题。把上面的图我再拿下来

问题就在于 typeorm我使用的是0.3.20,而在typeorm-seeding最新的1.6.1版本中依赖的还是typeorm的0.2.24版本。

3、解决

知道问题原因了就好办了。因为typeorm-seeding依赖的typeorm对于我的项目而言是一个幻影依赖,这是不可控的,我可控的只有本身依赖的0.3.20版本。于是所有的解决方案围绕可控的typeorm来:

(1)降低版本到和幻影依赖的版本一样。

(2)采取不同版本共存项目中的策略,只需要给0.3.20版本加上别名,并且后续的导入只要需要用最新的typeorm的地方就用设置的别名就好。

(1)降低版本

在 package.json 中修改

json 复制代码
- "typeorm": "^0.3.20"
+ "typeorm": "^0.2.24"

执行依赖更新

shell 复制代码
npm install 
# or
pnpm install

(2)采取别名

别名的方式我知道的有两种,一种是通过修改package.json,另一种是chartGPT推荐的使用typescript路径映射。前者我比较推荐,因为简单,后者我没试,但也不失为一个办法的储备,相对第一种更为麻烦一点但是也很优雅。

第一种:

修改 package.json

json 复制代码
- "typeorm": "^0.3.20"
+ "typeorm_new": "npm:typeorm@0.3.20" // 名称各位可以随意

后续涉及到的地方就可以使用新的名称替换旧的

第二种:

  1. 安装两个不同版本的 TypeORM:首先,在项目中安装两个不同版本的 TypeORM。
bash 复制代码
npm install typeorm@version1
npm install typeorm@version2
  1. 配置 TypeScript 路径映射 :在 TypeScript 配置文件(tsconfig.json)中添加路径映射,将不同版本的 TypeORM 映射到不同的导入名称。例如:
json 复制代码
{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "typeorm-v1": ["node_modules/typeorm-version1"],
      "typeorm-v2": ["node_modules/typeorm-version2"]
    }
  }
}
  1. 使用别名导入 TypeORM:在代码中,使用别名来导入不同版本的 TypeORM。例如:
typescript 复制代码
import { createConnection as createConnectionV1 } from 'typeorm-v1';
import { createConnection as createConnectionV2 } from 'typeorm-v2';

四、总结

解决对等依赖这篇文章主要提供了两种解决方案:

1、依赖统一。对应上文就是把版本统一为低的两者都能使用的,这样会比较简单

2、采用别名,使依赖共存且不会受影响。成本也不大。

别名的使用,例如:

bash 复制代码
# package.json
"typeorm_new": "npm:typeorm@0.3.20" 
相关推荐
想用offer打牌5 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
KYGALYX7 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
爬山算法7 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端