Enrique Valdivia Rios Portafolio - Senior Backend Engineer | Java · Spring Boot · Kubernetes | Fintech & Payments | Scalable Distributed Systems

API Health CLI: Monitor de Endpoints en Go

Una herramienta de línea de comandos para monitorear la salud de APIs HTTP en entornos SRE y DevOps. Construida en Go para aprovechar la concurrencia nativa del lenguaje.

El código completo está en mi repositorio de GitHub.


El Problema

Los equipos de infraestructura necesitan verificar rápidamente el estado de múltiples endpoints — durante deployments, incidentes, o como parte de pipelines CI/CD. Las herramientas existentes son o demasiado pesadas o no permiten configuración flexible. Esta CLI resuelve:


Arquitectura

El proyecto sigue una arquitectura en capas con paquetes bien definidos:

cmd/          → Comandos CLI (cobra): check y watch
internal/
  checker/    → Lógica de checks HTTP concurrentes con reintentos
  config/     → Parser de YAML con expansión de variables de entorno
  notifier/   → Notificador Slack via webhook
  output/     → Renderizado de resultados en terminal con color

Stack

ComponenteTecnología
LenguajeGo 1.22
CLI FrameworkCobra
Output con colorfatih/color
Configgopkg.in/yaml.v3
Testsstdlib (net/http/httptest)

Decisiones de Diseño

1. Concurrencia con goroutines y WaitGroup

Los checks se ejecutan en goroutines independientes y los resultados se almacenan en un slice pre-asignado por índice, garantizando el orden sin necesidad de sincronización adicional:

func (hc *HealthChecker) CheckAll(endpoints []Endpoint) []Result {
    results := make([]Result, len(endpoints))
    var wg sync.WaitGroup

    for i, ep := range endpoints {
        wg.Add(1)
        go func(idx int, endpoint Endpoint) {
            defer wg.Done()
            results[idx] = hc.Check(endpoint)
        }(i, ep)
    }

    wg.Wait()
    return results
}

2. Reintentos con exponential backoff

Si un endpoint falla, reintenta con backoff creciente para evitar saturar el servidor:

for attempt := 0; attempt <= retries; attempt++ {
    if attempt > 0 {
        backoff := time.Duration(math.Pow(2, float64(attempt-1))) * 500 * time.Millisecond
        time.Sleep(backoff)
    }
    lastResult = hc.doCheck(ep.URL, method, ep.Headers, expectedStatus)
    if lastResult.Healthy {
        return lastResult
    }
}

3. Expansión de variables de entorno en config

Los tokens y URLs sensibles no viven en el archivo de configuración; se inyectan como variables de entorno:

endpoints:
  - url: https://api.example.com/health
    headers:
      Authorization: "Bearer ${API_TOKEN}"

notifications:
  slack:
    webhook_url: "${SLACK_WEBHOOK_URL}"

4. Modo CI con exit code

En pipelines de CI/CD, el flag --ci hace que la herramienta retorne exit code 1 si algún endpoint falla:

healthcheck check https://api.example.com/health --ci
# Retorna exit 1 si el endpoint está caído → el pipeline falla correctamente

Uso

# Check rápido de URLs
healthcheck check https://api.example.com/health https://status.example.com

# Con config file
healthcheck check --config endpoints.yaml

# Con timeout y reintentos
healthcheck check https://api.example.com/health --timeout 3s --retries 2

# Monitoreo continuo (30s por defecto)
healthcheck watch --config endpoints.yaml --interval 10s

# Con notificaciones Slack
healthcheck check --config endpoints.yaml --slack "$SLACK_WEBHOOK_URL"

# Modo CI
healthcheck check --config endpoints.yaml --ci

Salida de ejemplo

  ENDPOINT                                     STATUS         TIME
  ──────────────────────────────────────────────────────────────────
  ✔ https://api.example.com/health             200 OK         124ms
  ✔ https://status.example.com/ping            200 OK         89ms
  ✘ https://internal.example.com/readiness     503 Service U  2.1s

  2/3 healthy — total time 2.3s

Testing

El proyecto tiene 24 tests en tres paquetes:

func TestCheckAll_ReturnsResultsInOrder(t *testing.T) {
    statuses := []int{200, 503, 200, 404, 200}
    // ... crea un servidor por cada status
    results := hc.CheckAll(endpoints)

    for i, result := range results {
        expectedHealthy := statuses[i] == 200
        if result.Healthy != expectedHealthy {
            t.Errorf("result[%d]: expected healthy=%v, got healthy=%v", i, expectedHealthy, result.Healthy)
        }
    }
}

Cobertura: checker 91.8% · config 100% · notifier 95.7%


Cómo Ejecutar

git clone https://github.com/enriquevaldivia1988/api-health-cli.git
cd api-health-cli

# Compilar
make build

# Ejecutar tests
make test

# Instalar globalmente
make install

# Usar directamente con go run
go run . check https://httpbin.org/status/200 https://httpbin.org/status/503

Conclusión

Este proyecto demuestra cómo construir una herramienta CLI de producción en Go: con concurrencia real, configuración flexible, tests con servidores HTTP embebidos y una experiencia de usuario cuidada en terminal.


Enrique Valdivia

Ver código en GitHub