Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
25 Sep 2025 · 5 min read ·Article 109 / 110
Go

109. Case Study: Managing Protobuf Dependencies with Buf for Cross-platform

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

title: “109. Case Study: Managing Protobuf Dependencies with Buf for Cross-platform” published: true author: Senior Software Engineer tags:

  • protobuf
  • buf
  • cross-platform
  • dependency management
  • grpc

Dependency management for Protocol Buffers (protobuf) is often a classic problem when developing systems made up of many services or different platforms; whether those are microservices, frontend-backend setups, or even cross-language scenarios such as Go, Java, Python, and JavaScript. In this article, I will walk through a case study of managing Protobuf dependencies using Buf, a modern tool that is increasingly becoming the new standard, while also sharing the shape of the problem, the technical solution, and best practices you can apply if you find yourself in a similar landscape.


Background of the Problem

Let’s start with a fairly familiar picture:

  • There are many repos (monorepo or multi-repo) that use .proto files to define APIs (gRPC, REST).
  • Every project and team wants the freedom to pick its own language: Go, Node.js, Python, Java, and so on.
  • Copy-pasting proto files becomes routine; versioning is often manual and easy to miss.
  • You want clean code, automation, and good scaling without breaking changes.

The fundamental problems:

  1. Stale dependencies: Custom proto files are often outdated across repos.
  2. Conflicting versions: Duplicate proto definitions with different versions.
  3. Difficulty in updating: Hard to update the schema without the risk of incompatibility.
  4. Multi-language generation: Stub code from proto must be generated for many languages, and dependencies must stay consistent.

Why Buf?

Buf is a toolkit for linting, building, breaking change analysis, artifact generation, and – most importantly – dependency management plus a module registry for Protobuf.

Key benefits of Buf:

  • Version and distribute Protobuf schemas through a registry.
  • Manage proto package dependencies the same way npm does for JavaScript.
  • Easy to enforce rules and linting.
  • Reproducible builds across many languages.

Case Study: Multi-platform Payment System

Assume we have a payment system like the following:

  • A payment service (Go)
  • A notification service (Node.js)
  • Mobile apps (Flutter/Dart)
  • A web frontend (React/TypeScript)

All of them consume the transaction.proto and user.proto schemas.


The Classic Problem

Without dependency management, the workflow usually looks like this:

┌──────────────────┐
│ payment-service  │
└───────┬──────────┘
        │
┌───────▼──────────┐
│  notification    │
└───────┬──────────┘
        │
┌───────▼───────────────┐
│ mobile & web clients  │
└───────────────────────┘

Each team copies the .proto files into their own repos. In the end, every repo can have a different version of the proto file, with uncontrolled differences.


The Architectural Solution with Buf

Let’s look at an architecture diagram of proto dependency management orchestrated by Buf:

MERMAID
flowchart TD
    subgraph Buf Registry
        A[transaction/v1] 
        B[user/v1]
    end
    subgraph Services & Client
        S1[payment-service (Go)]
        S2[notification (Node.js)]
        S3[mobile-app (Dart)]
        S4[web-app (TypeScript)]
    end
    S1-->|import@buf.build/org/transaction|A
    S2-->|import@buf.build/org/transaction|A
    S1-->|import@buf.build/org/user|B
    S3-->|import@buf.build/org/transaction|A
    S4-->|import@buf.build/org/transaction|A
    S2-->|import@buf.build/org/user|B
    S4-->|import@buf.build/org/user|B

Now, all services and client apps pull their dependencies from the Buf Registry. No copy-pasting, no invisible divergence.


Implementation Using Buf

1. Protobuf Module Directory Structure

For example, suppose we have a dedicated repo for proto schemas:

text
1proto/
2  ├── buf.yaml
3  ├── buf.lock
4  └── transaction/
5      └── v1/
6          └── transaction.proto

transaction/v1/transaction.proto

proto
 1syntax = "proto3";
 2
 3package transaction.v1;
 4
 5message Transaction {
 6    string id = 1;
 7    string user_id = 2;
 8    double amount = 3;
 9    string status = 4;
10}

2. Module Configuration in Buf

buf.yaml

yaml
1version: v1
2name: buf.build/yourorg/transaction
3lint:
4  use:
5    - DEFAULT
6breaking:
7  use:
8    - FILE

buf.lock will be generated automatically.

3. Publishing to the Buf Registry

Buf provides a CLI command to publish a module:

bash
1$ buf push
Now, buf.build/yourorg/transaction can be imported anywhere.


4. Consuming Proto via Buf in a Service

For example, inside the Go payment microservice repo:

buf.yaml

yaml
1version: v1
2deps:
3  - buf.build/yourorg/transaction

Buf.lock acts like a generator similar to node_modules in NPM:
it locks dependencies so versions stay consistent.


5. Generate Code for Any Language

With Buf, multi-language generation is cleaner than with vanilla protobuf.

Example of generating Go code:

bash
1$ buf generate --template buf.gen.yaml

buf.gen.yaml

yaml
1version: v1
2plugins:
3  - plugin: go
4    out: gen/go
5  - plugin: go-grpc
6    out: gen/go

For Node.js, Dart, or TypeScript, you only need to add the relevant plugin.


Simulating a Dependency Update

Suppose we want to update the Transaction schema by adding a created_at field. The update workflow:

  1. Update transaction.proto
  2. Push to the Buf Registry (buf push)
  3. Update the dependencies in each service (buf mod update)
  4. Regenerate the code stubs (buf generate)

All consumers pull the latest version with buf mod update.


Comparison Table: Buf vs Manual Copy

FeatureManual CopyBuf Registry
VersioningManualAutomatic
Dependency LockNoneYes (buf.lock)
Lint & Breaking CheckManualBuilt-in
Multi-language GeneratorComplicatedSimple
Up-to-date DependenciesOften WrongConsistent
CI/CD IntegrationComplicatedEasy

Best Practices & Lessons Learned

  • One source of truth: There is only one Protobuf repo published to the Buf Registry.
  • Avoid breaking changes: Buf’s breaking check is incredibly helpful for backward compatibility.
  • Automate in CI/CD: Linting, checking, and regeneration run automatically on every proto commit.
  • Buf lock (buf.lock): Dependencies that are pinned and reproducible.
  • Documented registry versioning: Every team knows which version and semver its dependency pulls from.

Conclusion

The experience of managing cross-platform Protobuf dependencies is easier, more scalable, and far less error-prone with Buf Module & Registry than with copy-paste techniques or other custom versioning solutions. With repeatable infrastructure as code, an engineering team can save time, reduce human error, and collaborate more effectively across languages and platforms.

Buf is not merely a linter; it is a new way of thinking about Protobuf schema and dependency management—and from my experience, the upfront investment in Buf will save you a great deal of debugging and refactoring time in the long run.


References

If you have a unique experience managing Protobuf dependencies, share it in the comments!

Related Articles

💬 Comments