ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

mongoose 跨项目共享 Schema 完整实践:peerDependencies、导出 Schema 与 POJO 迁移方案

mongoose 跨项目共享 Schema 完整实践:peerDependencies、导出 Schema 与 POJO 迁移方案 mongoose 跨项目共享 Schema 完整实践peerDependencies、导出 Schema 与 POJO 迁移方案【免费下载链接】mongooseMongoDB object modeling designed to work in an asynchronous environment.项目地址: https://gitcode.com/GitHub_Trending/mo/mongoose大型组织里经常存在一个独立 npm 包专门存放多个项目共用的 Mongoose schema例如initech/shared-schemas。本文以当前 mongoose 仓库MongoDB object modeling library的官方指南 docs/shared-schemas.md 为核心系统讲解共享 schema 包的三大最佳实践把 mongoose 声明为peerDependencies、只导出 Schema 而不导出 Model、以及针对旧共享库的 POJO 迁移方案同时结合仓库源码lib/mongoose.js、lib/schema.js、lib/connection.js说明这些实践背后的实现原理帮助读者在企业级多项目架构中安全、可升级地共享 Mongoose 数据结构定义。典型场景客户端项目 共享 schema 库先看一个典型的依赖关系。假设公司内部有一个私有 npm 包initech/shared-schemas在客户端项目initech/web-app1中执行npm list输出如下initech/web-app11.0.0 ├── initech/shared-schemas1.0.0 ├── mongoose8.0.1其中initech/web-app1是客户端项目client project真正连接 MongoDB、执行查询与写入的业务应用initech/shared-schemas是共享库shared library只负责提供可复用的 schema 定义本身不连接数据库。这种一个包负责定义、多个包负责使用的结构是共享 schema 的最基本形态。下面三个实践决定了这个结构能否长期健康运转。实践一把 Mongoose 放进 peerDependencies而不是 dependencies最重要的一条initech/shared-schemas必须在package.json中把 mongoose 声明在peerDependencies中而不是顶层dependencies。推荐的package.json如下{ name: initech/shared-schemas, peerDependencies: { mongoose: 8.x } }官方指南给出了这样做的两条核心理由更容易升级。假设initech/shared-schemas依赖 Mongoose 8initech/web-app1使用 Mongoose 8 没有问题但initech/web-app2暂时无法从 Mongoose 7 升级。peerDependencies 把用哪个版本的 Mongoose的决定权交还给依赖共享库的项目各项目可以自行选择版本互不冲突。降低 Mongoose 模块重复duplicate的风险。用 Mongoose A 版本的 schema 和 model 去搭配 Mongoose B 版本是不被支持的行为。这条建议在官方 docs/faq.md 中也有呼应如果你把 schemas 或 models 放在独立的 npm 包中请在你的独立包中把 Mongoose 放进peerDependencies而不是dependencies。从源码看为什么同一应用里出现两个 Mongoose 模块会出问题Mongoose 的模型注册是有状态的全局缓存。看 lib/mongoose.js 中Mongoose.prototype.model()的实现可以清楚地看到mongoose.model(User, schema)会把编译好的模型写入_mongoose.models[name]与_mongoose.connection.models[name]两处缓存当name已存在且传入的 schema 与缓存中模型的 schema 不是同一个实例时会抛出OverwriteModelError对应 lib/error/overwriteModel.js——这正是相同模型名在多个 Mongoose 实例/版本间冲突的典型表现模型一旦创建内部持有的是某个特定 Mongoose 实例的 Schema 与内部数据结构。因此如果共享库的dependencies里装了一份 Mongoose客户端项目又装了另一份node_modules 里就会出现两个 Mongoose 模块。共享库导出的 schema/model 由它的那份编译客户端由自己的那份编译二者互不认账就会出现OverwriteModelError、类型判断失败例如schema instanceof mongoose.Schema为false等诡异问题。peerDependencies 能保证整个应用只存在一份 Mongoose从根源上消除这种重复。实践二导出 Schema而不是导出 Model第二项建议initech/shared-schemas应导出 MongooseSchema而不是Model。官方示例// userSchema.js in initech/shared-schemas const userSchema new mongoose.Schema({ name: String }); // 推荐做法导出 schema module.exports userSchema; // 不推荐做法导出 model // module.exports mongoose.model(User, userSchema);这么做的原因有两层更灵活。客户端项目可以用自己偏好的模式实例化模型默认连接、自定义连接、连接工厂等共享库不需要替客户端做决定。导出 model 无法跨连接迁移。mongoose.model()注册的模型是绑定在Mongoose 默认连接default connection上的。一旦initech/shared-schemas内部用mongoose.model()注册了模型客户端没有任何办法把这个模型转移到另一条连接例如mongoose.createConnection()创建的多租户连接。源码佐证模型与连接的绑定关系从 lib/mongoose.js 可以看到mongoose.model()创建模型后同时写入_mongoose.models[name]和_mongoose.connection.models[name]即默认连接的模型缓存而从 lib/connection.js 的Connection.prototype.model()可以看出每条连接都维护着自己独立的模型注册表。这也是官方 docs/connections.md 中多连接下要导出 schema 而非 modelexport schema pattern的同一个原理导出 model 的模式export model pattern受限因为一个模型只能对应一条连接。客户端两种标准的模型实例化方式导出 schema 后客户端需要自己把 schema 变成 model官方在 docs/connections.md 中给出了两种常见模式。方式 A连接工厂最灵活——每次调用创建一个新连接并注册全部模型const mongoose require(mongoose); module.exports function connectionFactory() { const conn mongoose.createConnection(process.env.MONGODB_URI); conn.model(User, require(../schemas/user)); conn.model(PageView, require(../schemas/pageView)); return conn; };方式 B导出连接——在文件顶层注册模型后导出连接对象业务代码按需require()// connections/index.js const mongoose require(mongoose); const conn mongoose.createConnection(process.env.MONGODB_URI); conn.model(User, require(../schemas/user)); module.exports conn;如果你的应用同时有 Web API 后端和移动端后端还可以像官方建议的那样拆成connections/web.js、connections/mobile.js各自管理连接。无论是哪种方式前提都是共享包里导出的是 schema——这正是实践二的价值所在。实践三变通方案共享库导出 POJO而不是 Schema 或 Model有些历史遗留的共享库并不遵循上述最佳实践它们可能把某个旧版本 Mongoose 写进了自己的dependencies甚至直接导出 model。此时推荐一个实用的变通方案让共享库导出 POJOPlain Old JavaScript Object而不是 schema 或 model。POJO 不携带任何 Mongoose 内部结构因此可以彻底消除共享库的 Mongoose 版本与客户端项目的 Mongoose 版本之间的冲突。共享库侧// 替换这个 module.exports new mongoose.Schema({ name: String }); // 改成这个 module.exports { name: String };客户端侧// 替换这个 const { userSchema } require(initech/shared-schemas); // 改成这个 const { userSchemaDefinition } require(initech/shared-schemas); const userSchema new mongoose.Schema(userSchemaDefinition);注意示例中的命名变化共享库导出的是 schema 的定义definition客户端拿到定义后用客户端自己的 Mongoose现场构造 Schema。这样共享库里即便还残留着旧版 Mongoose也不会再参与 schema 的编译过程。源码佐证POJO 是 Schema 与 model 的合法输入这一方案在实现层面完全成立因为 Mongoose 的模型编译入口和 Schema 构造器都原生接受 POJOlib/mongoose.js 中mongoose.model()对第二个参数做了处理if (utils.isObject(schema) !(schema instanceof Schema)) { schema new Schema(schema); }即传入普通对象时自动包装成 Schema只有既不是 Schema 也不是普通对象时才抛错。lib/schema.js 的Schema构造函数直接把obj可以是 plain object存为this.obj后续的路径解析this.paths、this.tree、this.nested等都围绕它展开lib/schema.js 也明确校验输入必须是 POJO 或SchemaTypeOptions。也就是说POJO 定义 →new mongoose.Schema(def)→mongoose.model(User, schema)是一条完全受支持的编译链路客户端可以放心使用。端到端模板一个可落地的共享 schema 包把三个实践串起来一个完整的共享 schema 包结构如下initech/shared-schemas/ ├── package.json # mongoose 放在 peerDependencies ├── index.js # 聚合导出所有 schema / schemaDefinition └── schemas/ ├── user.js # module.exports new Schema({...}) 或导出 POJO └── pageView.jspackage.json关键片段{ name: initech/shared-schemas, version: 1.0.0, main: index.js, peerDependencies: { mongoose: 8.x } }客户端项目接入默认连接 多租户连接两个例子// 客户端使用默认连接 const mongoose require(mongoose); const userSchema require(initech/shared-schemas/schemas/user); mongoose.connect(process.env.MONGODB_URI); const User mongoose.model(User, userSchema); // 客户端使用独立连接多租户/多数据库 const conn mongoose.createConnection(process.env.OTHER_MONGODB_URI); const User conn.model(User, userSchema);若共享库是历史遗留、无法改为导出 Schema 的旧包则退回到实践三的 POJO 方案共享库导出{ name: String }这类纯定义客户端new mongoose.Schema(userSchemaDefinition)后再建模型。升级与排错要点升级共享库由于 mongoose 在peerDependencies中升级共享库本身通常不需要同步升级客户端项目的 Mongoose反之客户端要升级 Mongoose 也只需改自己的dependencies两者解耦。警惕 OverwriteModelError若客户端里对同一个模型名用不同 schema 重复调用mongoose.model()会触发 lib/error/overwriteModel.js 中的OverwriteModelError判断逻辑见 lib/mongoose.js。共享多项目时建议在客户端内统一 schema 来源避免从共享包和本地各取一份定义注册同名模型。不要混用版本仓库文档与 FAQdocs/faq.md反复强调——用一份 Mongoose 编译的 schema/model 搭配另一份 Mongoose 使用是不被支持的。出现instanceof 判断失败模型方法异常时优先检查 node_modules 里是否存在多个 mongoose 副本。多连接场景当项目使用多个连接时务必遵循实践二导出 schema否则模型将被锁死在默认连接上详见 docs/connections.md。总结在 mongoose 的多项目共享场景中三条准则缺一不可peerDependencies 控制版本单一性避免模块重复只导出 Schema 保住模型与连接的解耦与灵活性历史遗留包用 POJO 方案平滑过渡。从源码看mongoose.model()的全局缓存机制lib/mongoose.js、模型与连接的绑定关系lib/connection.js以及 Schema 对 POJO 的原生支持lib/schema.js共同印证了这些实践的必要性与可行性。按照本文方案落地即可让共享 schema 包在多个客户端项目间长期稳定、可升级地复用。【免费下载链接】mongooseMongoDB object modeling designed to work in an asynchronous environment.项目地址: https://gitcode.com/GitHub_Trending/mo/mongoose创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表