/// the philosophy, for engineers
“Auto-detect application patterns, explicitly wire platform capabilities.”
One sentence drives every API in the framework. This page unpacks it: the mental model, the feedback loop it buys, the pipeline it produces, the batteries it ships — and the opinions underneath, every one of them written down and arguable.
Prefer long-form prose? Why Elarion, in the docs.
Your code already says it — or you say it once.
What the code already states is detected at build time. What only you can decide is declared once, as an attribute. Nothing is guessed at runtime.
- A class implementing IHandler<,> has said it handles that request — repeating it in a registration list creates a second model that drifts.
- Cache duration, retry policy, permissions are decisions — and decisions deserve syntax the reviewer can see.
- The third option — guessing at runtime — is banned outright.
Move every failure to the left.
The cost of a mistake is a function of when you learn about it. Compile-time wiring moves whole failure classes to where they are cheapest — determinism is a budget decision, not an aesthetic one.
- A red squiggle costs seconds; a failed build, minutes; a production incident, a post-mortem.
- Missing wiring, unroutable endpoints, authorization that can't fail closed — build errors.
- Contract drift fails CI the moment the generated client stops type-checking.
Cross-cutting concerns, cut once.
Every request — JSON-RPC, REST, MCP, a scheduled job, an event — runs the same decorator pipeline around your handler.
- Authorization sits outermost: a denied caller never warms a cache or opens a transaction.
- Domain events ride your transaction; integration events wait for the commit.
- The handler stays a plain function: request in, Result<T> out — it has no idea HTTP exists.
Each stage attaches only when the handler asks for it — a bare handler compiles to a bare call. And because it's all generated source, you can read the exact chain in your build output.
Batteries included. Sockets standard.
Frameworks make you choose: batteries included — plus a dependency graph you didn't order — or bring-your-own-everything. Elarion's answer is the socket.
- The seam and its decorator live in the dependency-light core; the battery lives one opt-in package away.
- A service that never caches never ships a cache.
- Every seam is public — swap any battery without touching a handler.
References Microsoft.Extensions *.Abstractions packages and nothing else. Every capability below is a public seam here — the attribute and the decorator live in the core; the heavy dependency hangs off a socket.
HybridCache, two tiers
In-process L1 plus a distributed L2. The recommended L2 is a PostgreSQL UNLOGGED table — reuse the database you already run instead of operating Redis.
Elarion.Caching · .PostgreSql
Polly, when asked
[Resilient] handlers and deferred scheduler retries run named retry/timeout pipelines. Polly enters your build the day a handler asks — not before.
Elarion.Resilience
OpenFeature underneath
[FeatureGate] works against any OpenFeature provider. Microsoft.FeatureManagement ships as the batteries-included, config-driven default.
Elarion.FeatureFlags.*
A transactional outbox
Events recorded in the same transaction as your data, delivered at-least-once after commit. An in-memory bus for development — same interface.
Elarion.Messaging.Outbox · .InMemory
Streaming blobs in Postgres
Streaming-first storage with direct and resumable (tus) uploads and a pending → commit lifecycle for attach-then-reference flows.
Elarion.Blobs.*
Runtime settings, watchable
Global and per-user key/value settings in EF Core with optimistic concurrency, surfaced live through IConfiguration and IOptionsMonitor.
Elarion.Settings.*
Your implementation
Implement the seam, register it, done — no handler, attribute, or generated line changes. Unplug ours, plug in yours.
hover a battery to unplug it — that's the point
Opinions, on the record.
A framework is a set of opinions with a package manager. These are the strongest ones — each backed by a written decision record that states the alternatives we rejected and why. Disagree with one? Good: argue with the ADR, not with vibes.
Events split on the transaction, not on a verb
Domain events commit with the command that raised them; integration events deliver after commit. The phase is the API — not an afterthought.
The event bus is pub/sub-only
A reply is a typed call with a compile-time contract. Request/reply over a bus is a runtime surprise wearing a messaging costume.
Start modular, not distributed
Modules are mini bounded contexts with analyzer-enforced walls. Extraction into a service is a graduation along existing contract lines — never a rewrite.
No repository pattern
Handlers inject the concrete DbContext — no repository, not even a context interface. EF Core is already the abstraction; wrapping it buys ceremony, not portability, and leaks the moment you need raw SQL.
Idempotency fences on a database row
An INSERT … ON CONFLICT inside your transaction is the lock. No distributed lock service to run, no fencing tokens to get wrong.
A cache may live in Postgres
An UNLOGGED table behind HybridCache beats operating Redis for most applications. Worst case after a crash is a cold cache — never lost data.
Heavy dependencies are opt-in
The core references Microsoft.Extensions abstractions only. Polly, HybridCache, and OpenFeature arrive when a handler asks for them.
Generated code answers to the framework’s name
Generated infrastructure is framework-named, deterministic, and readable — magic you can diff in code review, not magic you must trust.
Now see it in code.
The front page has the meat — real handlers, the generated output, a five-minute quickstart. The docs carry the long-form rationale. And if the people who hold the budget need convincing, there's a page in their language too.