Go和Rust中的workspace

go workspace 使用

已经有一段时间没有和大家分享关于Go语言的内容了。今天,我想向大家介绍Go语言在1.18版本中推出的一个新功能——Workspace。

Workspace的推出,极大地方便了我们在编写涉及多模块项目时的工作流程。以往,在处理一个大型项目时,我们可能会将项目分割成多个模块,例如一个通用模块(common)和一个业务模块(service)。如果service模块需要使用common模块中的某些方法,Go语言在1.18版本之前虽然提供了解决方案,但引入Workspace概念后,本地依赖管理变得更加便捷。

下面,我将演示如何使用Go Workspace。假设我们有一个名为common的模块,我们首先初始化这个模块的go mod文件。在common模块中,我们定义了一个简单的加法函数,这个函数可以被其他模块调用。接下来,我们还需要创建一个名为service的模块,同样通过go mod init进行初始化。

在Go 1.18之前,我们需要通过go mod tidy命令拉取依赖,但由于common模块是本地的,这时我们需要使用replace指令将远程仓库替换为本地路径。这样,service模块的go.mod文件中就会添加一行replace命令,从而能够解析并使用common模块。

然而,引入了Workspace之后,我们可以更方便地管理本地模块依赖。我们可以删除之前的go.mod文件修改,然后在项目的根目录下使用go work init初始化一个go.work文件。通过go work use命令,我们可以将需要的模块加入到Workspace中。这样,我们就能在不使用replace指令的情况下,直接解析并使用本地模块。

在IDE中,可能会出现无法解析模块的情况。这时,我们可以通过将所有模块同时加入到go.work文件中来解决这个问题,使IDE能够正常解析模块。

通过Workspace的使用,我们的开发过程变得更加灵活方便,特别是在处理多模块项目时。不过,在日常开发中,我们需要注意不要将go.work文件推送到远程仓库,并确保在进行任何更改推送前,要么删除go.work文件,要么将其添加到.gitignore中。这是因为go.work文件中使用的是本地路径,其他开发者可能并没有将模块放在相同的目录结构下。

这就是Go Workspace的简单介绍。希望对大家有所帮助,感谢大家的收听。


【go小知识】go1.18新功能workspace,非常有用和实用


Workspace的引入背景


Go Workspace和泛型,模糊测试一样, 也是Go语言在1.18版本中引入的一个新特性,目的是为了解决在开发涉及多个模块(module)的Go项目时的依赖管理问题。

在引入Workspace之前,当项目被拆分成多个模块进行管理时,依赖于本地模块的管理可能会变得复杂。特别是在本地开发环境中,开发者需要频繁地使用replace指令在go.mod文件中手动指定模块的本地路径,以便在不发布到远程仓库的情况下引用本地模块。这种方法虽然可行,但对于涉及多个模块的大型项目来说,维护成本较高,而且容易出错。

即对于较大的项目,一般会抽离出一个或多个公用组件(往往叫做go-base,go-common). 当开发这个公共组件库时,不太方便push到远程仓库,再通过go get等拉取到本地(因为多人共用这个库), 之前的办法是在go.mod中增加一个或多个replace块.

而Workspace的引入旨在简化和优化这一流程,使得开发者能够更方便地在本地环境中管理和编译涉及多个模块的项目,而无需修改go.mod文件或使用多个replace指令。


之前用replace方式


假设项目抽离出了两个模块:commonserviceservice模块对外提供服务, 其依赖于common模块。在没有Workspace功能之前,如果common模块在本地进行了修改,service模块需要通过replace指令在其go.mod文件中指定common模块的本地路径来引用这些更改,从而进行调试.

1
2
3
4
5
6
7
8
9
10
11
12
13
mkdir workspace-demo
cd workspace-demo

mkdir common
cd common
code utils.go
go mod init dashen.tech/common

mkdir service
cd service
code main.go
go mod init dashen.tech/service
code go.mod

其中utils.go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package utils

import "math/rand"

func GenerateOrderID() string {
return "order-" + randomString(10)
}

func randomString(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = byte(65 + rand.Intn(26)) // A = 65, Z = 90
}
return string(b)
}

修改service/go.mod为:

1
2
3
4
5
6
7
module dashen.tech/service

go 1.21rc2

require dashen.tech/common v0.0.0-incompatible // 引入这个包

replace dashen.tech/common => ../common // 将此包指向本地目录的路径

replace的作用很多,可以

  • 替换无法下载的包
  • 调试依赖包(或引用本地包)
  • 使用fork的仓库
  • 禁止被依赖

此处仅用到了引用本地包的功能

更多可参考:


main.go:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"

utils "dashen.tech/common"
)

func main() {

fmt.Println(utils.GenerateOrderID())

}

执行go run main.go,输出:


IDE可能会有错误提示,忽略即可


使用workspace方式


使用Go Workspace方式也非常简单,主要有以下几个步骤:

  1. 初始化Workspace: 在项目的根目录下,使用go work init命令初始化一个新的Workspace。这将创建一个go.work文件。

  2. 添加模块到Workspace: 通过go work use命令,可以将一个或多个模块的路径添加到Workspace中。这样做可以告诉Go工具链在构建和测试时考虑这些模块。

  3. 构建和测试: 在Workspace环境中,可以像平常一样使用go buildgo test等命令,Go工具链会自动解析Workspace中的模块依赖关系。


清空service/go.mod中的require和replace信息

1
2
3
4
5

cd workspace-demo
go work init
go work use ./service
go work use ./common

此时会新生成一个go.work文件,内容为如下:


无需修改main.go, 直接执行go run main.go, 可输出预期结果


不仅仅是go run, 在项目目录下运行go buildgo test命令时,Go工具链也会自动识别这两个模块之间的依赖关系,通过Go Workspace这种方式,一定程度上简化了涉及多个模块的项目的本地开发和测试流程,提高了开发效率。

(我感觉只是比之前replace的方式优雅了一点点,效率其实大差不差)

另外,go.work需要加入到.gitignore中,不要提交到仓库.






Rust中Workspace的使用


对于较大型项目,随着功能的不断增加,规模的不断扩大,将面临如何组织项目的问题。在这种情况下,可以使用 Cargo workspace来组织和管理项目。

workspace可以用于管理多个依赖包,它允许在一个单独的项目中构建、测试和共享多个包。这一机制特别适用于大型项目,或者当想将一个项目拆分成多个较小的、可重用的组件时。


引入的背景


workspace可以帮助管理多个相关的包,通过共享同一个Cargo.lock文件和同一个输出目录(target),以及其他配置(比如发布配置)

在Rust出现workspace之前,独立管理多个相关的包可能非常困难。每个包都需要单独配置、编译和测试,这使得跨包的依赖管理和版本控制变得复杂。随着项目规模的增长,这种方法变得越来越不可持续。

为了解决这些问题,Rust引入了workspace概念,目的是简化这些工作,特别是在涉及多个包的场景中。workspace允许开发者在一个共享的环境中工作,其中所有包都可以被统一构建和测试,依赖关系也被智能地管理。



如何使用


要在Rust中使用workspace,需要创建一个顶层的Cargo.toml文件来定义workspace及其成员。

下面是一个简单的例子:

假设想创建一个名为my_workspace的workspace,其中包含两个包:library(一个库包)和app(一个二进制包,依赖于library)。

  1. 创建workspace目录
1
2
mkdir my_workspace
cd my_workspace
  1. 配置workspace的Cargo.toml

my_workspace目录中,创建Cargo.toml

1
2
3
4
5
6
[workspace]

members = [
"library",
"app",
]

  1. 创建包
  • 对于library包:
1
2
3
mkdir library
cd library
cargo init --lib

默认生成的lib.rs中会有如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pub fn add(left: usize, right: usize) -> usize {
left + right
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

  • 对于app包:
1
2
3
4
cd ../ # 回到workspace根目录
mkdir app
cd app
cargo init
  1. 添加依赖

appCargo.toml中添加对library的依赖:

1
2
[dependencies]
library = { path = "../library" }

并且在app/main.rs中,调用一下library中的add函数:

1
2
3
4
5
6
7
8
9
use library;

fn main() {
println!("Hello, world!");

let rs = library::add(1, 6);

println!("最终结果为: {}", rs);
}

  1. 构建和运行

在workspace根目录下运行cargo build,或cargo run -p app来构建或运行特定的包。

(只会有一个全局的target目录,即便在app目录下执行cargo build,也不会在app目录下再生成一个target文件夹)


综上,通过使用workspace,Rust项目可以更轻松地管理多包的构建、测试和依赖关系,特别是对于大型或分解成多个组件的项目而言。




在上一节课中,我们介绍了如何发布一个包到 crates.io,以及与 Cargo 和工作空间相关的内容。在第12章中,我们创建了一个命令行应用程序。随着应用程序的不断扩大,我们面临如何组织项目的问题。在这种情况下,我们可以利用 Cargo 工作空间来组织和管理项目。

工作空间可以帮助我们管理多个相关的包,通过共享同一个Cargo.lock文件和同一个输出目录(target),以及其他配置,比如发布配置。接下来,我将演示如何使用 Cargo 工作空间。我首先切换到我的终端,并假设我要创建一个工作空间,包括名为add_oneadd_two的包。我首先创建一个名为add的目录,然后进入这个目录并打开 VS Code。

add目录中,我创建一个Cargo.toml文件,在这个文件中,我声明它是一个工作空间,并添加一些成员,比如第一个成员叫add。然后,我在工作空间的根目录中创建一个名为add的二进制项目。

创建工作空间后,我可以构建工作空间,cargo build将在工作空间的根目录中构建项目。构建完成后,会在工作空间根目录中生成Cargo.lock文件和一个target目录。工作空间中的子项目可能有相互依赖关系。利用工作空间的构建组织方式,可以减少重新构建和编译的次数。

接着,我继续在工作空间中添加新项目,例如添加一个叫add_one的新成员。创建add_one项目后,我可以在add_one中添加一个函数,然后在add项目中引用add_one。这样,我就可以在add项目中使用add_one提供的功能。

最后,我讲解了如何在工作空间中管理外部依赖。所有子项目使用的依赖版本将保持一致,确保子项目之间的兼容性。如果需要发布工作空间中的包,需要逐个执行cargo publish

此外,我还介绍了cargo install的用法,这是安装可执行二进制包的便捷方式,安装的位置默认在$HOME/.cargo/bin目录下。最后,我提到了如何通过在$HOME/.cargo/bin目录下放置以cargo-为前缀的可执行文件来自定义和扩展 Cargo 命令。

通过这个教学视频,我们学习了如何有效地使用 Cargo 工作空间来组织和管理大型项目,以及如何管理依赖和扩展 Cargo 功能。

波波微课: 第14章~Cargo工作空间(Workspace)

下面是我们在生产Web应用程序中使用Cargo工作区的一种方法。这样做能够让我们将Web应用程序扩展成一个多服务系统。通常,生产应用程序可能作为单个服务启动,但随着时间的推移,它们往往会发展成为一个包含多种服务的复杂系统。这些服务可能由Kubernetes管理,或者采用lambdas或wasm技术。无论采用何种技术,我们都需要规范化行为并最大化代码重用。这正是Cargo工作区发挥作用的地方。

因此,我们的重点将从Cargo工作区开始,首先规划我们的整体架构。我们会有模型存储、上下文和事件处理、辅助工具、RPC(远程过程调用),这些都是独立于网络层的。此外,我们还有Web层,包括与Axel等组件的集成,这将被打包进Web服务器中。正如我们之前讨论的,我们将发展成一个多服务系统,因此我们希望将我们的模块分割成合理的部分。首先,所有的模型存储、上下文和事件将被归类为核心模块。然后,所有的RPC、业务逻辑层也将有自己的包。Web层将成为我们Web服务器服务的一部分。当我们拥有多个Web服务器时,我们还可以创建一个lib web,其中包含一些适用于AOM请求和日志处理的通用Web结构。

从结构上看,我们将所有这些模块放在一个名为“crates”的文件夹下,并在Cargo工作区中组织它们。这将是我们使用的服务的基础。起初我们可能只有一个服务,但最终我们会有多个,比如面向最终用户的Web服务器、DevOps的IT服务器以及一些专门的服务,如人工智能和数据采集服务等。我们还有工具集,可以在开发机器或构建机器上运行,这对于管理任务或相似的操作非常有用。

接下来,让我们深入了解Cargo工作区的配置。自Rust 1.74以来,我们现在可以使用这个工作区链接(workspace Linz)。我们会把不安全的代码放在特定的地方,因此如果你没有15年的C++经验,请避免编写不安全的代码。我个人喜欢在代码中允许未使用的代码段,但有时为了实验性开发,我可能会暂时关闭这些警告。

Cargo工作空间的一个关键优势是它允许我们将代码和依赖项组织得更加清晰和高效。我们需要确保依赖关系正确,没有循环依赖,并且每个包(crate)都有明确的职责。例如,我们的Web层依赖于RPC层,但RPC层和业务逻辑层不应该相互依赖。这样的结构不仅有助于代码的清晰组织,而且还便于未来的扩展和维护。

总之,通过合理利用Cargo工作空间,我们可以有效地组织和扩展我们的Web应用程序,确保代码重用和最佳实践的应用。希望你喜欢这次的分享,任何形式的帮助都是非常宝贵的。感谢支持,祝编码愉快!

Rust 生产级别 Workspace 组织示例


今天我想和大家分享一下在开发过程中使用pnpm的workspace功能来管理monorepo的经验。在开发中,我们常常需要在同一个仓库中管理多个项目,这些项目之间可能存在紧密的依赖关系。为了方便管理,我们会选择将它们放在同一个仓库中,这种做法在业界被称为monorepo。使用pnpm的workspace功能,我们可以更高效地管理这样的项目结构。

首先,我假设大家都有一定的前端开发经验,比如对Node.js、JavaScript以及常见的脚手架有一定了解。今天的分享旨在帮助大家理解如何管理公司产品的代码仓库时可能遇到的问题,并提供解决方案。

接下来,让我简要介绍一下pnpm。pnpm是一个快速且节省磁盘空间的包管理工具,是npm的一个替代品。它通过链接本地存储的包来节省空间,并且提高了安装速度。此外,pnpm对依赖的处理也进行了优化。

关于monorepo,它简单来说就是一个大的代码仓库,包含了多个项目。这种结构便于管理多个互相依赖的项目,共享代码,减少项目管理成本。不过,它也可能带来版本管理混乱、代码质量不一、技术栈升级困难等问题。

使用pnpm的workspace来管理monorepo时,每个项目可以独立管理自己的依赖,同时又能很好地利用workspace提供的依赖共享机制。当你需要在项目间共享代码时,可以轻松地做到,而不需要将共享的代码发布到npm上。

总之,选择是否使用monorepo结构以及如何管理它,取决于你的项目需求和团队偏好。如果你的项目之间存在紧密的依赖关系,或者你希望团队成员能够快速地从项目中学习和共享代码,使用pnpm的workspace功能来管理monorepo可能是一个不错的选择。希望今天的分享能帮助大家更好地理解和使用这些工具,提高开发效率。

使用 pnpm workspace 管理 monorepo