Building Microservices
Microservices in Go decompose systems into independently deployable services communicating via HTTP/gRPC and async messaging (Kafka, NATS, RabbitMQ).
Introduction
Microservices in Go decompose systems into independently deployable services communicating via HTTP/gRPC and async messaging (Kafka, NATS, RabbitMQ). Go's small binaries, fast startup, and concurrency model make it ideal for containerized microservices on Kubernetes.
Each service owns its database (database-per-service pattern), exposes health endpoints, and propagates trace context. Service discovery, API gateways, and circuit breakers address distributed system challenges — interview favorites.
This lesson outlines order and inventory microservices with HTTP communication, shared observability, and independent deployment — architecture patterns from real cloud-native systems.
The story
Uber's architecture splits ride matching, pricing, and payments into independent Go services communicating over gRPC and Kafka. Each service owns its database, exposes health and metrics endpoints, and deploys independently through Kubernetes — a failure in the pricing service doesn't take down trip history if circuit breakers and timeouts are configured correctly.
Microservices trade operational complexity for team autonomy: Go's fast compile times and single-binary deployment make service proliferation manageable compared to JVM-based alternatives.
Understanding the topic
Key concepts
- Single responsibility per service with bounded context.
- Database per service — no shared tables across teams.
- Synchronous: REST/gRPC between services.
- Async: events via message broker for decoupling.
- API Gateway routes external traffic to internal services.
- Health (/health) and readiness (/ready) probes for K8s.
flowchart TBGateway[API Gateway] --> SvcA[Order Service]Gateway --> SvcB[User Service]SvcA --> MQ[Message Queue]SvcB --> MQ
Step-by-step explanation
- Client → API Gateway → Order Service.
- Order Service calls Inventory Service via HTTP client.
- Order created event published to Kafka.
- Notification Service consumes event, sends email.
- Each service has own repo, Dockerfile, K8s Deployment.
- OpenTelemetry traces propagate across service calls.
Practical code example
Order service calling inventory service with timeout and error handling:
package mainimport ("context""encoding/json""fmt""net/http""time")type InventoryClient struct {base stringclient *http.Client}func NewInventoryClient(base string) *InventoryClient {return &InventoryClient{base: base,client: &http.Client{Timeout: 3 * time.Second},}}func (c *InventoryClient) CheckStock(ctx context.Context, sku string, qty int) (bool, error) {url := fmt.Sprintf("%s/stock/%s?qty=%d", c.base, sku, qty)req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)if err != nil {return false, err}resp, err := c.client.Do(req)if err != nil {return false, fmt.Errorf("inventory call: %w", err)}defer resp.Body.Close()if resp.StatusCode != http.StatusOK {return false, fmt.Errorf("inventory status %d", resp.StatusCode)}var result struct{ Available bool `json:"available"`}if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {return false, err}return result.Available, nil}func orderHandler(inv *InventoryClient) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {ctx := r.Context()ok, err := inv.CheckStock(ctx, "SKU-42", 1)if err != nil {http.Error(w, "inventory unavailable", 503)return}if !ok {http.Error(w, "out of stock", 409)return}w.WriteHeader(http.StatusCreated)fmt.Fprint(w, `{"order_id":"ord-123"}`)}}
Line-by-line code explanation
service boundaries— split by business capability, not by technical layer.gRPC + protobuf— typed contracts between services with codegen from.protofiles.each service owns its datastore— no shared databases across service boundaries./healthz and /readyzendpoints for Kubernetes liveness and readiness probes.Prometheus /metricsexposes request latency histograms and error counters.distributed tracing— propagate trace IDs via OpenTelemetry context across gRPC metadata.circuit breakers— fail fast when downstream services exceed error thresholds.API gateway— Kong or Envoy terminates TLS and routes external traffic to internal services.
Key takeaway: 503 when dependency down; 409 for business conflict. Context propagates cancellation. Each service deploys independently.
Real-world use
Where you'll use this in production
- E-commerce order, payment, inventory, notification services.
- Banking account and transaction processing domains.
- IoT telemetry ingestion and alerting pipelines.
- Multi-team SaaS platforms with service ownership.
Best practices
- Design for failure — timeouts, retries, circuit breakers.
- Idempotent endpoints for safe retries.
- Database per service — integrate via API/events only.
- Distributed tracing with OpenTelemetry on all outbound calls.
- Version internal APIs; avoid breaking changes without notice.
- Document service contracts with OpenAPI or protobuf.
Common mistakes
- Distributed monolith — shared database across services.
- No timeout on service calls — cascading failure.
- Chatty synchronous calls — prefer events for non-critical paths.
- Missing health checks — K8s routes traffic to broken pods.
- Shared libraries with business logic coupling teams.
Advanced interview questions
Q1BeginnerMicroservice vs monolith?
Q2BeginnerSync vs async communication?
Q3IntermediateDatabase per service?
Q4IntermediateHandle inventory service down?
Q5AdvancedDesign order flow across 4 microservices.
Summary
Go microservices deploy as small containers on Kubernetes. Database per service; integrate via HTTP/gRPC and events. Timeouts, health checks, and tracing are mandatory. API Gateway exposes unified external interface. Next lesson: performance optimization.