# The Six Things We Didn't Build: What the Framework Teaches

> Six things gortex deliberately left unbuilt, from a full graceful-shutdown chain to OpenAPI generation, and the principles the whole series teaches.

Source: https://sheng.page/en/posts/gortex-what-we-didnt-build/ · Published: 2026-06-14 · Author: Sheng

Part of a research project's value is what it deliberately left out. This closing post inventories gortex's six unbuilt directions and what the whole framework teaches.

## The six unbuilt directions

Gortex's own design doc lists six "worth doing, not done" directions. What they share: each looks one mile away, and each hides production-grade devils in the detail.

1. **The full graceful-shutdown chain**: The hub in [The WebSocket Hub: Go's Take on the Actor Model](/en/posts/gortex-websocket-actor-hub/) already has basic graceful shutdown, but production-grade also needs draining (remove from the load balancer first — K8s preStop + readiness flipped false — then let in-flight requests finish), shutdown ordering (HTTP listener → drain → WS Hub → DB pool), and propagating the OS signal's deadline down to every subsystem.
2. **Error semantics in the middleware chain**: today a middleware returns an `error` and `ServeHTTP` handles it uniformly. A production framework also distinguishes retryable vs non-retryable, should-log vs already-handled, and the error boundary — once a layer swallows an error, can the layers above still observe it?
3. **Config hot-reload**: `pkg/config` loads once. A K8s ConfigMap is updated into the mounted directory, so an `fsnotify` watch would let you change log level, rate-limit thresholds, or feature flags without a restart.
4. **Request-level timeout propagation**: there's no "global request timeout" today. A production framework usually sets a deadline context in the outermost middleware, so a handler that forgets a timeout still can't hang forever (the per-check timeout in [Observability: Three-State Health Checks and a Tracing Abstraction](/en/posts/gortex-observability-health-tracing/) is a local version of the same move).
5. **Structured errors**: `pkg/errors` is a flat code→message registry today. A more advanced design has error-code namespaces, error chains, and i18n:

```go
// today: a flat code → message
// wanted: namespaces + layered matching
err := errors.New("ORDER.PAYMENT_FAILED", ...)
if errors.Is(err, order.ErrPayment) { /* handle by layer */ }
```

6. **OpenAPI / Swagger generation**: `core/app/doc/` has a `swagger.Provider` skeleton, but spec generation isn't finished. Deriving an OpenAPI spec from struct tags + method signatures is the natural extension of the declarative routing in [Struct-Tag Routing: reflect in Practice](/en/posts/gortex-struct-tag-routing/).

## What the framework teaches

Read the whole series backwards and a few recurring principles surface:

- **The answer is context-dependent**: a kitchen-sink is over-engineering for a microservice and a governance tool for a platform team ([Why Build a Kitchen-Sink Framework](/en/posts/gortex-why-kitchen-sink-framework/)). No architecture is universal.
- **Pay the cost once**: reflect scans routes and builds the tree at startup, so the hot path is zero-allocation and reflection-free (the routing and zero-allocation posts, from struct-tag routing through [The Segment-Trie Router: Matching Without Regex](/en/posts/gortex-segment-trie-router/) to [The Zero-Allocation Secret: Context Pool and smartParams](/en/posts/gortex-zero-allocation-context/)). Push expensive work to the boot moment.
- **Pick the tool by the shape of the data**: high-frequency counting wants atomics and sharded locks ([Concurrent Counting and Resilience: Sharded Metrics and a Circuit Breaker](/en/posts/gortex-metrics-and-circuit-breaker/)); an owned-state event stream wants a channel (the WebSocket Hub post). Don't reach for a channel reflexively, and don't lock everything.
- **Program to interfaces**: swapping tracing to OTel or a health check to another implementation touches no business code (the observability post, [Interfaces and Composition](/en/posts/go-interfaces-and-composition/)).
- **Fail loud, bound your inputs**: an unknown middleware or the unimplemented rbac/inject crashes at startup (the struct-tag routing post); body, JWT, and rate limit are all bounded at the boundary ([The Security Middleware Suite: CORS / CSRF / JWT / Rate Limit / Binder](/en/posts/gortex-security-middleware-suite/)).
- **Be honest about limits**: `LogResponseBody` is a no-op, `inject` only parses, `rbac` isn't implemented; saying so beats pretending to do everything.

## Closing

Gortex isn't an open-source framework out to compete with Gin or Echo; it's a reference implementation — for research and record, not production. Its value is two-sided: it lays out the decisions a platform framework has to make (where the kitchen-sink post started), and the "six things we didn't build" list is itself a map of where production-grade infrastructure actually gets hard.

Knowing what to build, what to deliberately leave out, and why — that's probably the most worthwhile thing to take from reading a framework's source.


## Notes

- The six unbuilt directions (full graceful shutdown, middleware error semantics, config hot-reload, request-level timeout, structured errors, OpenAPI generation) each stall on production-grade detail.
- The series' throughline: the answer is context-dependent, pay the cost once, pick the tool by shape, program to interfaces, fail loud + bound inputs, be honest about limits.
- Gortex is a reference implementation; its value is in demonstrating decisions and deliberate omissions, not in being a production framework.
- The "didn't build" list is itself a map of where production-grade infrastructure is hard.
- The most worthwhile takeaway from reading a framework: knowing what to build, what to skip on purpose, and why.

Source: [yshengliao/gortex](https://github.com/yshengliao/gortex).

---

> Ideas and technical judgement by Sheng; drafted with Claude · Go 1.25 (gortex go.mod) · compiled retroactively · part of the 2026-06-13 blog renovation, paint still drying.