依赖管理学习

依赖管理&构建工具应该是什么样子的? github.com/vikyd/note/... todo: 生产环境中如何使用容器进行依赖的控制

nodejs的版本控制www.jianshu.com/p/bca5e4ce7...

语义化版本

  • 只在必要的时候触发重新构建
  • 不轻易的更新依赖的版本, 即便具备了更新的版本
  • 多次重复构建应该得出相同的构建结果
  • 构建系统的细节应该不需要用户参与(也就是对用户而言是黑盒),如果不是专门学习, 我们知道gcc编译过程中原来隐藏了预处理器, 编译器, 连接器等细节吗?
  • 构建的内容应该可以被分发, 也就是集成版本仓库, 可以从远端上传/拉构建库
  • 能够在拉取依赖库的时候进行版本控制: go get时代是做不到的

术语

1. 语义版本(semver)

这个术语类似git tag, 不同之处在于, go提倡

  • 如果依赖库跨一个大版本, 最好用额外的tag标识, 比如xxx.com/module/v2
  • 同时,项目的路径中也要能反映出这种语义, 这种就是一种"预先约束", 该约束可以方便管理工具在拉取代码的时候完成依赖控制
java 复制代码
|-- module
|-- src // v0, v1
|-- v2 // v2 单独启一个路径, 下面放很可能不兼容v1的大修之后的代码(如果指定拉取v2版本,就会拉到这里)
    |-- src

发展历程

总结下,go vendor其实就是为每个项目弄一个本地的依赖库,保证可复现的构建

go get无法满足可复现, 原因在于: go get每次都会从远程仓库拉最新的版本, 如果版本偷偷升级了, 其实构建出的就是依赖新版本的产物。

解决办法: snapshot, 基本哪里都有snapshot的身影, 原理就是把远程版本制作为本地依赖包, 然后设置为依赖本地的该包, 任由remote如何更新版本, 都不会影响本地(此时为远程的某个old version的快照)

但这个其实违背了 构建系统的细节应该不需要用户参与 这个原则, 用户切实的参与到了版本固定的过程...

所以下一步就是如何以用户无感的方式完成这种构建, 道理还是基于snapshot, 区别在于原本用户来做, 现在工具来做.

但是推进这个可复现构建的自动化的过程毕竟也是摸着石头过河, 由于没有明确的规范, 很多社区实现上定义了很多metadata, 包括在项目中使用项目特有的go vendor目录以及元数据。这些都不被go的官方识别。此时就是很混乱的阶段。

所以下一步就是官方出场, 定义规范,整合好用的社区版,杜绝群雄混战的问题。

就算是官方,也没法一次性直接把依赖管理从社区混战直接切换到最终稳定的工具,官方也需要推进一些临时性的工具,这些工具需要满足

  • 灵活性和用户定制性的取舍: 官方需要考虑是设计一款makefile这样细节比较难以掌握, 但是构建过程可以让用户最大化定制这样的产品, 还是尽可能简洁, 用户不需要什么额外的学习成本的产品
  • 集成工具链: 官方需要设计一款go toolchain里的一个命令, 直接在安装go toolkit的时候就能使用到这个依赖管理功能

官方的这个实践也称 go dep,但实际上最终的产品形态不是这个, 而是另一个项目vgo, go dep是依赖管理发展中的一个过渡.


提案

  1. 此时依赖管理朝着模块化发展, 每个模块都有自己的一套依赖库, 并且自己也可以作为一个模块分发出去给别人当sdk用
  2. import的兼容性: 如果新版本import xxx, 旧版本也import xxx(新旧版本相同moduleName), 那么新版本应该向下兼容
  3. 最小版本选择

import兼容性:

根据之前的规定(很多情况都是"官方没有明说, 但大家都遵守"), 如果一个依赖不跨大版本, 不管他有多少个小的feature version, 还是多少个bugfix version, import的形式都是一样的

go 复制代码
// e.g
// 对于这个import, some_module的版本可能是一个大版本下的任何版本
// 比如v1.1.1 v1.2.3 v1.11.4 v.1.114514.1919810
import example.com/some_module

当然,这个import兼容性其实主要是对 库的开发者来说的,如果我们在开发一个库,应该保证,如果我们要升级版本v1.1.0 到 v1.2.0,必须保证v1.2.0兼容v1.1.0

这个兼容性适用于什么case呢?

less 复制代码
A dep B 
A dep C

B dep D 1.1 // B使用了 import D
C dep D 1.2 // C使用了 import D

此时依赖管理工具可以放心的用D的1.1版本, 原因在于: 对于C而言,它使用的D1.2向下兼容D1.1

对于上面的论述,好像少了很多东西

  1. 我们如何保证D1.2就得兼容D1.1
  2. 为什么都是import D, B就依赖的是1.1版本, C就依赖的是1.2 版本

实际上,这就是一种"约定大于xx"的理念, 如果我们是D的设计者,我们当然可以让D1.2不去兼容D1.1,那么可能日后的某一天,A这个项目的设计人员就会找到我们,说"你的设计不合理,导致我的项目出错"。所以这种约定其实是为了不给别人添加额外处理依赖的成本。

那如果D的新版本的确就不兼容旧版本呢? 此时如果都import D肯定不行, 此时就利用到了semver(语义版本)

less 复制代码
A dep B 
A dep C

B dep D 2.0 // B使用了 import D/v2 鲁迅
C dep D 1.2 // C使用了 import D 周树人(假设鲁迅和周树人不兼容)

此时依赖管理工具看到了,就可以认为: B依赖的是v2的D,而C依赖的是D,抓鲁迅和周树人没关系, 各自维护自己的依赖


但是还有一个问题,上面的约定还是比较严格的,哪怕一个小的版本升级,也可能带来不兼容性,比如D1.2也不兼容D1.1。

此时,与其把烂摊子留给用户来做, 比如在依赖管理工具上下点文章: 尽量延迟依赖的更新。不升级就不会出事,非必要不升级。而且如果真升出问题了,可能会在D的作者对应的仓库里存在对应的issue,D的作者也可能进行回滚,这样尽量减少了受影响面。

实现这一点的方法是: 最小版本选择

迁移到go mod后

  • 用户之前的各种杂七杂八的依赖管理可以被go mod工具管理起来
  • go mod中只需要少量的语法,降低用户的学习成本,其中很多都是"约定提前约束好"的内容,降低了灵活性,但是减少了学习成本。

todo: 日后更新

相关推荐
夜月行者1 小时前
如何使用ssm实现基于SSM的宠物服务平台的设计与实现+vue
java·后端·ssm
Yvemil71 小时前
RabbitMQ 入门到精通指南
开发语言·后端·ruby
sdg_advance1 小时前
Spring Cloud之OpenFeign的具体实践
后端·spring cloud·openfeign
猿java2 小时前
使用 Kafka面临的挑战
java·后端·kafka
碳苯2 小时前
【rCore OS 开源操作系统】Rust 枚举与模式匹配
开发语言·人工智能·后端·rust·操作系统·os
kylinxjd2 小时前
spring boot发送邮件
java·spring boot·后端·发送email邮件
2401_857439695 小时前
Spring Boot新闻推荐系统:用户体验优化
spring boot·后端·ux
进击的女IT6 小时前
SpringBoot上传图片实现本地存储以及实现直接上传阿里云OSS
java·spring boot·后端
一 乐7 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
艾伦~耶格尔10 小时前
Spring Boot 三层架构开发模式入门
java·spring boot·后端·架构·三层架构