Skip to main content

Architecture

The microservices-app consists of 6 independent services that communicate via gRPC (using Connect protocol) and REST APIs. The architecture is designed to demonstrate various microservice patterns including service-to-service communication, circuit breakers, retry budgets, and both synchronous and asynchronous database patterns.

Service Catalog

Greeter Service

Go-based gRPC service that orchestrates calls to external APIs via the Caller service

Caller Service

Go-based gRPC service that makes HTTP calls to external URLs

Gateway Service

Go-based gRPC service with circuit breaker and retry logic for custom language service

Auth Service

Node.js REST API providing JWT-based authentication with RSA signing

Custom Language Service

Node.js REST API for custom invocation logic with configurable response codes

Technology Stack

Go Services (greeter, caller, gateway)

  • Framework: Connect-Go (gRPC-compatible protocol)
  • HTTP/2: h2c handler for efficient communication
  • Database: PostgreSQL via pgx/v5
  • Observability: OpenTelemetry with automatic tracing
  • Protocol: Protocol Buffers for service definitions

Node.js Services (auth-service, custom-lang-service)

  • Framework: Express
  • Database: PostgreSQL via pg driver
  • Authentication: JWT with RSA-256 signing
  • Observability: OpenTelemetry instrumentation

Service Communication

Database Architecture

Each service has its own PostgreSQL database following the database-per-service pattern:
ServiceDatabasePatternPurpose
Greetergreeter_dbSynchronous writesStores greeting logs with external API status
Callercaller_dbAsynchronous writesLogs external API calls (fire-and-forget)
Gatewaygateway_dbSynchronous writesRecords invocation history including failures
Authauth_dbSynchronous writesUser accounts and credentials
Custom Langlang_dbSynchronous writesExecution logs

Service Patterns

Synchronous DB Pattern (Greeter, Gateway, Auth, Custom Lang)

Database writes complete before sending the response. Guarantees data persistence but adds latency.
// From greeter service
if s.pool != nil {
    _, dbErr := s.pool.Exec(ctx, 
        "INSERT INTO greetings (name, message, external_status) VALUES ($1, $2, $3)", 
        name, msg, callerResp.Msg.GetStatusCode())
    if dbErr != nil {
        slog.Error("failed to insert greeting", "error", dbErr)
    }
}
return resp, nil

Asynchronous DB Pattern (Caller)

Database writes happen in a goroutine after returning the response. Lower latency but risk of data loss.
// From caller service
if s.pool != nil {
    capturedURL := targetURL
    go func() {
        _, err := s.pool.Exec(context.Background(), 
            "INSERT INTO call_logs (url, status_code, body_length) VALUES ($1, $2, $3)", 
            capturedURL, statusCode, bodyLength)
        if err != nil {
            slog.Error("failed to insert call log", "error", err)
        }
    }()
}

Circuit Breaker Pattern (Gateway)

The Gateway service uses the gobreaker library to prevent cascading failures:
  • Max Requests: 3 requests allowed in half-open state
  • Timeout: 30 seconds before transitioning to half-open
  • Trip Threshold: 5 consecutive failures
  • Interval: 10 seconds for counting failures

Retry Budget Pattern (Gateway)

Limits retry attempts to prevent retry storms:
  • Capacity: 20 tokens
  • Refill Rate: 10 tokens per second
  • Retryable Errors: 429, 502, 503, 504, network timeouts

Common Configuration

All services support these standard environment variables:
PORT
string
default:"varies"
HTTP server port (8080 for greeter, 8081 for caller, 8082 for gateway, 8090 for auth, 3000 for custom-lang)
DATABASE_URL
string
PostgreSQL connection string. Services run without DB if not provided.Format: postgresql://user:pass@host:port/dbname
OTEL_EXPORTER_OTLP_ENDPOINT
string
OpenTelemetry collector endpoint for traces and metrics
OTEL_SERVICE_NAME
string
Service name for OpenTelemetry spans and metrics

Health Checks

All services expose a /healthz endpoint:
  • Returns 200 OK if service is healthy
  • Returns 503 Service Unavailable if database health check fails
  • Go services: Check database ping
  • Node.js services: Execute SELECT 1 query

Development Ports

When running locally via Docker Compose:
ServiceInternal PortExternal Access
Traefik80localhost:30081
Greeter8080via Traefik
Caller8081internal only
Gateway8082via Traefik
Auth8090via Traefik
Custom Lang3000internal only
PostgreSQL5432localhost:5432

Next Steps

Greeter Service

Learn about the main greeting service

Gateway Service

Explore circuit breaker patterns

Auth Service

Implement authentication

Deployment

Deploy the services

Build docs developers (and LLMs) love