Rust协议缓冲区工具protofetch的使用,高效管理Protobuf依赖与版本控制

Rust协议缓冲区工具protofetch的使用,高效管理Protobuf依赖与版本控制

动机

如果你广泛使用protobuf作为服务间通信的数据格式或与外界共享API,你需要一种方法来获取每个服务的正确版本的protobuf文件,并能够依赖特定版本。这在服务器和客户端都需要。没有自动化,这很快就会变得繁琐、容易出错且难以管理。

为了使这一过程可承受、可用且稳定,需要自动化工具使这项工作可预测。这就是Protofetch的目标。

为什么选择Protofetch?

Protofetch旨在以声明式方式处理protobuf依赖的复杂性。它使得声明依赖和管理依赖变得非常简单。

它提供以下能力:

  • 依赖特定版本/哈希
  • 依赖protobuf的可预测构建/测试/CI
  • 易于阅读的protobuf依赖声明规范
  • 自动获取依赖及其传递依赖
  • 依赖缓存,可在多个项目间共享

入门指南

你可以下载预构建的二进制文件。

Protofetch也发布在crates.io上,所以如果你已经安装了Rust工具链,可以通过cargo install protofetch从源码构建。

使用方式

# 获取proto源文件,如果需要更新lock文件
protofetch fetch
   
# 验证lock文件并获取proto源文件。适用于CI
protofetch fetch --locked

Protofetch模块

每个使用protofetch的服务都需要一个使用toml格式的模块描述符。默认情况下,这个描述符称为protofetch.toml,位于服务仓库的根目录。

依赖格式

以下是一个protofetch依赖的toml示例:

name = "repository name"
description = "this is a repository"

[dep1]
url = "github.com/org/dep1"
protocol = "https"
revision = "1.3.0"
prune = true
allow_policies = ["/prefix/*", "*/subpath/*", "/path/to/file.proto"]

[dep2]
url = "github.com/org/dep2"
protocol = "ssh"
branch = "feature/v2"

[another-name]
protocol = "ssh"
url = "github.com/org/dep3"
revision = "a16f097eab6e64f2b711fd4b977e610791376223"
transitive = true

Git协议支持

Protofetch支持使用sshhttps访问Git仓库。默认情况下,Protofetch使用ssh。你可以通过环境变量配置默认Git协议。

SSH支持

你需要运行SSH代理并加载你的SSH密钥:

ssh-add ~/.ssh/your-private-key

HTTPS支持

如果你想使用https,需要配置git使用凭证助手。

传递依赖支持和修剪

Protofetch支持拉取传递依赖以方便使用。然而,如果依赖没有定义自己的protofetch模块,则需要一些手动工作。

以下示例展示了如何定义带有传递依赖的模块:

name = "repository name"
description = "this is a repository"
proto_out_dir = "proto/src/dir/output"

[A]
protocol = "https"
url = "github.com/org/A"
revision = "1.3.0"
allow_policies = ["/proto/path/example.proto"]
prune = true

[B]
protocol = "ssh"
url = "github.com/org/B"
revision = "5.2.0"
transitive = true

完整示例

下面是一个完整的Rust项目使用protofetch的示例:

  1. 首先安装protofetch:
cargo install protofetch
  1. 在项目根目录创建protofetch.toml文件:
name = "my-service"
description = "My gRPC service"

[user-api]
url = "github.com/myorg/user-api"
protocol = "https"
revision = "v1.2.0"
prune = true
allow_policies = ["/user/*.proto"]

[product-api]
url = "github.com/myorg/product-api"
protocol = "ssh"
revision = "a1b2c3d4e5f6"
transitive = true
  1. 运行protofetch获取依赖:
protofetch fetch
  1. 在Rust项目中使用protobuf文件:
// 生成Rust代码的build.rs
fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::configure()
        .build_server(true)
        .compile(
            &["proto/user/v1/user_service.proto"],  // 你的proto文件路径
            &["proto"],  // proto文件所在目录
        )?;
    Ok(())
}
  1. 在你的Cargo.toml中添加tonic依赖:
[dependencies]
tonic = "0.8"
prost = "0.11"

[build-dependencies]
tonic-build = "0.8"

这个示例展示了如何:

  1. 定义protobuf依赖
  2. 获取特定版本的proto文件
  3. 在Rust项目中使用这些proto文件生成gRPC客户端/服务器代码
  4. 管理proto文件的版本控制和依赖关系

完整示例demo

以下是一个更完整的实际项目示例,展示如何使用protofetch管理protobuf依赖:

  1. 首先安装protofetch和必要的Rust工具链:
# 安装protofetch
cargo install protofetch

# 安装protoc编译器 (根据你的系统)
# 例如在Ubuntu上:
sudo apt-get install protobuf-compiler
  1. 创建项目目录结构:
my-grpc-service/
├── protofetch.toml
├── build.rs
├── Cargo.toml
└── src/
    └── main.rs
  1. 配置protofetch.toml文件:
name = "my-grpc-service"
description = "A gRPC service using protofetch"

# 定义用户服务API依赖
[user-service]
url = "github.com/myorg/user-service-protos"
protocol = "https"
revision = "v2.1.0"
prune = true
allow_policies = ["/user/v1/*.proto"]

# 定义订单服务API依赖
[order-service]
url = "github.com/myorg/order-service-protos"
protocol = "ssh"
revision = "3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f"
transitive = true
  1. 配置build.rs构建脚本:
// build.rs - 生成gRPC代码
fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 配置tonic-build生成代码
    tonic_build::configure()
        .build_server(true)
        .build_client(true)
        .out_dir("src/generated") // 指定生成代码的输出目录
        .compile(
            &[
                "proto/user/v1/user_service.proto",  // 用户服务proto文件
                "proto/order/v1/order_service.proto" // 订单服务proto文件
            ],
            &["proto"],  // proto文件搜索路径
        )?;
    
    // 重新编译时重新运行build.rs
    println!("cargo:rerun-if-changed=proto");
    Ok(())
}
  1. 配置Cargo.toml依赖:
[package]
name = "my-grpc-service"
version = "0.1.0"
edition = "2021"

[dependencies]
tonic = "0.8"
prost = "0.11"
tokio = { version = "1.0", features = ["full"] }
futures = "0.3"

# 生成的gRPC代码会放在src/generated目录下
# 这里需要包含生成的模块
user-service = { path = "src/generated/user/v1" }
order-service = { path = "src/generated/order/v1" }

[build-dependencies]
tonic-build = "0.8"
  1. 在main.rs中使用生成的gRPC代码:
// src/main.rs
mod generated {
    pub mod user {
        pub mod v1 {
            tonic::include_proto!("user.v1");
        }
    }
    pub mod order {
        pub mod v1 {
            tonic::include_proto!("order.v1");
        }
    }
}

use generated::{user::v1::*, order::v1::*};
use tonic::{Request, Response, Status};

// 实现gRPC服务
#[derive(Default)]
pub struct MyUserService;

#[tonic::async_trait]
impl user_service_server::UserService for MyUserService {
    async fn get_user(
        &self,
        request: Request<GetUserRequest>,
    ) -> Result<Response<GetUserResponse>, Status> {
        // 实现获取用户逻辑
        todo!()
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 创建并启动gRPC服务器
    let addr = "[::1]:50051".parse()?;
    let user_service = MyUserService::default();
    
    tonic::transport::Server::builder()
        .add_service(user_service_server::UserServiceServer::new(user_service))
        .serve(addr)
        .await?;

    Ok(())
}

这个完整示例展示了:

  1. 使用protofetch管理多个protobuf依赖
  2. 自动下载指定版本的proto文件
  3. 使用tonic-build生成Rust gRPC代码
  4. 实现gRPC服务端
  5. 完整的项目结构和配置

通过这种方式,你可以轻松管理protobuf依赖的版本,确保服务间通信的API一致性,并简化开发流程。


1 回复

Rust协议缓冲区工具protofetch的使用指南

什么是protofetch?

protofetch是一个用于管理Protobuf依赖关系和版本控制的Rust工具,它简化了Protobuf文件的管理流程,特别适合在大型项目或多个服务间共享Protobuf定义时使用。

主要特性

  • 声明式依赖管理
  • 版本锁定支持
  • 多仓库支持
  • 与Rust构建系统集成良好

安装方法

cargo install protofetch

基本使用方法

1. 初始化protofetch配置

在项目根目录创建Protofetch.toml文件:

[config]
protobuf_dir = "proto"  # Protobuf文件存放目录

2. 添加依赖

[[dependencies]]
name = "googleapis"
target = "github.com/googleapis/googleapis"
version = "d26ed3f"  # 可以是commit hash、tag或分支名

3. 下载依赖

protofetch sync

高级用法

版本锁定

生成锁定文件:

protofetch lock

多仓库配置

[[dependencies]]
name = "service_a"
target = "github.com/yourorg/service_a/proto"
version = "v1.2.0"

[[dependencies]]
name = "service_b"
target = "github.com/yourorg/service_b/proto"
version = "a1b2c3d"

与Rust项目集成

build.rs示例:

fn main() {
    // 确保protofetch依赖已同步
    if !std::path::Path::new("proto").exists() {
        std::process::Command::new("protofetch")
            .arg("sync")
            .status()
            .expect("Failed to run protofetch sync");
    }

    // 使用tonic-build编译Protobuf
    tonic_build::configure()
        .compile(&["proto/your_service.proto"], &["proto"])
        .unwrap();
}

完整示例

  1. 创建项目结构:
my_project/
├── Protofetch.toml
├── build.rs
├── Cargo.toml
└── src/
    └── main.rs
  1. Protofetch.toml内容:
[config]
protobuf_dir = "protos"

[[dependencies]]
name = "googleapis"
target = "github.com/googleapis/googleapis"
version = "d26ed3f"

[[dependencies]]
name = "user_service"
target = "github.com/yourorg/user-service/proto"
version = "v2.1.3"
  1. build.rs内容:
fn main() {
    // 同步protobuf依赖
    if !std::path::Path::new("protos").exists() {
        std::process::Command::new("protofetch")
            .arg("sync")
            .status()
            .expect("Failed to sync protobuf dependencies");
    }

    // 编译proto文件
    tonic_build::configure()
        .compile(
            &[
                "protos/google/calendar/v3/calendar.proto",
                "protos/user/v1/user.proto"
            ],
            &["protos"]
        )
        .unwrap();
}
  1. src/main.rs示例:
mod protos {
    pub mod google {
        pub mod calendar {
            pub mod v3 {
                tonic::include_proto!("google.calendar.v3");
            }
        }
    }
    pub mod user {
        pub mod v1 {
            tonic::include_proto!("user.v1");
        }
    }
}

use protos::{google::calendar::v3::*, user::v1::*};

fn main() {
    // 使用生成的protobuf代码
    let event = CalendarEvent {
        id: "123".to_string(),
        title: "Meeting".to_string(),
        // ...其他字段
    };

    let user = User {
        id: "user1".to_string(),
        name: "Alice".to_string(),
        // ...其他字段
    };

    println!("Event: {:?}, User: {:?}", event, user);
}

最佳实践

  1. Protofetch.lock提交到版本控制中
  2. 为Protobuf依赖使用语义化版本标签
  3. 定期运行protofetch update检查更新
  4. 在CI流程中加入protofetch check验证依赖一致性
回到顶部