Go Microservices - gRPC, Architecture, and Testing¶
Production Go microservice patterns - gRPC with protobuf, clean architecture layers, dependency injection, testing with mocks, and Docker/Kubernetes deployment.
Key Facts¶
- buf toolchain replaces manual protoc: lint, breaking change detection, multi-plugin code generation
- gRPC Gateway exposes same proto service as both gRPC (50051) and REST/JSON (8081) via reverse proxy
- Clean architecture: three model levels (proto, domain, repository) with converters at boundaries
- Define repository interfaces in the consuming layer (service), not the implementation layer
protoc-gen-validatevalidates fields in .proto files via gRPC interceptors- Go workspaces (
go.work) enable cross-module development without publishing
Patterns¶
gRPC Service Design¶
# buf.gen.yaml - code generation config
plugins:
- plugin: go
out: pkg/proto
- plugin: go-grpc
out: pkg/proto
- plugin: grpc-gateway
out: pkg/proto
- plugin: validate
out: pkg/proto
Interceptor chain:
server := grpc.NewServer(
grpc.ChainUnaryInterceptor(
loggingInterceptor,
recoveryInterceptor,
validationInterceptor,
),
)
Nullable fields: use google.protobuf.wrappers (StringValue, BoolValue, Int32Value) for fields that must distinguish absent vs zero.
Partial updates:
message UpdateRequest {
string uuid = 1;
UpdateInfo update_info = 2;
}
message UpdateInfo {
google.protobuf.StringValue description = 1;
google.protobuf.StringValue color = 2;
}
Clean Architecture Layers¶
cmd/main.go - entry point
internal/
api/ - controllers (HTTP/gRPC handlers)
service/ - use cases, business logic
interfaces.go - repository interfaces defined here
repository/ - data access implementations
model/ - DB-specific models
converter/ - domain <-> repo model conversion
model/ - domain entities
converter/ - proto/API <-> domain model conversion
Dependency direction: api -> service -> repository (dependencies point inward).
Three model levels: - Proto models: versioned API contract, generated code - Domain models: stable business entities, independent of external concerns - Repository models: optimized for DB representation (DB tags, nullable types)
Converters at each boundary allow each layer to change independently.
Dependency Injection¶
// service/interfaces.go
type UserRepository interface {
Get(ctx context.Context, id uuid.UUID) (*model.User, error)
Create(ctx context.Context, user *model.User) (uuid.UUID, error)
}
type UserService struct {
repo UserRepository // depends on interface, not implementation
}
For complex graphs: wire (Google) or dig (Uber). Manual DI in app/di.go is often clearest.
Testing¶
// Table-driven tests
tests := []struct{ name string; input int; want int }{
{"zero", 0, 0},
{"positive", 5, 25},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, square(tt.input))
})
}
// Benchmarks (Go 1.24+)
func BenchmarkMyFunc(b *testing.B) {
for b.Loop() {
myFunc()
}
}
mockery generates mocks from interface definitions. Test API and service layers with mocks; repository layer with integration tests (testcontainers).
Docker Multi-Stage Build¶
FROM golang:1.24 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o app ./cmd/main.go
FROM alpine:3.21
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder /app/app .
USER appuser
CMD ["./app"]
Layer caching: copy go.mod/go.sum first for module download caching.
Go Monorepo with Workspaces¶
go.work enables cross-module development without publishing. buf.work.yaml for shared proto files.
Microservice Standard Stack¶
- HTTP:
net/http(Go 1.22+ routing), chi, or ogen (code-gen from OpenAPI) - gRPC: google.golang.org/grpc + protobuf + buf
- DB PostgreSQL: pgx, goqu SQL builder, goose migrations
- DB MongoDB: official mongo-driver, bson
- Kafka: segmentio/kafka-go or confluent-kafka-go
- Redis: go-redis
- Mocking: mockery (from interfaces)
- Testing: testify, testify/suite
- Linting: golangci-lint
- Build: Taskfile.yml or Makefile
Gotchas¶
- gRPC uses HTTP/2 multiplexing - connection-level load balancers are insufficient; use client-side LB or service mesh
protoc-gen-validatereturns gRPC status code 3 (INVALID_ARGUMENT) for validation failures- Docker
depends_onwithcondition: service_healthywaits for healthcheck, not just container start go test -coverprofile=coverage.out ./...thengo tool cover -html=coverage.outfor visualization- Soft delete pattern:
deleted_at *time.Timefield, filterWHERE deleted_at IS NULL
See Also¶
- go fundamentals - types, slices, maps, interfaces
- go concurrency - goroutines, channels, sync
- go database patterns - PostgreSQL, MongoDB, Redis, transactional outbox
- observability query languages - PromQL, LogQL, TraceQL for monitoring Go services
- kafka messaging - delivery semantics, consumer groups