Stratara

July 18, 2026 · View on GitHub

Stratara

Stratara

CQRS and Event Sourcing for .NET — with tamper-evident streams and tenant-aware encryption built in.

CI NuGet License: MIT Docs .NET 10 GDPR Art. 17 — crypto-shredding


Stratara is the integrated CQRS, Event Sourcing, and audit stack you'd otherwise compose yourself from three or four libraries. Mediator, outbox, event store, sagas, projections, and identity — all wired together, lockstep-versioned across 25 NuGet packages for .NET 10. Opt in à la carte.

License: Stratara ships under the MIT License (LICENSE) — OSI-approved open source, free for any use including commercial.

Why Stratara

🔒 Tamper-Evident by Design — Every event stream is hash-chained by a background worker, and periodic anchors pin the chain head to a source of truth outside your infrastructure. Edit a row directly in Postgres and the chain no longer recomputes: a verification pass — your audit job, or the anchor check — names the exact sequence where it broke. The framework preserves the evidence and leaves the schedule to you; it does not scan for tampering on its own. Audit-grade evidence, not a "trust the DBA" promise. (Concept · Hero Sample)

🛡️ Tenant-Aware Encryption[EncryptData] fields are sealed with AES-GCM and an authentication tag bound to the tenant id as Associated Data. A row leaked from one tenant cannot be decrypted in another tenant's session — even with the correct master key. The tenant binding is cryptographic, not a WHERE tenant_id = … you hope nobody forgets. (Concept · Hero Sample)

⚖️ GDPR Article 17 by Construction — Append-only event sourcing and the right to erasure are natural enemies: you cannot delete an immutable event. Stratara's answer is crypto-shredding — each subject's data is encrypted under a destroyable per-scope key, and a single EraseScopeAsync call shreds that key so every copy — events, snapshots, replicas, and backup tapes you can't even reach — becomes undecryptable noise. Erasure without rewriting history, provable on a 30-day regulatory clock. The same per-subject-key model underwrites SOC 2 / ISO 27001 key-lifecycle controls and HIPAA per-patient separation. (Concept)

🚦 Tenant Isolation at the Door — Beyond field-level encryption: a request that names a tenant other than the caller's is rejected at the mediator entrance, before your handler runs — opt-in via the ITenantScopedRequest marker. A strict mode routes every privileged cross-tenant operation (a platform admin acting on another tenant's data) through one auditable ICrossTenantAuthorizer that denies by default. The command-/query-side complement to the database tenant filters, so isolation isn't a WHERE tenant_id = … each handler has to remember. (Guide)

🧩 Integrated, not Assembled — Mediator + Outbox + Event Store + Sagas + Projections + Identity, lockstep-versioned across 25 packages. One <VersionPrefix> bump moves everything together. No multi-library composition tax, no version-skew puzzles, no integration tests to prove your bus and your event store still see eye-to-eye.

Fast by Default, Scales Horizontally — Reflection-free hot paths: commands, event replay, and projection dispatch run through compiled expressions, not MethodInfo.Invoke. Projections are push-driven — subscribed to the event bus, never polling a table. And every stream maps to one of 4096 deterministic buckets, so command, projection, and saga workers scale out as competing consumers across N nodes on RabbitMQ or Azure Service Bus. The measured numbers — replay throughput, reflection-free property access, constant-memory rebuilds — are in Performance & horizontal scale just below.

Performance & horizontal scale

Numbers, not adjectives — measured with BenchmarkDotNet on a fanless MacBook Air M4 (Apple M4, .NET 10, Arm64 RyuJIT). Passively-cooled laptop hardware, so read these as conservative ratios, not a tuned server's ceiling. Re-run them yourself: dotnet run -c Release --project tests/Stratara.Benchmarks -- --filter '*'.

Replay stays cheap — and flat on memory. In-memory aggregate replay through the compiled-expression apply-dispatch (excludes the database read a real rehydration adds):

Events replayedTimeAllocated
10,0000.11 ms64 B
100,0001.13 ms64 B
1,000,00011.6 ms64 B

~11 ns per event — and the allocation stays constant at 64 bytes no matter how long the stream is. A million-event aggregate rebuilds without producing garbage.

Reflection-free hot paths. Commands, event-apply, projections, and sagas dispatch through compiled Expression delegates, never MethodInfo.Invoke. The same machinery drives property access:

Property accessReflectionCompiled delegateSpeedup
Write6.04 ns0.47 ns~13× faster
Read3.86 ns0.62 ns~6× faster

Both allocation-free. Source-generated logging adds ~0.5 ns / 0 B when its level is off (classic interpolated logging allocates regardless); tamper-evident chain hashing runs sub-microsecond per event (hardware-accelerated SHA-256). Full methodology and the honest caveats live in Performance & Scaling.

Scales horizontally. Commands flow through a message bus to competing-consumer workers — you add nodes, not bigger machines. Stream ids partition deterministically across 4096 buckets (single-writer per aggregate, full parallelism across aggregates), and projections are pushed from the event bus, never polled.

flowchart LR
    A1[API / host 1] --> BUS
    A2[API / host 2] --> BUS
    A3[API / host N] --> BUS
    BUS{{"Message Bus<br/>RabbitMQ / Azure Service Bus"}}
    BUS -->|competing consumers| C1[Command Worker 1]
    BUS --> C2[Command Worker 2]
    BUS --> C3[Command Worker N]
    C1 --> ES
    C2 --> ES
    C3 --> ES
    ES[("Event Store<br/>4096 stream buckets")] -->|pushed event bundles| P1[Projection / Saga Worker 1]
    ES --> P2[Projection / Saga Worker N]
    P1 --> RM[(Read Models)]
    P2 --> RM

Why we share this

Stratara is the integrated CQRS / Event Sourcing / audit stack we built for our own products — the wiring that production event-sourced apps tend to write from scratch, plus the tamper-evident and tenant-aware-encryption properties we wanted as a default, not as an enterprise-tier add-on.

We're publishing it because the .NET ecosystem deserves these primitives without the composition tax of stitching together Marten + Wolverine + MassTransit + your own crypto layer. Compliance-relevant integrity (GDPR Article 17 via crypto-shredding, SOC 2 audit-trail, HIPAA data integrity) should not be locked behind a license tier — it should be how the storage layer works by default.

That's why Stratara ships under the MIT License — true open source, free for any use including commercial, with no competition clause and no time-delay. Take what you need, fork it, build on it.

Documentation

Full docs, conceptual overview, getting-started walkthrough, guides, samples and the auto-generated API reference live at docs.stratara.tech.

Using an AI assistant? Stratara ships an llms.txt at the repo root — a machine-readable index of the core interfaces, routing conventions, tier rules, and package map, following the llms.txt convention. Point your assistant at it, or connect any MCP-capable client to gitmcp.io/yesbert/Stratara (which reads llms.txt first) so it grounds on current Stratara APIs instead of stale training data.

Install

# Minimum: in-process mediator + pipeline behaviors
dotnet add package Stratara.Mediator

# Event-sourced apps with the full stack
dotnet add package Stratara.EventSourcing.EntityFrameworkCore
dotnet add package Stratara.EventSourcing.WorkerDefaults
dotnet add package Stratara.Outbox.RabbitMQ

Hello-mediator in five lines:

var builder = WebApplication.CreateBuilder(args);

// The mediator traces each dispatch — AddMediator() does not register the Tracer for you.
builder.Services.AddSingleton(TracerProvider.Default.GetTracer("MyApp"));
builder.Services.AddMediator();
builder.Services.AddCommandHandlersFromAssemblyContaining<Program>();

var app = builder.Build();

// IMediator is scoped — resolve it from a scope, not from app.Services.
using var scope = app.Services.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.HandleAsync(new MyCommand(), CancellationToken.None);

Package map

Lockstep-versioned NuGet family — every package in the table below ships at the same <VersionPrefix>, bumped together (Microsoft.Extensions.* convention).

TierPackagePurpose
AStratara.AbstractionsContract interfaces + POCO records (no implementation)
AStratara.ContractsWire-level POCO contracts
AStratara.DiagnosticsActivitySource / Meter / log-event-ID schema
AStratara.ResiliencePolly named pipelines
BStratara.SessionsActor / Subject session model + ASP.NET middleware
BStratara.MediatorIn-process mediator + pipeline behaviors
BStratara.DomainTenant aggregate + lifecycle events
BStratara.SharedUmbrella re-export of A/B abstractions + source-generated logger extensions
BStratara.ServiceDefaultsOpenTelemetry + Serilog defaults
CStratara.EventSourcing.EntityFrameworkCoreWrite / read / identity stores on PostgreSQL
CStratara.EventSourcing.Pipeline.CommandAuditCommand-audit pipeline behavior
CStratara.ValidationVendor-neutral IValidator<T> + validation pipeline behavior
CStratara.EventSourcing.WorkerDefaultsWorker-host wiring composites
CStratara.ProjectionsProjection runtime
CStratara.SagasSaga runtime
CStratara.SecurityKey store (KEK-wrapped versioned DEKs) + AES-GCM envelope encryption
CStratara.Outbox.RabbitMQOutbox + RabbitMQ-backed IMessageBus
CStratara.Outbox.AzureServiceBusOutbox + Azure Service Bus-backed IMessageBus
CStratara.InfrastructureCross-cutting infrastructure glue
CStratara.Identity.CoreChannel-agnostic identity primitives
CStratara.Identity.AspNetCoreChannel-agnostic ASP.NET Core identity wiring (sign-in manager wrapper + membership tenant-claim bridge + i18n + email-sender stub)
CStratara.Identity.EntityFrameworkCoreIdentity directory: user↔tenant membership, membership-backed authorization (roles + permissions), scoped settings store
CStratara.ServiceDefaults.AspNetCoreASP.NET health checks + request OpenTelemetry
Stratara.TestingTest doubles (in-memory key store / message bus / session) + given/when/then aggregate harness — reference from test projects only
Stratara.Testing.EntityFrameworkCoreRun the real event-sourcing write stack on in-memory SQLite (EventStoreTestHost) — reference from test projects only

Tier order: a Tier-N package may only reference Tier-(≤N). Tier-A has no inbound dependencies from B or C.

Quick start

The fastest path is to run the samples under samples/ — each is self-contained, runs in under a second, and reads top-to-bottom in 5–20 minutes.

🌟 Hero Samples — see what makes Stratara different

SampleWhat it proves
Stratara.Sample.TamperProofHash-chained event streams catch direct-DB tampering at the next verifier pass
Stratara.Sample.EncryptionTenant-bound AAD makes cross-tenant decryption cryptographically impossible

📚 Learning Path — five samples on a shared bank-account domain

#SampleConcept
1Stratara.Sample.CqrsBasicsIMediator + ICommand / IQuery + handler discovery
2Stratara.Sample.EventSourcedEvent-sourced aggregate + read-side projection
3Stratara.Sample.OutboxWorkerOutbox + message bus + background worker
4Stratara.Sample.MoneyTransferSagaSaga / process manager
5Stratara.Sample.AspNetCoreApiHTTP minimal-API → mediator wiring

🧩 Pipeline behaviors

SampleConcept
Stratara.Sample.ValidationIValidator<T> running before the handler — errors block, warnings pass through

🔐 Identity & access

SampleConcept
Stratara.Sample.IdentityExternal OpenID Connect sign-in + hardened JIT provisioning, API keys / PATs, auth-scheme selector
Stratara.Sample.IdentityDirectoryTenant membership with per-membership roles, [RequirePermission], and scoped settings
dotnet run --project samples/Stratara.Sample.TamperProof

Each sample has a step-by-step walkthrough under docs.stratara.tech/samples.

Build from source

Requires the .NET 10 SDKglobal.json pins the version; dotnet --version should report 10.0.x.

# Build the publish solution filter (every packable csproj + tests)
dotnet build Stratara.Publish.slnf -c Release

# Run the test suite (xUnit v3 with Microsoft Testing Platform)
dotnet test

Versioning

Lockstep across the whole family — one <VersionPrefix> in Directory.Build.props controls every package. Tag-driven builds (v*) publish stable versions; main-branch pushes publish {VersionPrefix}-preview.{BuildId} pre-releases. SemVer applies — see CHANGELOG.md for per-release notes.

License

MIT — see LICENSE.

Stratara is OSI-approved open source. You may use, copy, modify, and distribute it for any purpose — including commercial — subject only to the MIT License's attribution requirement.

Contributing

Issues, questions, and ideas are genuinely welcome — they shape where Stratara goes next:

  • Bug reportsopen an issue with the bug template.
  • Feature ideas & questions — open an issue with the question template (check docs.stratara.tech first).
  • Security issues — see SECURITY.md; please don't file a public issue.

A note on the workflow: this GitHub repository is a one-way mirror of an internal Azure DevOps source-of-truth, force-pushed as a squashed commit per release. We can't merge pull requests here — they'd be overwritten on the next sync — so please open an issue instead. Good ideas make their way in through the internal repo, with credit.

Full details on the contribution model: CONTRIBUTING.md. Community standards: CODE_OF_CONDUCT.md. Getting help: SUPPORT.md.