Go for the services that have to stay up.
Small binaries, predictable latency, one way to do things. AI-native Go engineers for high-throughput APIs, event consumers, gateways and the platform tooling around Kubernetes.
- Go
- gRPC
- Kafka
- PostgreSQL
- Redis
- Kubernetes
For a service with one job, at volume, for years.
Go is the language we pick when a service has one job and must do it at volume for years: an API gateway, a Kafka consumer that settles a steady stream of events, an exporter that sits next to every pod in the cluster. It compiles to a single static binary, starts quickly, uses memory you can reason about, and its concurrency model — goroutines and channels — matches how these workloads actually behave.
The less obvious advantage is that Go codebases stay readable. The language is small, the formatter is not optional, and there is usually one idiomatic way to write a thing. A senior engineer can read a Go service they have never seen and know where the risk is. On systems that outlive the team that wrote them, that matters more than expressiveness.
When we don’t: Go is a poor fit for domain-heavy business applications with hundreds of entity types and rich validation, where Java or .NET give you more structure for less code; for the web front end, which is React and Next.js; and for data science and ML, which stay in Python.
Six things we ship in Go.
High-throughput APIs
REST and gRPC services with net/http or connect-go, structured for high request volumes, with p99 latency as a tested requirement.
Event consumers and stream processors
Kafka consumers with explicit offset management, idempotent handlers, dead-letter topics and back-pressure — the heart of the event-driven estates we build.
API gateways and edge proxies
Custom gateways, authentication sidecars and rate limiters where an off-the-shelf proxy does not express the policy you need.
Kubernetes operators and platform tooling
Controllers, admission webhooks, CLIs and internal tools built with client-go and controller-runtime for the platform teams we work with in DevOps & Cloud.
Integration adapters
Long-running connectors to SAP, telematics feeds, payment rails and carrier APIs — resilient to the partner going away for a while and coming back with a backlog.
Migration bridges
Strangler-pattern facades and CDC consumers that let a legacy system and its replacement run side by side during a re-engineering programme.
When to choose it.
Choose Go when the service is infrastructure-shaped: high request volume, strict latency targets, long-running processes, small teams who need to operate it without a framework to learn. It fits gateways, consumers, schedulers, exporters, CLIs and anything that lives inside or next to a Kubernetes cluster. It also fits organisations that want lightweight containers — a Go service has a modest memory footprint.
Look elsewhere when the system is mostly business rules and screens — an insurance policy admin, a loyalty rules engine, a procurement workflow — where a richer type system and mature ORM save work. Look elsewhere too when your team has no Go experience and the service is not performance-critical; a language the team already owns beats a faster one they will have to learn under deadline.
A consumer loop with a context and a budget.
func (c *Consumer) Run(ctx context.Context) error {
for {
msg, err := c.reader.FetchMessage(ctx)
if err != nil { return fmt.Errorf("fetch: %w", err) }
// Each message gets its own deadline; a stuck handler cannot stall the partition
hctx, cancel := context.WithTimeout(ctx, 2*time.Second)
err = c.handle(hctx, msg)
cancel()
if errors.Is(err, ErrUnprocessable) { c.deadLetter(ctx, msg, err); err = nil }
if err != nil { return err }
+ c.metrics.Processed.WithLabelValues(msg.Topic).Inc()
if err := c.reader.CommitMessages(ctx, msg); err != nil { return err }
}
}Where Go sits in an event-driven estate.
What the Go services we hand over have.
- Context everywhere. Every I/O call takes a
context.Contextwith a deadline, so cancellation and timeouts propagate instead of leaking goroutines. - Table-driven tests and the race detector in CI. Integration tests run against real dependencies in Testcontainers, not mocks of the database.
- Errors wrapped, not swallowed.
%wwrapping with sentinel errors for the cases callers branch on, and a lint rule that fails the build on an ignored error. - Observability built in: OpenTelemetry traces, Prometheus metrics with sensible cardinality, and structured logs with
slogcarrying the request ID. - Graceful shutdown that drains in-flight work on SIGTERM, reducing the risk of a rolling deploy dropping a request or double-processing an event.
- Static, distroless images. Multi-stage builds, non-root user, no shell, SBOM generated and images scanned before they reach a registry.
- golangci-lint with a pinned config — vet, staticcheck, errcheck, gosec and a small set of project rules, enforced on every pull request.
- Load tested to a stated target. A k6 scenario in the repo that checks the p99 and throughput targets written in the service’s README.
What usually ships alongside the code.
- A single Go module per service with a documented package layout and dependency direction
- Protobuf or OpenAPI contracts with generated clients for consuming teams
- Dockerfile, Helm chart or Kustomize overlays, and the CI pipeline that publishes them
- Dashboards and alert rules for the four golden signals, exported as code
- Load-test scenarios and the results that established the service’s capacity
- A runbook covering deploy, rollback, rebalancing consumers and replaying a topic
Frequently asked questions
net/http covers most HTTP services since Go 1.22 added routing patterns; we add chi or Echo only when a project needs their middleware ecosystem. For gRPC we use the official libraries or connect-go. Frameworks that hide the request lifecycle tend to hide the bugs too.Neighbouring stacks and the services that use them.
For the service that has to be fast and predictable.
Tell us the throughput and the latency target. A Go engineer reads it and replies in writing with how we would approach it.