Skip to content

Dependency Management

O Go Overlay fornece um sistema robusto de gerenciamento de dependências que permite controlar a ordem de inicialização dos serviços. Isso é essencial quando você tem serviços que dependem de outros estarem operacionais antes de iniciar.

Overview

Em ambientes com múltiplos serviços, é comum ter dependências entre eles. Por exemplo:

  • Uma aplicação web precisa que o banco de dados esteja rodando
  • Um worker precisa que o Redis esteja disponível
  • Um proxy reverso precisa que os serviços backend estejam prontos

O Go Overlay resolve essas dependências automaticamente, garantindo que os serviços iniciem na ordem correta.

Configuration

depends_on

O campo depends_on especifica quais serviços devem estar em estado RUNNING antes que o serviço atual possa iniciar.

Sintaxe:

[[services]]
name = "web-app"
command = "/usr/bin/app"
depends_on = ["database", "redis"]

Comportamento: - O serviço permanece em estado PENDING até que todas as dependências estejam RUNNING - Se uma dependência falhar ao iniciar, o serviço dependente não iniciará - Dependências são verificadas recursivamente

wait_after

O campo wait_after adiciona um delay adicional após as dependências estarem prontas, antes de iniciar o serviço.

Sintaxe:

[[services]]
name = "web-app"
command = "/usr/bin/app"
depends_on = ["database"]
wait_after = "5s"

Uso comum: - Aguardar que um banco de dados esteja totalmente inicializado - Dar tempo para serviços aceitarem conexões - Aguardar warmup de caches

Formato: - Aceita valores como: "5s", "30s", "1m", "2m30s"

dependency_wait_timeout

Timeout global para aguardar que dependências estejam prontas.

[timeouts]
dependency_wait_timeout = "60s"  # Padrão: 60 segundos

Comportamento: - Se as dependências não estiverem prontas dentro do timeout, o serviço falha - Previne que o sistema fique travado aguardando dependências que nunca iniciarão - Aplica-se a todos os serviços

How Dependencies Affect Startup

Startup Sequence

O Go Overlay determina a ordem de inicialização baseado no grafo de dependências:

flowchart TD
    A[Load Configuration] --> B[Build Dependency Graph]
    B --> C[Detect Circular Dependencies]
    C --> D{Circular\nDependency?}
    D -->|Yes| E[Fail with Error]
    D -->|No| F[Topological Sort]
    F --> G[Start Services in Order]
    G --> H{Service Has\nDependencies?}
    H -->|Yes| I[Wait for Dependencies]
    H -->|No| J[Start Immediately]
    I --> K{Dependencies\nRunning?}
    K -->|Yes| L[Apply wait_after]
    K -->|No| M{Timeout\nExpired?}
    M -->|No| K
    M -->|Yes| N[Fail Service]
    L --> O[Start Service]
    J --> O
    O --> P{More\nServices?}
    P -->|Yes| G
    P -->|No| Q[All Services Started]

Example: Simple Dependency Chain

[[services]]
name = "database"
command = "/usr/bin/mysqld"
enabled = true

[[services]]
name = "cache"
command = "/usr/bin/redis-server"
enabled = true

[[services]]
name = "api"
command = "/usr/bin/api-server"
depends_on = ["database", "cache"]
wait_after = "3s"

[[services]]
name = "web"
command = "/usr/bin/web-server"
depends_on = ["api"]
wait_after = "2s"

Startup order:

  1. database e cache iniciam simultaneamente (sem dependências)
  2. Aguarda ambos atingirem estado RUNNING
  3. Aguarda 3 segundos adicionais (wait_after)
  4. api inicia
  5. Aguarda api atingir estado RUNNING
  6. Aguarda 2 segundos adicionais
  7. web inicia

Timeline:

t=0s:    database, cache start
t=5s:    database RUNNING
t=6s:    cache RUNNING
t=9s:    api starts (3s wait_after)
t=12s:   api RUNNING
t=14s:   web starts (2s wait_after)
t=16s:   web RUNNING

Dependency Chain Examples

Example 1: Web Application Stack

Uma stack típica com banco de dados, cache, API e frontend:

[[services]]
name = "postgres"
command = "/usr/lib/postgresql/14/bin/postgres"
args = ["-D", "/var/lib/postgresql/data"]
user = "postgres"
enabled = true

[[services]]
name = "redis"
command = "/usr/bin/redis-server"
args = ["/etc/redis/redis.conf"]
enabled = true

[[services]]
name = "migration"
command = "/app/migrate"
args = ["up"]
depends_on = ["postgres"]
wait_after = "5s"
enabled = true

[[services]]
name = "api-server"
command = "/app/api"
depends_on = ["postgres", "redis", "migration"]
wait_after = "2s"
enabled = true

[[services]]
name = "nginx"
command = "/usr/sbin/nginx"
args = ["-g", "daemon off;"]
depends_on = ["api-server"]
wait_after = "1s"
enabled = true

Dependency graph:

postgres ──┬──> migration ──> api-server ──> nginx
           │                      ↑
redis ─────┴──────────────────────┘

Startup flow:

  1. postgres e redis start (no dependencies)
  2. Wait for both to be RUNNING
  3. migration starts after 5s delay
  4. Wait for migration to complete
  5. api-server starts after 2s delay
  6. Wait for api-server to be RUNNING
  7. nginx starts after 1s delay

Example 2: Microservices with Shared Dependencies

[[services]]
name = "rabbitmq"
command = "/usr/lib/rabbitmq/bin/rabbitmq-server"
enabled = true

[[services]]
name = "postgres"
command = "/usr/lib/postgresql/14/bin/postgres"
args = ["-D", "/var/lib/postgresql/data"]
user = "postgres"
enabled = true

[[services]]
name = "user-service"
command = "/app/user-service"
depends_on = ["postgres", "rabbitmq"]
wait_after = "3s"
enabled = true

[[services]]
name = "order-service"
command = "/app/order-service"
depends_on = ["postgres", "rabbitmq"]
wait_after = "3s"
enabled = true

[[services]]
name = "notification-service"
command = "/app/notification-service"
depends_on = ["rabbitmq"]
wait_after = "2s"
enabled = true

[[services]]
name = "api-gateway"
command = "/app/gateway"
depends_on = ["user-service", "order-service"]
wait_after = "2s"
enabled = true

Dependency graph:

                    ┌──> user-service ──┐
postgres ──┬────────┤                   ├──> api-gateway
           │        └──> order-service ─┘
rabbitmq ──┼────────────> notification-service
           └────────────> (shared by multiple services)

Example 3: Complex Multi-Tier Application

# Data Layer
[[services]]
name = "mysql"
command = "/usr/bin/mysqld"
enabled = true
required = true

[[services]]
name = "redis"
command = "/usr/bin/redis-server"
enabled = true
required = true

# Application Layer
[[services]]
name = "backend-api"
command = "/app/backend"
depends_on = ["mysql", "redis"]
wait_after = "5s"
enabled = true
required = true

[[services]]
name = "worker-queue"
command = "/app/worker"
depends_on = ["mysql", "redis"]
wait_after = "5s"
enabled = true

[[services]]
name = "scheduler"
command = "/app/scheduler"
depends_on = ["mysql", "redis"]
wait_after = "5s"
enabled = true

# Presentation Layer
[[services]]
name = "frontend-server"
command = "/app/frontend"
depends_on = ["backend-api"]
wait_after = "3s"
enabled = true

# Proxy Layer
[[services]]
name = "nginx"
command = "/usr/sbin/nginx"
args = ["-g", "daemon off;"]
depends_on = ["frontend-server", "backend-api"]
wait_after = "2s"
enabled = true

Circular Dependency Detection

O Go Overlay detecta automaticamente dependências circulares e falha com erro claro.

What is a Circular Dependency?

Uma dependência circular ocorre quando dois ou mais serviços dependem um do outro, direta ou indiretamente:

Direct circular dependency:

[[services]]
name = "service-a"
depends_on = ["service-b"]

[[services]]
name = "service-b"
depends_on = ["service-a"]  # ❌ Circular!

Indirect circular dependency:

[[services]]
name = "service-a"
depends_on = ["service-b"]

[[services]]
name = "service-b"
depends_on = ["service-c"]

[[services]]
name = "service-c"
depends_on = ["service-a"]  # ❌ Circular!

Detection Behavior

Quando uma dependência circular é detectada:

  1. Go Overlay falha ao iniciar
  2. Erro claro é exibido nos logs
  3. O ciclo é identificado

Example error:

ERROR: Circular dependency detected: service-a -> service-b -> service-c -> service-a
Cannot start services with circular dependencies

How to Fix

Redesenhe suas dependências para remover o ciclo:

Before (circular):

A ──> B ──> C ──> A  ❌

After (acyclic):

A ──> B ──> C  ✓

Ou use um serviço intermediário:

A ──┐
    ├──> D
B ──┘

Best Practices

1. Keep Dependency Chains Short

Evite cadeias muito longas de dependências:

# ❌ Avoid
A -> B -> C -> D -> E -> F

# ✓ Better
A -> B
C -> B
D -> B

2. Use wait_after Appropriately

Adicione delays apenas quando necessário:

[[services]]
name = "database"
command = "/usr/bin/mysqld"
# Sem wait_after - inicia imediatamente

[[services]]
name = "app"
command = "/usr/bin/app"
depends_on = ["database"]
wait_after = "5s"  # Database precisa de tempo para aceitar conexões

3. Set Reasonable Timeouts

[timeouts]
dependency_wait_timeout = "120s"  # Ajuste baseado no seu ambiente

4. Make Critical Services Required

[[services]]
name = "database"
command = "/usr/bin/mysqld"
required = true  # Sistema falha se database não iniciar

5. Test Dependency Order

Teste a ordem de inicialização:

# Observe logs de startup
docker logs -f <container-id>

# Verifique ordem de inicialização
docker logs <container-id> | grep "Starting service"

6. Document Dependencies

Comente suas configurações:

# API depends on database and cache being ready
[[services]]
name = "api"
command = "/app/api"
depends_on = ["database", "redis"]
wait_after = "3s"  # Allow time for connection pools to initialize

Troubleshooting

Service Never Starts (Stuck in PENDING)

Sintomas: - Serviço permanece em PENDING indefinidamente - Logs mostram "Waiting for dependencies"

Causas possíveis: - Dependência não está iniciando - Dependência está falhando - Timeout muito curto

Soluções:

# Verifique status de todas as dependências
go-overlay status

# Verifique logs para erros
docker logs <container-id>

# Aumente timeout se necessário
[timeouts]
dependency_wait_timeout = "180s"

Dependency Timeout Expired

Sintomas: - Erro: "Dependency wait timeout expired" - Serviço falha ao iniciar

Soluções:

  1. Verifique se dependências estão configuradas corretamente
  2. Aumente dependency_wait_timeout
  3. Verifique se dependências estão habilitadas (enabled = true)

Services Start in Wrong Order

Sintomas: - Serviços iniciam antes de suas dependências - Erros de conexão nos logs

Soluções:

  1. Verifique configuração depends_on
  2. Adicione wait_after se necessário
  3. Verifique se não há typos nos nomes dos serviços

Circular Dependency Error

Sintomas: - Erro ao iniciar: "Circular dependency detected" - Sistema não inicia

Soluções:

  1. Revise o grafo de dependências
  2. Remova dependências desnecessárias
  3. Redesenhe arquitetura se necessário

Advanced Patterns

Optional Dependencies

Se um serviço pode funcionar sem uma dependência, não use depends_on. Em vez disso, implemente retry logic no serviço:

// Retry connection to optional service
for i := 0; i < 10; i++ {
    conn, err := connectToService()
    if err == nil {
        break
    }
    time.Sleep(2 * time.Second)
}

Conditional Startup

Use enabled = false para desabilitar serviços opcionais:

[[services]]
name = "debug-service"
command = "/app/debug"
enabled = false  # Habilite apenas em desenvolvimento

Health Check Dependencies

Implemente health checks nos serviços para garantir que estão realmente prontos:

// Health check endpoint
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
    if isReady() {
        w.WriteHeader(http.StatusOK)
    } else {
        w.WriteHeader(http.StatusServiceUnavailable)
    }
})