Learning GoLang, gRPC, Protobuf

From my experience working at Punch. Read docs, youtube, GPT explanations.

Learning Golang, gRPC, Protobuf. I may take occasional detours as a part of understanding things ‘properly’.

Fundamentals

Nuances in Go:

Phase 1

Phase 2

File structure in Go: Assume:

hello-go/
├── go.mod
├── main.go
└── mathutils/
    └── add.go

Create go.mod using: go mod init hello-go. Note for production related projects, consider naming convention like: github.com/XYZCompanyOrName/project

go.mod can be imagined as: requirements.txt + project identity + version lock.

Keeping module name same as folder name is best practice, else when you import other packages, you’ll have to do an explicit handling.

Files:

mathutils/add.go:
package mathutils
// Add is a public function
func Add(a int, b int) int {
    return a + b
}
// Note: Package name mathutils would be same across all files in that folder. Note that main.go, is a special package, since it's entrypoint file.  

main.go:
package main
import (
    "fmt"               // standard library
    "hello-go/mathutils" // local package
)
func main() {
    sum := mathutils.Add(3, 4)
    fmt.Println("Sum:", sum)
}
// Note: Import path = (module name initialized) + (folder or relative path from go.mod to the package directory).

Run program using: go run .

To install third party packages: go get github.com/google/uuid

When you do so, a go.sum file is created (you don’t edit this). It stores checksums, ensures integrity; Can be imagined as pip-lock/poetry.lock file in Python. Version selection happens via go.mod (what versions). Integrity is enforced via go.sum (prove this code hasn’t changed).

Key Go Directory Naming Conventions:

- cmd/: Contains entry points for executable binaries. Each subdirectory (cmd/app1, cmd/app2) acts as a main package, allowing a single repository to generate multiple binaries.
- internal/: Contains code intended only for this project, enforced by the Go compiler. Code in internal/ cannot be imported by other projects, making it ideal for encapsulated application logic.
- pkg/: Contains library code designed to be consumed by external applications or other projects, serving as a shared library.
- api/: Houses API definitions such as Swagger/OpenAPI specs, JSON schemas, or Protocol Buffers.
- configs/: Stores configuration files or default configuration templates.
- web/: Holds front-end components, such as static assets, HTML templates, or CSS/JS files.
- scripts/: Contains build, installation, analysis, or administrative scripts.
- testdata/: Stores data files required for tests; Go tools automatically ignore this directory during building.
- vendor/: Contains application dependencies. Although becoming less common with Go modules, it's still a standard directory name for vendored code.
- test/ (or tests/): Used for system or integration tests, rather than unit tests which usually reside alongside the code. 

Essential Go commands:

go run .                 # run main package
go build                 # build binary
go build ./cmd/consumer  # build specific binary
go get github.com/google/uuid   # add dependency
go mod tidy                    # clean unused deps (VERY important)
go list -m all                 # list modules
go fmt ./...       # auto-format code
go vet ./...       # static analysis
go test ./...      # run all tests

Phase 3

Go - Memory model

Phase 4

Go - Error handling

Phase 5

Go - Concurrency

Phase 6

Miscellaneous stuff in Go

Phase 7

Extras - gRPC, Protobuf

1) The Mental Model Shift — REST vs gRPC

2) What is Protobuf? (It’s Actually Three Things)

3) What is gRPC and What Does the Name Mean?

4) Why gRPC Uses Protobuf Instead of JSON

5) The .proto File — The Contract

6) Code Generation — The Magic Step

7) The Server — No net/http, No Manual Routes

8) The Client — Calling Remote Functions Like Local Ones

9) Where Does the HTTP Request Actually Go?

10) Both Sides Must Speak gRPC

11) Adding New Endpoints

Step 1 — Update the proto file.

service Greeter {
  rpc SayHello   (HelloRequest)   returns (HelloReply)   {}
  rpc SayGoodbye (GoodbyeRequest) returns (GoodbyeReply) {}  // New endpoint — just one line
}
// Add the new message types
message GoodbyeRequest {
  string name = 1;
}
message GoodbyeReply {
  string message = 1;
}

Step 2 — Regenerate the code by re-running protoc. The generated stub and servicer now automatically include SayGoodbye on both the client side and the server side.

Step 3 — Implement the method on the server. In strongly-typed languages like Go, the compiler will actually refuse to compile until you implement every method declared in the proto service. This prevents you from accidentally shipping a server with unimplemented endpoints — a guarantee REST has no equivalent for.

class GreeterServicer(greeter_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        return greeter_pb2.HelloReply(message=f"Hello, {request.name}!")
    # Must implement this now — gRPC framework will raise errors if you don't
    def SayGoodbye(self, request, context):
        return greeter_pb2.GoodbyeReply(message=f"Goodbye, {request.name}!")

Step 4 — The client gets the updated .proto file, regenerates, and can immediately call stub.SayGoodbye(...). There is no API documentation to update, no Postman collection to edit, no URL to communicate to other teams. The proto file is all of that.

This is a significant developer experience win over REST, where adding a new endpoint means updating route handlers, updating documentation, updating any shared Postman collections, and manually communicating the change to all consumers.

12) Error Handling in gRPC

13) Testing gRPC — The curl Equivalent

14) The Full Picture — End to End Flow

1. You write greeter.proto
         │
         ▼
2. Run protoc compiler
         │
         ├──► greeter_pb2.py          (message classes: HelloRequest, HelloReply, etc.)
         └──► greeter_pb2_grpc.py     (GreeterStub for client, GreeterServicer for server)
                      │
         ┌────────────┴────────────┐
         │                         │
      CLIENT                    SERVER
      imports Stub               imports Servicer
      calls stub.SayHello()      implements SayHello() with business logic
         │                         │
         │   HTTP/2 POST           │
         │   /Greeter/SayHello     │
         │   [binary protobuf] ───►│
         │                         │ deserializes binary → HelloRequest object
         │                         │ runs your SayHello() method
         │                         │ serializes HelloReply → binary
         │◄─── [binary response] ──┘
         │
      deserializes binary → HelloReply object
      response.message is available as a normal Python string

The key insight is that the binary serialization, HTTP/2 transport, routing, and deserialization are all handled invisibly by the generated code and the gRPC framework. You write the schema, you write the business logic, and the framework handles everything in between.

15) Quick Reference Comparison — REST vs gRPC

Concern REST gRPC
API style Resource-oriented (/users/123) Procedure/function-oriented (SayHello)
Transport Usually HTTP/1.1 HTTP/2 by default
Data format Usually JSON (text) Usually Protobuf (binary)
Schema required Optional Strongly expected (.proto file)
Client setup Any HTTP client Generated client/stub typically used
Routes/URLs Manually designed Auto-derived from proto service/method
Code generation Optional Core part of workflow via protoc
Streaming support Not native (usually WebSockets/SSE) Built in (server/client/bidirectional)
Adding an endpoint New route + handler + docs New rpc line + regenerate code
Testing curl, browser, Postman grpcurl, Postman gRPC, test client
Type safety Mostly runtime validation Compile-time contract enforcement
Performance Good Typically lower latency and smaller payloads
Browser support Native Requires gRPC-Web or transcoding
Human readability Human-readable payloads Binary payloads not human-readable
Best for Public APIs, browser/mobile clients Internal microservices, streaming, high-throughput systems

16) When to use gRPC, and when not to