Technology · Languages & frameworks

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.

GoWhat we build with it
01High-throughput APIs
02Event consumers and stream processors
03API gateways and edge proxies
04Kubernetes operators and platform tooling
05Integration adapters
06Migration bridges
Usually runs with
  1. Go
  2. gRPC
  3. Kafka
  4. PostgreSQL
  5. Redis
  6. Kubernetes
Why we use it

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.

What we build with it

Six things we ship in Go.

01

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.

02

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.

03

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.

04

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.

05

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.

06

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.

Fit

When to choose it.

Right fit

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

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.

In practice

A consumer loop with a context and a budget.

consumer.gogo
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 }
    }
}
Reference architecture

Where Go sits in an event-driven estate.

Edge
Ingress / API gateway (Go)Auth sidecarRate limiting
Services
gRPC servicesKafka consumersSchedulers & workersIntegration adapters
Data
PostgreSQLRedisKafka topicsObject storage
Platform
KubernetesHelm / ArgoCDPrometheus & OpenTelemetryGitHub Actions
Our standards

What the Go services we hand over have.

  • Context everywhere. Every I/O call takes a context.Context with 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. %w wrapping 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 slog carrying 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.
Typical pairings

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
Usually runs with
GogRPCKafkaPostgreSQLRedisKubernetesHelmPrometheusOpenTelemetryTerraform
FAQ

Frequently asked questions

As few as possible. The standard library’s 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.
Yes. Operators, admission controllers, CLIs and internal platform tools are a regular part of our DevOps & Cloud work, and Go is the natural language for anything that talks to the Kubernetes API.
Comfortably. We usually introduce Go at the edges — a gateway, a consumer, an adapter — with a gRPC or REST contract between it and the existing estate, and OpenTelemetry tracing across both so a request can be followed end to end regardless of runtime.
sqlc or pgx with hand-written SQL for the queries that matter, migrations under version control with golang-migrate or Atlas, and a thin repository layer. For services with a large relational model we say so early and suggest a different language.
That fits a dedicated team engagement: we pair on the first services, run reviews with your engineers, and hand over a codebase with conventions written down rather than assumed.
Related

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.

✓ No pitch deck✓ Reply from an AI-native engineer✓ Written follow-up