You can normally install protobuf and protoc packages in your host OS to generate protocol buffer files. However, the problem with this solution is that, every engineer has to go through installation before start using the commands. Given the OS can be Linux, Windows or Mac, there will definitely be version differences after creating protocol buffer files as well as other problems. This example standardises the way how everything is done. All you have to do is, just run the docker command which will create all files for you. Also you won't install anything to your OS apart from Docker. Pay attention to Dockerfile and docker-compose.yaml files. The *.pb.go will be added/updated after running make proto-compile command. In your application, all you have run is go get -u google.golang.org/grpc command.


Structure


├── Makefile
├── cmd
│   ├── client
│   │   └── main.go
│   └── server
│   └── main.go
├── docker
│   ├── Dockerfile
│   └── docker-compose.yaml
├── go.mod
├── go.sum
└── protobuf
├── league.pb.go
├── league.proto
├── team.pb.go
└── team.proto

Files


docker-compose.yaml


services:
protogen:
build:
context: "."
args:
PLATFORM: ${PLATFORM}
working_dir: "/source"
volumes:
- "../protobuf:/source"
command: bash -c "protoc --go_out=plugins=grpc:. --go_opt=paths=source_relative *.proto"

Dockerfile


FROM golang:1.18.2-buster

ARG PLATFORM

RUN apt-get update && apt-get install -y unzip

# By default Intel chipset (x86_64) is assumed but if the host device is an Apple
# silicon (arm) chipset based then a relevant (aarch_64) release file is used.

RUN export ZIP=x86_64 && \
if [ ${PLATFORM} = "arm64" ]; then export ZIP=aarch_64; fi && \
wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v3.15.8/protoc-3.15.8-linux-${ZIP}.zip && \
unzip -o protoc-3.15.8-linux-${ZIP}.zip -d /usr/local bin/protoc && \
unzip -o protoc-3.15.8-linux-${ZIP}.zip -d /usr/local 'include/*' && \
go install github.com/golang/protobuf/protoc-gen-go@v1.5.2

league.proto


syntax = "proto3";

package football;

option go_package = "github.com/you/football/protobuf;footballpb";

message CreateLeagueRequest {
string name = 1;
}

message CreateLeagueResponse {
string id = 1;
}

service LeagueService {
rpc CreateLeague(CreateLeagueRequest) returns (CreateLeagueResponse) {}
}

team.proto


syntax = "proto3";

package football;

option go_package = "github.com/you/football/protobuf;footballpb";

message CreateTeamRequest {
string name = 1;
}

message CreateTeamResponse {
string id = 1;
}

service TeamService {
rpc CreateTeam(CreateTeamRequest) returns (CreateTeamResponse) {}
}

Makefile


.PHONY: help
help: ## Display available commands.
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

.PHONY: server-run
server-run: ## Run server.
go run --race cmd/server/main.go

.PHONY: client-run
client-run: ## Run client.
go run --race cmd/client/main.go

.PHONY: proto-compile
proto-compile: ## Compile protobuf files.
PLATFORM=$(shell uname -m) docker-compose -f docker/docker-compose.yaml run --rm protogen

.PHONY: docker-config
docker-config: ## Dump docker-compose configuration.
PLATFORM=$(shell uname -m) docker-compose -f docker/docker-compose.yaml config

.PHONY: docker-down
docker-down: ## Clean up docker artefacts.
PLATFORM=$(shell uname -m) docker-compose -f docker/docker-compose.yaml down
@-docker rmi docker_protogen
docker system prune --volumes --force

client/main.go


package main

import (
"context"
"log"
"time"

"google.golang.org/grpc"

footballpb "github.com/you/football/protobuf"
)

func main() {
// Initialise TCP connection.
con, err := grpc.Dial(":50051", grpc.WithInsecure())
if err != nil {
log.Fatalln(err)
}
defer con.Close()

// Initialise league client.
clt := footballpb.NewLeagueServiceClient(con)

// Create league client request.
req := &footballpb.CreateLeagueRequest{Name: "Super League"}

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

// Send create league request.
res, err := clt.CreateLeague(ctx, req)
if err != nil {
log.Fatalln(err)
}

log.Println(res.GetId())
}

server/main.go


package main

import (
"context"
"log"
"net"

"google.golang.org/grpc"

footballpb "github.com/you/football/protobuf"
)

func main() {
// Initialise TCP listener.
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatal(err)
}
defer lis.Close()

// Initialise GRPC server.
grpcSrv := grpc.NewServer()

footballpb.RegisterLeagueServiceServer(grpcSrv, &LeagueServer{})

log.Fatalln(grpcSrv.Serve(lis))
}

type LeagueServer struct{}

func (l *LeagueServer) CreateLeague(ctx context.Context, req *footballpb.CreateLeagueRequest) (*footballpb.CreateLeagueResponse, error) {
log.Println(req.GetName())

return &footballpb.CreateLeagueResponse{Id: "ID"}, nil
}