docs(httpserver): correct tier from 4 to 3

httpserver depends on launcher (Tier 2), placing it at Tier 3.
With launcher corrected from Tier 5 to Tier 2, httpserver's tier
drops accordingly.
This commit is contained in:
2026-03-19 13:39:19 +00:00
commit 1ec0780f72
15 changed files with 750 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
# ADR-002: Embedded chi.Router in HttpServerComponent
**Status:** Accepted
**Date:** 2026-03-18
## Context
`HttpServerComponent` must expose routing methods to callers so they can register routes before the server starts. There are two design options:
1. **Wrapper methods** — define `Get(pattern, handler)`, `Post(...)`, `Route(...)`, `Mount(...)`, `Use(...)`, etc. on `HttpServerComponent` explicitly, delegating each to an internal router.
2. **Embedded router** — embed `chi.Router` directly in the interface and the concrete struct, so all chi routing methods are promoted to the component surface automatically.
The application bootstrap pattern in this project registers routes in a `lc.BeforeStart(...)` hook, after `db.OnInit` has run but before `srv.OnStart` binds the port. This means the same value returned by `httpserver.New(...)` is used both as a `launcher.Component` (lifecycle) and as a router (route registration). Callers write:
```go
srv := httpserver.New(logger, cfg, httpserver.WithMiddleware(...))
lc.Append(db, srv)
lc.BeforeStart(func() error {
srv.Get("/health", healthHandler)
srv.Route("/api/v1", func(r chi.Router) { ... })
return nil
})
```
## Decision
Embed `chi.Router` directly in both the `HttpServerComponent` interface and the `httpServer` concrete struct. The interface is declared as:
```go
type HttpServerComponent interface {
launcher.Component
chi.Router
}
```
The struct embeds `chi.Router` as an anonymous field, initialized to `chi.NewRouter()` in `New()`.
This means callers receive the complete chi routing API — `Get`, `Post`, `Put`, `Delete`, `Patch`, `Head`, `Options`, `Route`, `Mount`, `Use`, `With`, `Group`, `Handle`, `HandleFunc`, `Method`, `MethodFunc`, `Connect`, `Trace`, `NotFound`, `MethodNotAllowed` — without any wrapper boilerplate and without any risk of an incomplete wrapper missing a method.
The compliance test (`compliance_test.go`) asserts at compile time:
```go
var _ launcher.Component = httpserver.New(...)
var _ chi.Router = httpserver.New(...)
```
## Consequences
- `chi` is a visible part of the `HttpServerComponent` API. Callers using `Route(...)` or `Mount(...)` must import `github.com/go-chi/chi/v5` for the callback type. This is intentional and explicit, not a leaky abstraction — httpserver is documented as chi-backed.
- A future swap to a different router would be a breaking change to `HttpServerComponent`. This is accepted: the router choice is a deliberate, long-term decision (see ADR-001).
- Writing wrapper methods would have to be updated every time chi adds a new method. Embedding avoids that maintenance burden permanently.
- Middleware is applied to the embedded router in `OnInit()` by calling `s.Router.Use(mw)` for each registered middleware, preserving chi's standard middleware chaining semantics.