Skip to content

Visão geral

Caminho de uma requisição

POST /smiles/login


┌────────────────────────────────────────────────┐
│ chi.Mux                                        │
│  └─ MetricsMiddleware (bot-tools-go)           │  latência/contagem HTTP
└────────────────────────────────────────────────┘

┌────────────────────────────────────────────────┐
│ huma API                                       │
│  └─ RequestLogger                              │  gera/propaga trace id
└────────────────────────────────────────────────┘
        │  valida o body contra o schema do Input

┌────────────────────────────────────────────────┐
│ handlers.SmilesLoginHandler                    │
│   defer EmitLoginCompleted(...)                │  evento terminal, 1 por requisição
└────────────────────────────────────────────────┘


┌────────────────────────────────────────────────┐
│ services.SmilesService.Login()                 │
│   cache → refresh → login novo                 │
│   emite eventos por passo, conta métricas      │
└────────────────────────────────────────────────┘
        │                              │
        ▼                              ▼
  infra/redis                requests/smiles/*.go


                          serviço de automação Smiles

Middlewares

Só existem dois, e a ordem importa:

  1. MetricsMiddleware — registrado no chi.Mux, antes da API huma. Cobre tudo, inclusive /metrics e as rotas estáticas.
  2. middleware.RequestLogger — registrado na API huma (router.UseMiddleware). Cobre apenas as operações registradas via huma.Register.

RequestLogger

go
traceID := utils.TraceIDFromContext(ctx.Context())
if traceID == "" {
    traceID = uuid.New().String()
}
ctx.SetHeader("X-Trace-ID", traceID)
ctx = huma.WithValue(ctx, constant.ContextKeyTraceID, traceID)
next(ctx)
ctx.SetHeader("X-Trace-ID", traceID)

Pontos relevantes:

  • O header é escrito antes e depois do next. Antes, para que respostas de erro também o carreguem; depois, para que o valor continue autoritativo caso a operação mexa nos headers.
  • O trace id vai para o contexto sob constant.ContextKeyTraceID — um tipo próprio (type contextKey string), não uma string solta.
  • A partir daí, utils.SetTraceHeader(request) propaga o mesmo id nas chamadas ao serviço de automação e ao Pigeon, via header X-Trace-ID.

Trace id vindo de fora

O middleware lê o contexto, não o header da requisição. Na prática isso significa que o Khronos sempre gera um id novo por requisição HTTP; a reutilização acontece internamente (o cron, por exemplo, cria o seu com uuid.New() e injeta no contexto).

Registro de rotas

Todas as rotas passam por huma.Register, o que dá de graça: validação do body, geração do OpenAPI e mapeamento de huma.StatusError para o status HTTP correto.

go
huma.Register(api, huma.Operation{
    OperationID:   operationSmilesLogin,
    Method:        http.MethodPost,
    Path:          pathSmilesLogin,
    Summary:       summaryLogin,
    Description:   fmt.Sprintf(descriptionLogin, tagSmiles),
    DefaultStatus: http.StatusOK,
    Tags:          []string{tagSmiles},
}, SmilesLoginHandler)

Rotas com Hidden: true (/smiles/account-info, /smiles/account-statement, /smiles/account-exp-points, /smiles/dashboard) funcionam normalmente, mas não aparecem no OpenAPI nem no /docs.

Ciclo de vida

humacli cuida do start/stop:

go
hooks.OnStart(func() { http.ListenAndServe(":"+port, handler) })
hooks.OnStop(func()  { time.Sleep(config.Options.AppGracePeriod) })

O OnStop apenas dorme APP_GRACE_PERIOD — não há Shutdown graceful do servidor HTTP. A janela serve para o balanceador tirar o pod de rotação antes de o processo morrer.

Falha ao subir não mata o processo

Se ListenAndServe retornar erro, o código apenas registra dois slog.Error e retorna. O processo continua vivo, sem servidor — o pod não reinicia sozinho, mas falha o health check em /ping.

Concorrência

  • O cliente Redis é criado sob sync.Once (ensureConnection), então a conexão é única e segura para uso concorrente.
  • O registry de métricas também é sync.Once.
  • Os services são criados por requisição (NewSmilesService(ctx, params)) e não guardam estado compartilhado.
  • O LatamService monta um http.Client próprio, com cookiejar e política explícita de não seguir redirecionamentos (http.ErrUseLastResponse) — o fluxo da LATAM depende de ler o 302 e seus cookies.
  • O job do cron usa um lock distribuído (SETNX em oauth:smiles:task:lock, TTL de 9 min) para que só uma réplica execute por vez.

Documentação interna — 123milhas