Home/Packages/EvalApp

EvalApp · foundation

Describe a process as data and steps. Stop hand-writing the coordination.

EvalApp replaces the plumbing every real async .NET codebase ends up writing by hand: a SemaphoreSlim to stop a flood of requests hammering a database, a guessed constant for "how many things run at once," a Task.WhenAll wrapped in try/catch to aggregate failures, a rollback path bolted on after something breaks halfway through. You describe a process as a plain data record and a sequence of steps; EvalApp compiles that into a single, reusable execution path, gates every resource it touches at a real bound, and — when you turn tuning on — discovers the concurrency that actually works on the hardware it's running on, instead of a number someone picked once and never revisited.

net8.0 zero dependencies AOT & trim-safe no feature gated
dotnet add package EvaluatedApplications.EvalApp

It's the shared runtime under every Evaluated Applications product, and it works standalone: no broker, no external service, no config store — add the package reference and it works from the first run.

Quick start

install, then this

dotnet add package EvaluatedApplications.EvalApp
using EvalApp; public sealed record OrderData(string CustomerName, decimal Total, string Status = "New"); Eval.App("Orders") .DefineDomain("Orders") .DefineTask<OrderData>("ProcessOrder") .AddStep("Validate", d => d with { Status = "Validated" }) .AddStep("ApplyDiscount", d => d with { Total = d.Total * 0.9m }) .AddStep("Complete", d => d with { Status = "Complete" }) .Run(out ICompiledPipeline<OrderData> pipeline) .Build(); // no license key needed — nothing is license-gated today var result = await pipeline.RunAsync(new OrderData("Alice", 99.99m));

Build the pipeline once (e.g. at startup); call RunAsync many times.

Why it's useful

one dependency instead of a stack

One dependency instead of a stack

Where you'd otherwise reach for a DI container plus MediatR plus Polly plus TPL Dataflow, EvalApp is a single compiled pipeline that covers fan-out, resource gating, retries, and compensation together.

The tuner is proactive, not reactive

A circuit breaker waits for something to break, then responds. EvalApp's adaptive tuner is continuously probing for the operating point that keeps things from breaking in the first place — independently, per instance, no coordination required. A Bayesian mode persists what it learned across restarts.

Whole categories of bugs don't compile

A saga you open has to be closed before the pipeline builds. A side-effecting step can't be added without declaring the resource it touches — there is no path to an ungated side effect.

The builder chain is the architecture

Reading the fluent declaration top to bottom reads like the process it describes, because it isn't documentation about the code — it is the code, so it can't drift out of sync the way a diagram does.

Nothing to stand up first

It's a project reference, not infrastructure. Most pipeline libraries want a broker or a database before you've processed your first item; EvalApp doesn't.

What you'd otherwise assemble

the plumbing this replaces

A SemaphoreSlim to stop a flood hitting a resource; MediatR for dispatch and pipeline behaviours; Polly for retry, timeout and circuit-breaking; TPL Dataflow for staged parallelism; a DI container to wire it together; and a hand-rolled rollback path for when something fails halfway through. Each is a solved problem in isolation — getting all of them right together, and keeping them right as a codebase grows, is the part that never stays solved. EvalApp is one compiled pipeline that covers fan-out, resource gating, retries, sagas with compensation, and adaptive tuning together.

SemaphoreSlim+MediatR+Polly+TPL Dataflow+DI container=EvalApp

Key features

what you get

Fluent pipeline builder

Eval.App(...) → domains → tasks → steps, compiled once into an immutable, reusable ICompiledPipeline<T>.

Resource-gated concurrency

Declare Network / DiskIO / Cpu / Database (or a named custom pool) and every step that touches it contends for the same bound.

Adaptive and Bayesian tuning

A hill-climbing tuner that rediscovers the optimum each run, or a Bayesian tuner with a performance model that survives restarts. Both opt-in, both tune within the bounds you declare.

Parallel ForEach and branches

Bounded per-item fan-out over a collection (Tunable.ForItems() lets the tuner pick the count) and fixed concurrent branches with configurable merge strategies.

Sagas with compensation

Chain steps with an undo; when a later step fails, every prior step's compensation runs in reverse (LIFO) order.

Middleware

Retry, timeout, circuit breaker, timing, audit, and validation, composable around any step.

Results as data, not exceptions

PipelineResult<T> is Success / Failure / Skipped, pattern-matched instead of caught.

Offline licensing facade

The same cross-product license check every Evaluated Applications package embeds, entirely offline (ECDsa P-256, no network call, ever).

None of this is invented from nothing

where the ideas come from

What EvalApp does Where the idea comes from
All state is a plain typed record; steps transform it, never own itData-driven design, out of game and simulation architecture
A step returns success or failure and the pipeline short-circuits without throwingRailway-Oriented Programming — Scott Wlaschin, the F# community
Compensating actions run in reverse when something fails partway throughThe Saga pattern — Garcia-Molina & Salem, 1987
The builder's return type changes with every call, so invalid orderings don't compileThe .NET fluent-builder convention plus the type-state pattern
Probe a neighbouring value, measure, keep it or revertHill-climbing / adaptive concurrency, out of the auto-tuning literature
A persistent belief about what works best, updated as evidence comes in, that survives a restartBayesian bandits / Thompson sampling, out of the multi-armed-bandit literature
Domains map to bounded contexts; naming follows how the business actually talksDomain-Driven Design — Eric Evans, 2003

Where it fits, and where it doesn't

honest scope

Not a durable-execution system

Long-running workflows that must survive a process restart over hours or days are Temporal's job, not EvalApp's.

Not a reactive-resilience library

Purely reactive resilience — detect a break, respond to it — is what Polly is for. EvalApp is proactive: it tunes toward the operating point that avoids the break.

The case in between

No infrastructure to stand up, throughput that matters, and concurrency that tunes itself instead of getting hand-picked once — that's what EvalApp is for.

Get started

minimal example

using EvalApp; public sealed record OrderData(string CustomerName, decimal Total, string Status = "New"); Eval.App("Orders") .DefineDomain("Orders") .DefineTask<OrderData>("ProcessOrder") .AddStep("Validate", d => d with { Status = "Validated" }) .AddStep("ApplyDiscount", d => d with { Total = d.Total * 0.9m }) .AddStep("Complete", d => d with { Status = "Complete" }) .Run(out ICompiledPipeline<OrderData> pipeline) .Build(); // no license key needed — nothing is license-gated today var result = await pipeline.RunAsync(new OrderData("Alice", 99.99m));

Build the pipeline once (e.g. at startup); call RunAsync many times. For the full walkthrough — gates, ForEach, conditional and parallel branches, sagas, DI, and tuning — see the manual.

No feature on this page is gated behind a tier or a license key. A license key changes no runtime behavior right now; the license system exists as shared, ready-to-use wiring for a possible future advanced tier, but it caps nothing today. License: proprietary — evaluation and non-commercial use are permitted, commercial use requires a commercial licence. Where it doesn't fit: long-running workflows that must survive a process restart over hours or days are Temporal's job, not EvalApp's; purely reactive resilience is what Polly is for. Want the concurrency tuner itself to keep learning live instead of using the built-in heuristic? See EvalApp.Neural →.