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.
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.
dotnet add package EvaluatedApplications.EvalAppIt'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.
install, then this
dotnet add package EvaluatedApplications.EvalAppusing 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.
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.
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.
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.
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.
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.
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.
what you get
Eval.App(...) → domains → tasks → steps, compiled once into an immutable, reusable ICompiledPipeline<T>.
Declare Network / DiskIO / Cpu / Database (or a named custom pool) and every step that touches it contends for the same bound.
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.
Bounded per-item fan-out over a collection (Tunable.ForItems() lets the tuner pick the count) and fixed concurrent branches with configurable merge strategies.
Chain steps with an undo; when a later step fails, every prior step's compensation runs in reverse (LIFO) order.
Retry, timeout, circuit breaker, timing, audit, and validation, composable around any step.
PipelineResult<T> is Success / Failure / Skipped, pattern-matched instead of caught.
The same cross-product license check every Evaluated Applications package embeds, entirely offline (ECDsa P-256, no network call, ever).
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 it | Data-driven design, out of game and simulation architecture |
| A step returns success or failure and the pipeline short-circuits without throwing | Railway-Oriented Programming — Scott Wlaschin, the F# community |
| Compensating actions run in reverse when something fails partway through | The Saga pattern — Garcia-Molina & Salem, 1987 |
| The builder's return type changes with every call, so invalid orderings don't compile | The .NET fluent-builder convention plus the type-state pattern |
| Probe a neighbouring value, measure, keep it or revert | Hill-climbing / adaptive concurrency, out of the auto-tuning literature |
| A persistent belief about what works best, updated as evidence comes in, that survives a restart | Bayesian bandits / Thompson sampling, out of the multi-armed-bandit literature |
| Domains map to bounded contexts; naming follows how the business actually talks | Domain-Driven Design — Eric Evans, 2003 |
honest scope
Long-running workflows that must survive a process restart over hours or days are Temporal's job, not EvalApp's.
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.
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.
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 →.