/// 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.

01 / The mental model

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.
YOUR CODE ALREADY SAYS ITclass GetClient : IHandler<Query, Result<…>>record Command { [EmailAddress] Email … }[ConsumeEvent] OnOrderShipped : IHandler<…>[EntityConfiguration] InvoiceConfigurationdetected at build — the wiring is generatedYOU SAY IT ONCE, EXPLICITLY[Cacheable(Duration = "00:05:00")][Resilient("external-api")][Idempotent][RequirePermission("invoices", "write")]declared — the decorator attaches in the pipeline✗ never: runtime assembly scanning · convention guessing · reflection magic
02 / The feedback loop

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.
CHEAP TO FIXEXPENSIVEas you typedotnet buildCIdeploy3 a.m.WITH ELARIONcross-module reachELMOD002, in the IDEmissing wiring · no verb ·auth that can’t fail closedcontract drift — the generatedclient stops type-checkingWITH RUNTIME DISCOVERYmissing registration —found at startupforgotten auth check —found by an incident
03 / The pipeline

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.
JSON-RPCREST · MCPjobs · eventsone pipelinealways ontracing[RequirePermission]authorization[FeatureGate]feature gateDTO attrs → autovalidation[Resilient]resilienceICommand → autotransaction[Cacheable]cachingyour handlerbusiness logic onlydomain events — inline, same transactionintegration events — after commit, via the outbox

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.

04 / Batteries

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.
Elarion coreElarion + Elarion.Abstractions

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.

IHandlerCache

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

IResiliencePipelineRunner

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

IFeatureFlagService

OpenFeature underneath

[FeatureGate] works against any OpenFeature provider. Microsoft.FeatureManagement ships as the batteries-included, config-driven default.

Elarion.FeatureFlags.*

IIntegrationEventBus

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

IBlobStore

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.*

ISettingsStore

Runtime settings, watchable

Global and per-user key/value settings in EF Core with optimistic concurrency, surfaced live through IConfiguration and IOptionsMonitor.

Elarion.Settings.*

any seam

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

05 / Opinions

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.

the full record, 0001 → 0023 — every spine opens the decision on GitHub. The tall ones are argued below.

ADR-0001decided · on file

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.

ADR-0010decided · on file

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.

ADR-0008decided · on file

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.

ADR-0007decided · on file

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.

ADR-0021decided · on file

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.

ADR-0020decided · on file

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.

ADR-0017decided · on file

Heavy dependencies are opt-in

The core references Microsoft.Extensions abstractions only. Polly, HybridCache, and OpenFeature arrive when a handler asks for them.

ADR-0018decided · on file

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.