Microservices in Go combineren de eenvoud en snelheid van de taal met een architectuur die schaalbaar is per team en per component. Waar een monoliet één grote codebase is, bestaat een microservices-architectuur uit meerdere kleine services die elk een duidelijke verantwoordelijkheid hebben en onafhankelijk gedeployed kunnen worden.
Go is niet voor niets de favoriete taal voor veel moderne microservices. Statische binaries, snelle opstarttijden en ingebouwde concurrency maken Go bij uitstek geschikt voor netwerk-georiënteerde services die snel moeten reageren en efficiënt met resources omgaan.
In deze handleiding lopen we door de belangrijkste bouwstenen: service design, communicatie, observability, resilience en deployment. We bouwen voort op concepten uit eerdere artikelen, dus wees niet bang om terug te klikken als je iets wilt opfrissen.
Wanneer kies je voor microservices?
Microservices zijn geen doel op zich. Ze lossen organisatorische en technische schaalbaarheidsproblemen op, maar introduceren ook complexiteit. Voordat je een monoliet opsplitst, stel jezelf een paar vragen.
Heeft je team meerdere autonome subteams die zonder elkaar te blokkeren willen kunnen deployen? Heb je duidelijke domeingrenzen in je applicatie die zich lenen voor aparte services? Zijn er onderdelen die significant andere schaalbaarheidseisen hebben dan de rest?
Als je op die vragen "nee" antwoordt, is een goed gestructureerde monoliet waarschijnlijk een betere keuze. Lees ook onze gids over project structuur in Go voor tips om een monoliet netjes te organiseren.
Voordelen van microservices
- Onafhankelijke deployments, teams kunnen zonder coördinatie releasen.
- Technische vrijheid, elke service kan eigen dependencies en versies hebben.
- Schaalbaarheid per service, alleen de drukste services krijgen extra instanties.
- Faultisolatie, een crash in één service trekt niet de hele applicatie omver.
Nadelen om eerlijk onder ogen te zien
- Operationele complexiteit, meer services betekent meer CI/CD, monitoring en dashboards.
- Netwerkcommunicatie, calls die lokaal functies waren, worden nu netwerkrequests met latency en faalkansen.
- Data consistency, transacties over services heen zijn lastig en vereisen patterns zoals sagas.
- Testing, integratietests worden zwaarder en vereisen service stubs of contract testing.
Een microservice opzetten in Go
Elke microservice in Go is in essentie een klein, zelfstandig programma dat via een netwerkprotocol met de buitenwereld praat. De kern bestaat meestal uit een HTTP- of gRPC-server, wat business logic en een data-laag.
Begin met een duidelijke project layout. Gebruik bijvoorbeeld een cmd/ directory voor de main entrypoint, internal/ voor applicatiecode die niet buiten de service gebruikt mag worden en api/ voor contracten zoals Protobuf- of OpenAPI-bestanden.
// cmd/orders/main.go
package main
import (
"context"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
mux := http.NewServeMux()
mux.HandleFunc("GET /orders/{id}", handleGetOrder(logger))
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Error("server error", "err", err)
os.Exit(1)
}
}()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = srv.Shutdown(shutdownCtx)
}
Voor meer diepgang over servers en routing, zie HTTP servers bouwen in Go en middleware en routing in Go.
Communicatie tussen services
Services moeten met elkaar praten. Welk protocol je kiest hangt af van de use case.
REST over HTTP
REST is de klassieker. Het werkt met elke taal, is makkelijk te debuggen en ideaal voor publieke API's. Voor interne communicatie is REST prima geschikt, zeker als je contracten vastlegt met OpenAPI.
gRPC voor interne communicatie
Voor service-to-service communicatie binnen je cluster is gRPC vaak een betere keuze. gRPC gebruikt Protobuf voor type-veilige contracten, heeft goede performance dankzij HTTP/2 en genereert client- en servercode automatisch.
// Vereenvoudigde gRPC server setup
func main() {
lis, err := net.Listen("tcp", ":9090")
if err != nil {
log.Fatal(err)
}
s := grpc.NewServer()
ordersv1.RegisterOrderServiceServer(s, &orderServer{})
if err := s.Serve(lis); err != nil {
log.Fatal(err)
}
}
Asynchrone messaging
Niet alles hoeft synchroon. Voor events als "order geplaatst" of "gebruiker geregistreerd" past een message queue beter. Services publiceren events en andere services reageren erop zonder directe koppeling. Populaire keuzes zijn NATS, RabbitMQ en Kafka.
Async messaging past goed samen met concurrency patterns. Bouw voort op onze gids over worker pools en pipelines om consumers te implementeren die robuust met backpressure omgaan.
Service discovery en configuratie
In een dynamische omgeving veranderen IP-adressen constant. Hardcoded hostnames werken niet. Er zijn drie veelgebruikte benaderingen.
DNS-based discovery werkt prima in Kubernetes: services praten via hun service name en kube-dns regelt de rest. Voor de meeste teams is dit de eenvoudigste en meest onderhoudbare optie.
Service mesh tools zoals Istio of Linkerd voegen een laag toe die discovery, load balancing, retries en mTLS regelt buiten je applicatiecode om.
Client-side discovery met tools zoals Consul geeft je meer controle maar vereist meer code in elke service.
Configuratie haal je uit environment variables of een central config store. Houd het simpel met 12-factor principes: één binary, configuratie via environment, logs naar stdout.
Observability is niet optioneel
Bij één monoliet open je de logs en zie je wat er gebeurt. Bij twintig services heb je structured logging, metrics en distributed tracing nodig om te begrijpen wat er in je systeem speelt.
Structured logging
Gebruik log/slog uit de standard library. Log in JSON, voeg altijd een request- of trace-ID toe en log alle cross-service calls. Meer hierover in onze gids over logging en observability in Go.
Distributed tracing
Met OpenTelemetry propageer je een trace context door elke call. Zo zie je in één view hoe een request door je hele systeem reisde en waar tijd verloren ging. Tools als Jaeger of Tempo visualiseren die traces.
Metrics
Exposeer Prometheus-compatible metrics op /metrics. Meet latency, request rates, error rates en resource gebruik. Alert op RED-metrics: Rate, Errors, Duration.
Resilience patterns
Netwerken falen. Services crashen. Databases worden traag. Bouw je services alsof dat normaal is, want dat is het.
Timeouts
Elke outgoing call moet een timeout hebben. Zonder timeout blokkeert een trage downstream service uiteindelijk je hele pool.
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
resp, err := client.GetUser(ctx, &userpb.GetUserRequest{Id: id})
Retries met backoff
Retry alleen op idempotente operaties en gebruik exponential backoff met jitter. Bibliotheken zoals github.com/cenkalti/backoff regelen dit netjes.
Circuit breakers
Als een downstream service volledig faalt, stop dan tijdelijk met requests sturen. Een circuit breaker opent na X opeenvolgende failures en sluit na een cooldown periode.
Graceful degradation
Wat gebeurt er als een niet-kritieke service offline is? Liever een degraded response dan een volledige foutmelding. Denk aan default waarden, cached responses of een simpelere fallback.
Voor robuuste error handling per service, zie functions en error handling in Go.
Data en consistency
Elke microservice heeft idealiter een eigen database. Dat voorkomt gekoppelde schema's en maakt onafhankelijke deployments mogelijk. Zie onze gids over database toegang in Go voor de basis.
Transacties over meerdere services heen zijn een uitdaging. Vermijd ze als het kan. Als je ze echt nodig hebt, kijk dan naar het saga pattern: een reeks lokale transacties met compenserende acties bij falen.
Gebruik events om data tussen services te synchroniseren. Als de user service een gebruiker update, publiceert het een event dat andere services kunnen oppikken om hun read models bij te werken.
Testing van microservices
Unit tests schrijf je zoals voor elke Go applicatie, zie testing in Go. Voor microservices komen daar twee niveaus bij.
Integration tests draaien je service met echte dependencies, bijvoorbeeld via testcontainers-go dat PostgreSQL of Redis in een Docker container opstart per test.
Contract tests verifiëren dat de API-contracten tussen services kloppen. Tools zoals Pact helpen producer en consumer onafhankelijk te testen zonder volledige end-to-end tests.
Deployment en CI/CD
Go microservices draaien vrijwel altijd in containers. Gebruik een multi-stage Dockerfile voor kleine images.
FROM golang:1.23 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/orders
FROM gcr.io/distroless/static:nonroot
COPY --from=builder /out/app /app
ENTRYPOINT ["/app"]
De resulterende image is vaak onder de 20 MB. Deploy op Kubernetes met health checks, resource limits en een HorizontalPodAutoscaler op basis van CPU of custom metrics.
Houd CI/CD pipelines per service. Elke service heeft een eigen build, test en deploy pipeline. Dat is het hele punt van microservices: autonomie per team en per service.
Veelgestelde vragen
Wat zijn microservices in Go?
Microservices in Go zijn kleine, onafhankelijk deploybare services geschreven in Go die samen een applicatie vormen. Elke service heeft een eigen verantwoordelijkheid en communiceert via HTTP, gRPC of message queues. Go is populair voor microservices vanwege de snelle compile tijden, lage geheugenvoetafdruk en uitstekende concurrency support.
Wanneer kies je voor microservices in plaats van een monoliet?
Kies voor microservices wanneer je team groot genoeg is om meerdere services te onderhouden en je duidelijke domeingrenzen hebt. Voor kleine teams of startende projecten is een goed gestructureerde monoliet vaak efficiënter. Microservices voegen operationele complexiteit toe die alleen loont bij voldoende schaal.
Welke communicatieprotocollen gebruik je tussen Go microservices?
De meest voorkomende keuzes zijn REST over HTTP voor externe API's, gRPC voor interne service-to-service communicatie en message queues zoals NATS of RabbitMQ voor asynchrone events. gRPC biedt type-veilige contracten en betere performance dan REST.
Hoe houd je microservices observable?
Implementeer structured logging, metrics en distributed tracing in elke service. Gebruik tools zoals OpenTelemetry voor traces, Prometheus voor metrics en een centrale log aggregator. Correleer requests via trace IDs die door alle services meereizen.
Hoe deploy je Go microservices?
Go microservices worden meestal als Docker containers gedraaid op Kubernetes of vergelijkbare orchestrators. Dankzij statische binaries zijn Go images klein en starten snel. Gebruik multi-stage builds, health checks en graceful shutdown voor betrouwbare deployments.