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

106. Case Study: Sharing Protobuf Files Between Go and C# Projects

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

106. Case Study: Sharing Protobuf Files Between Go and C# Projects

In the modern software era, interoperability between services has become absolutely crucial. One popular solution for cross-platform data exchange is Protobuf (Protocol Buffers). In this article, I’ll walk you through a concrete case study: sharing a Protobuf file between a Go backend project and a C#-based client application. We’ll cover the challenges, the solution, real code examples, and even a simulation and code flow illustrated with mermaid diagrams.


Background

Imagine the following scenario: you work on a backend team that develops the main service using Go (golang). Meanwhile, the client team builds a cross-platform desktop application based on C#. Both sides need to communicate using data that is structured, efficient, and type-safe. JSON is certainly flexible, but its serialization overhead is high and its type safety is limited. This is where Protobuf takes the stage.

Protobuf, the data serialization format created by Google, offers:

  • Lightweight data transmission (binary and compressible)
  • Backward compatibility via schema versioning
  • Type safety across many languages, including Go and C#
  • Solid tooling: code generators, validators, and so on.

However, aligning the Protobuf schema and the toolchain between two ecosystems isn’t always as simple as a copy-paste. Here’s the case study.


The Case: Sharing a Protobuf File Between Go & C#

Business Requirements

  1. The backend team (Go) builds a gRPC service.
  2. The client team (C#) consumes that gRPC and Protobuf service.
  3. Every schema change must stay in sync across both platforms.
  4. Both Go and C# developers must get typed code from the same .proto file.

The .proto File: A Simple Design

Suppose we have a user.proto schema:

protobuf
 1syntax = "proto3";
 2
 3package user;
 4
 5// A simple user record
 6message User {
 7    int32 id = 1;
 8    string name = 2;
 9    string email = 3;
10}
11
12// Request to fetch a user by ID
13message GetUserRequest {
14    int32 id = 1;
15}
16
17// User data response
18message GetUserResponse {
19    User user = 1;
20}
21
22// Service for users
23service UserService {
24    rpc GetUser(GetUserRequest) returns (GetUserResponse);
25}

The Protobuf File-Sharing Workflow

Here’s the ideal flow for sharing a .proto schema between Go and C#:

MERMAID
graph TD
    A[Developer Merancang user.proto] --> B[Commit ke Repo Protobuf]
    B --> C[CI Build: Generate Kode Go]
    B --> D[CI Build: Generate Kode C#]
    C --> E[Backend Go Menggunakan hasil generate]
    D --> F[Client C# Menggunakan hasil generate]

Notes:

  • Protobuf Repo: A dedicated repo (or submodule) so the .proto file is always the single source of truth (SSOT).
  • CI Build: Automated code generation via a pipeline (GitHub Actions or another CI).

Practical Steps: Integrating Protobuf Across Go & C#

Let’s look at the real process, along with the tooling involved.

1. Repo Structure for Collaboration

text
1/proto-schema/
2  └── user.proto
3/backend-go/
4  └── <Go code>
5/client-csharp/
6  └── <C# code>

Set up proto-schema/ either as a submodule or within a mono-repo. This is the key to a single source so the schema doesn’t drift.


2. Generating Go Code

Install Protoc and the Go Plugins

sh
1# Install the Protobuf Compiler
2brew install protobuf # (macOS)
3apt-get install protobuf-compiler # (Ubuntu)
4
5# Install the Go plugins
6go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
7go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

Generate the Go Files from .proto

sh
1protoc --go_out=./backend-go --go-grpc_out=./backend-go \
2    --proto_path=./proto-schema \
3    ./proto-schema/user.proto

Result: The Go files user.pb.go and user_grpc.pb.go are created in /backend-go/user/.

Snippet: Using It in Go

go
 1import (
 2    "context"
 3    userpb "backend-go/user"
 4)
 5
 6type UserServiceServer struct {
 7    userpb.UnimplementedUserServiceServer
 8}
 9
10func (s *UserServiceServer) GetUser(ctx context.Context, req *userpb.GetUserRequest) (*userpb.GetUserResponse, error) {
11    user := &userpb.User{
12        Id: 42,
13        Name: "Alice",
14        Email: "alice@example.com",
15    }
16    return &userpb.GetUserResponse{User: user}, nil
17}

3. Generating C# Code

Install Protoc + the C# Plugin

Generate the C# Files from .proto

sh
1protoc --csharp_out=./client-csharp \
2       --grpc_out=./client-csharp \
3       --plugin=protoc-gen-grpc=packages/Grpc.Tools/tools/windows_x64/grpc_csharp_plugin.exe \
4       --proto_path=./proto-schema user.proto

Automatic Alternative (in .csproj):

Add this to the project file:

xml
1<ItemGroup>
2  <Protobuf Include="..\proto-schema\user.proto" GrpcServices="Client" />
3</ItemGroup>

Build the project, and the C# files are auto-generated (under obj/).

Snippet: Consuming gRPC in C#

csharp
 1using Grpc.Net.Client;
 2using User;
 3
 4var channel = GrpcChannel.ForAddress("https://localhost:5001");
 5var client = new UserService.UserServiceClient(channel);
 6
 7var req = new GetUserRequest { Id = 42 };
 8var resp = await client.GetUserAsync(req);
 9
10Console.WriteLine($"User: {resp.User.Name}, {resp.User.Email}");

Simulation: Schema Changes and Consistency

Let’s simulate a schema change and the propagation of that change to both platforms.

1. Adding a Field to User

protobuf
1message User {
2    int32 id = 1;
3    string name = 2;
4    string email = 3;
5    string phone = 4; // New field!
6}

Change Simulation Steps:

StepBackend GoClient C#
Update .protoPull the proto-schema repoPull the proto-schema repo
Regenerateprotoc –go_out …Build or protoc …
Use the fielduser.Phone in GoUser.Phone in C#

Result: Both sides immediately get user.Phone in a type-safe way.


Challenges and Tips

1. Compatibility:

  • Follow the Protobuf rules (field numbers must not be changed; only add/remove optional fields).
  • Avoid breaking changes (renaming a field only affects the code, not the wire format).

2. Tooling/Automation:

  • Create a CI script/pipeline to generate and, if needed, publish the library.
  • Pay attention to import paths between .proto files when there is more than one file.

3. Testing:

  • Build integration tests that round-trip serialization/deserialization.
  • Validate the service end-to-end (Go server ↔️ C# client).

Conclusion

Sharing a Protobuf schema between Go and C# projects is not merely a matter of generating files; it’s about the discipline of a single source of truth, automation, and best practices. The case study above has proven that, with the right tooling, you can manage a shared schema safely, without copy-paste, and without losing type safety.

I hope this article makes your next step easier as you build cross-platform applications, with Protobuf serving as a reliable bridge!


References:


Happy coding, and feel free to share your experiences or questions in the comments section!

Related Articles

💬 Comments