Architecture
Ligero's design enforces SOLID principles structurally, not aspirationally:
- SRP — path normalization lives only in
PathNormalizer; matching only inRouteTrie; dispatch/404/405 semantics only in the pipeline. - OCP — cross-cutting features are middleware. CORS, auth, static files, metrics: none of them required touching the core.
- LSP —
HttpRequest/HttpResponse/Contextcontracts never depend on the concrete implementation (noinstanceoffeature toggles). - ISP — small, focused interfaces (
Handler,Middleware, the SPIs). - DIP — the core depends on abstractions; engines, JSON, templates and
metrics are adapters resolved via
ServiceLoaderor explicit injection.
┌────────────────────────────┐
your app ────► │ ligero-core (public API) │
│ Ligero, Router, Context, │
│ Middleware, SPIs │
└─────────────┬──────────────┘
│ SPIs (ServiceLoader / injection)
┌── ─────────────┬─────────┴───────┬──────────────────┐
▼ ▼ ▼ ▼
server engines ligero-json ligero-template-* MetricsCollector
(jdk, jetty) (Jackson) (mustache) (micrometer)
Request lifecycle
- The engine adapts the native request/response and calls the root handler.
- A
Contextis created (path normalized, context path stripped). - The middleware chain runs in registration order.
- The terminal dispatcher matches the route trie, injects path parameters and invokes your handler — or produces 404/405/OPTIONS semantics.
- Any exception is mapped: custom handlers →
HttpExceptionstatus → opaque 500. Uncommitted responses are finalized.
Performance notes
- Route matching: segment trie, O(path length) — ~140–215 ns/op with 301
routes registered (JMH, see the
benchmarksmodule). - Virtual thread per request: blocking I/O in handlers does not tie up carrier threads.
- No reflection in the hot path (also keeps GraalVM native-image viable).
The full engineering history — the viability analysis and the phased roadmap this framework was rebuilt against — lives in the main repo: ARCHITECTURE_ANALYSIS.md and ROADMAP.md.