feat(httpclient): initial stable release v0.9.0
Resilient HTTP client with circuit breaking, exponential-backoff retry, X-Request-ID propagation, and a generic typed JSON helper. What's included: - Client interface with Do(req) method; New(logger, cfg) and NewWithDefaults(logger) constructors - Config struct with env-tag support for timeout, dial timeout, retry, and circuit breaker parameters - Retry via avast/retry-go/v4 with BackOffDelay; triggers only on network errors and HTTP 5xx - Circuit breaker via sony/gobreaker wrapping the full retry loop; open circuit → xerrors.ErrUnavailable - X-Request-ID header propagated automatically from context via logz.GetRequestID on every attempt - DoJSON[T](ctx, client, req) generic helper for typed JSON request/response with xerrors error mapping - MapStatusToError(code, msg) exported function mapping HTTP status codes to xerrors types Tested-via: todo-api POC integration Reviewed-against: docs/adr/
This commit is contained in:
50
docs/adr/ADR-002-request-id-propagation.md
Normal file
50
docs/adr/ADR-002-request-id-propagation.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# ADR-002: Request ID Propagation via X-Request-ID Header
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-03-18
|
||||
|
||||
## Context
|
||||
|
||||
In a distributed system, a single inbound request may fan out to multiple downstream service
|
||||
calls. Without a shared correlation identifier, tracing a request across service logs requires
|
||||
matching timestamps or other heuristics. A request ID, propagated as an HTTP header
|
||||
(`X-Request-ID`), lets logs across services be correlated by a single value.
|
||||
|
||||
The `logz` module owns the request ID context key (ADR-003 global: context helpers live with
|
||||
data owners). `httpclient` depends on `logz` and should use its helpers rather than define its
|
||||
own context key.
|
||||
|
||||
## Decision
|
||||
|
||||
Inside the retry function in `Do`, before executing each request attempt, the client reads
|
||||
the request ID from the context using `logz.GetRequestID(req.Context())`. If a non-empty
|
||||
value is present, it is set as the `X-Request-ID` header on the outgoing request:
|
||||
|
||||
```go
|
||||
if id := logz.GetRequestID(req.Context()); id != "" {
|
||||
req.Header.Set("X-Request-ID", id)
|
||||
}
|
||||
```
|
||||
|
||||
The header is set on every retry attempt, not just the first, because the same `*http.Request`
|
||||
object is reused across retries.
|
||||
|
||||
If no request ID is present in the context (the ID is the zero string), the header is not
|
||||
set. This is verified by `TestClient_Do_NoRequestID`.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Request IDs flow automatically to downstream services without any caller boilerplate.
|
||||
- Correlation across service boundaries works with no additional middleware.
|
||||
- The integration is testable: `TestClient_Do_InjectsRequestID` verifies end-to-end
|
||||
propagation using `logz.WithRequestID` and an `httptest.Server`.
|
||||
|
||||
**Negative:**
|
||||
- `httpclient` takes a direct import dependency on `logz`. This is accepted per ADR-001
|
||||
(global) which permits direct imports between framework modules.
|
||||
- The header name `X-Request-ID` is hardcoded. Projects that use a different header name
|
||||
(e.g. `X-Correlation-ID`) cannot configure this without forking the client.
|
||||
- Header propagation only works when the caller places the request ID in the context via
|
||||
`logz.WithRequestID`. Requests built without a context carrying a request ID will not
|
||||
have the header set.
|
||||
Reference in New Issue
Block a user