Files
web/docs/adr/index.md
Rene Nochebuena c4ef1948f6 feat(web): initial implementation — server, mw, httputil, health (v1.0.0)
Introduces code.nochebuena.dev/einherjar/web — the HTTP transport layer of the
Einherjar framework. Absorbs httpserver, httpmw, and httputil from micro-lib,
replacing gorilla/mux with chi, adopting SecurityBag-native middleware, and
centralizing error handling through a single httputil.Error function.

server:
- Server interface — embeds lifecycle.Component and chi.Router
- Config struct (EINHERJAR_SERVER_* env vars); DefaultConfig
- New(logger, cfg, opts...) Server; WithMiddleware option
- Binds TCP synchronously in OnStart; logs "server: listening" on success
- Graceful shutdown within ShutdownTimeout on OnStop

mw:
- Recover — catches panics, returns 500, logs at Error
- RequestID — injects UUID v7 (UUID v4 fallback) into context and X-Request-ID header
- RequestLogger — structured access log per request
- CORS / CORSAllowAll — chi-based, applied only when origins non-empty
- IPRateLimit / UserRateLimit — pluggable RateLimiterStore interface
- InMemoryRateLimiterStore — token-bucket backed by golang.org/x/time/rate;
  background goroutine evicts stale entries every 5 minutes
- StatusRecorder — wraps ResponseWriter to capture HTTP status code

httputil:
- Handle[Req, Res] / HandleNoBody[Res] / HandleEmpty[Req] — generic handler adapters
- Error(logger, w, r, err) — derives log level from status (≥500→Error, 4xx→Warn,
  499→Info); writes standardized JSON body; logz enriches *xerrors.Err automatically
- JSON(w, status, v) / NoContent(w) — response helpers
- HandlerFunc adapter type

health:
- NewHandler / NewHandlerWithConfig — runs all Checkable checks concurrently;
  returns JSON {status, components} with per-component latency and error
- Config struct (EINHERJAR_HEALTH_CHECK_TIMEOUT, default 5s)

Root factory:
- web.New(logger, cfg...) Server — composes Recover+RequestID+RequestLogger+CORS
  in outermost-first order; CORS applied only when AllowedOrigins non-empty

- server.Server interface and web/server/identifiable.go: embeds observability.Identifiable;
  ModulePath and ModuleVersion read via runtime/debug.ReadBuildInfo() — prints in launcher banner
2026-05-29 15:48:11 +00:00

22 lines
1.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR Index — web
Module-level architecture decisions for `code.nochebuena.dev/einherjar/web`.
For framework-wide decisions see the
[Einherjar docs repository](https://code.nochebuena.dev/einherjar/docs).
No module-level ADRs for v1.0.0 — all design decisions were consistent with
existing framework principles (ADR-001 through ADR-003 from `core`, framework
ADRs 001004 from `docs`). No contested choices required a record.
Decisions worth noting (not ADR-worthy individually):
| Decision | Outcome | Rationale |
|---|---|---|
| `RateLimiterStore` as interface (not concrete) | Interface only; `InMemoryRateLimiterStore` as default | Allows `cache-valkey` to satisfy it via duck typing without cross-module import |
| Fail-open rate limiting | Request allowed on store error | Availability preferred over hard enforcement during infrastructure degradation |
| `last_seen` excluded | Not in v1.0.0 | Application-domain concern; not transport-level middleware |
| UUID v7 for request IDs | v7 with v4 fallback | Time-ordered IDs sort chronologically in logs; fallback ensures generation never fails |
| `observability.Checkable` not redefined | Imported from contracts | Starters implement contracts directly; no web import needed by db/cache starters |
| Background goroutine for in-memory eviction | `time.Ticker` goroutine | Avoids `worker` module dependency; in-memory store is self-contained |