Alberto 0.1.4
Prefix ReservedSee the version list below for details.
dotnet add package Alberto --version 0.1.4
NuGet\Install-Package Alberto -Version 0.1.4
<PackageReference Include="Alberto" Version="0.1.4" />
<PackageVersion Include="Alberto" Version="0.1.4" />
<PackageReference Include="Alberto" />
paket add Alberto --version 0.1.4
#r "nuget: Alberto, 0.1.4"
#:package Alberto@0.1.4
#addin nuget:?package=Alberto&version=0.1.4
#tool nuget:?package=Alberto&version=0.1.4
<img src="https://raw.githubusercontent.com/codest-be/alberto/main/icon.png" alt="" width="96" align="right">
Alberto
Early release, under active testing. Alberto is in its first public
0.xversions. The suite is green, but nobody except its author has run it in anger. Evaluate it and experiment with it; do not put it in front of production traffic yet. The API will break before 1.0. See Project status.
An event store for .NET where the consistency boundary is a query, not an aggregate.
// "Nobody else may have touched seat A12 of this show while I was deciding."
var boundary = DcbQuery.ByAllTags(
new EventTag("show", showId.ToString()),
new EventTag("seat", "A12"));
await store.Handle(new ReserveSeat(showId, "A12", customerId))
.Load(boundary, new SeatState(), Seat.Apply)
.Decide((cmd, state) => state.IsTaken
? Problem.Create("seat.taken", $"Seat {cmd.Seat} is already reserved.")
: Decision.Succeed(new SeatReserved(cmd.ShowId, cmd.Seat, cmd.CustomerId)))
.Commit(ct);
That block loads exactly the events the decision depends on, folds them into state, decides, and appends, refusing the append if anything matching that same query landed in between. No aggregate root, no stream to pick in advance.
Start here → docs/getting-started.md. A runnable 60-line sample, no database required.
Why DCB
Classical event sourcing makes you choose a stream per event before you know what your decisions will need. That choice is hard to undo, because the stream is simultaneously your storage layout, your consistency unit, and your replay unit. Two rules that need to see the same event from different angles force you into either duplicated events, saga choreography, or one big coarse-grained aggregate that serialises unrelated work.
Dynamic Consistency Boundaries (DCB) split those jobs apart. Events are written once to a single ordered log and tagged with every concept they concern:
[EventType("seat-reserved")]
public sealed record SeatReserved(
[property: Tag("show")] Guid ShowId,
[property: Tag("seat")] string Seat,
[property: Tag("customer")] Guid CustomerId) : IEvent;
Each decision then declares its own boundary as a query over those tags. "This seat at this show" and "everything this customer has ever booked" are both first-class boundaries over the same event, with no duplication and no coordination between them. Two people reserving different seats never contend; two people reserving the same seat always do.
Why Alberto specifically
- Postgres, and nothing else. The whole store is a handful of tables and functions in the database your application already has. No broker, no separate event-store server. The boundary check and the append run in one transaction under a transaction-scoped advisory lock, so a conflicting concurrent write is rejected rather than interleaved.
- A real async pipeline, not a
foreach. One control loop per module reads batches, dispatches through a middleware chain, retries with exponential backoff, dead-letters poison events, and splits a failing batch to isolate the one event that broke. - Zero-downtime projection rebuilds. Change how a projection reads history, then replay the whole log into a shadow copy while the live one keeps serving reads, and swap them in one transaction. Driven by the CLI, executed by your running application. See docs/projections.md.
- Multi-tenancy that reaches the SQL. Tenant isolation is enforced in the queries and the leases, not by a filter you might forget. See docs/multi-tenancy.md.
- An outbox. A processor turns committed events into outbox rows on the same pipeline as your
projections, and a relay claims them with
FOR UPDATE SKIP LOCKED. Messages are derived from events that are already durable, so at-least-once delivery needs no distributed transaction. See docs/reactors-and-outbox.md. - An operator CLI. Inspect checkpoints, events, projections, dead letters and tenant leases;
rewind a processor; retry or dismiss dead letters; run a rebuild. Mutating commands confirm before
they act and most of them take
--dry-run; every command that reports takes--json, so the tool you use interactively is the one your runbooks call. See docs/operations.md. - OpenTelemetry throughout. Traces across the append→consume seam, and metrics for lag, conflicts, retries and dead letters.
Install
Packages are on nuget.org. Take the core plus one backend:
dotnet add package Alberto
dotnet add package Alberto.Postgres
| Package | What it gives you |
|---|---|
Alberto |
Event store abstractions, control loop, middleware, projections, tenancy |
Alberto.Commands |
The AlbertoStore command pipeline (Handle → Load → Decide → Commit) |
Alberto.InMemory |
In-memory backend, checkpoint, dead-letter and state stores, for dev and tests |
Alberto.Postgres |
PostgreSQL backend, migrations, leases |
Alberto.EntityFramework |
EF Core-backed projections |
Alberto.Messaging |
Transactional outbox abstractions |
Alberto.Messaging.Postgres |
PostgreSQL outbox store |
Alberto.Telemetry |
OpenTelemetry tracing and metrics |
Alberto.Testing |
In-memory test helpers (InMemoryAlbertoModule, assertion extensions) |
Alberto.Testing.Xunit |
xUnit v3 test fixtures and collection definitions |
All libraries target net10.0. The operator CLI (alberto) is not a NuGet tool package; run
it from the repo with dotnet run --project tools/Alberto.Cli.
Sixty seconds
services.AddAlberto("tickets", builder => builder
.WithInMemory() // or .WithPostgres(...)
.WithEventsFrom(Assembly.GetExecutingAssembly()) // discovers [EventType] events
.AddProjection(OccupancyProjection.Declaration, _ => _ => occupancy)
.WithControlLoop(o => o with { PollingInterval = TimeSpan.FromMilliseconds(50) }));
Nothing is registered until the host starts. Declaration, configuration overlay, validation, and
service registration happen in three distinct phases; see
docs/configuration.md.
All knobs are also overridable from Alberto:Modules:{moduleKey}:{Section}:{Property} in
appsettings.json.
The full, runnable version of that program is docs/getting-started.md. It needs no Docker and no connection string.
Documentation
| Getting started | A complete runnable sample, built up piece by piece |
| Concepts | Events, tags, queries, boundaries, positions, checkpoints |
| Event schema versioning | Permanent slugs, the _version tag, upcasters and their limits |
| Projections | Declaring them, storing them, rebuilding them live |
| Reactors and the outbox | Side effects and publishing to the outside world |
| Multi-tenancy | Tenant isolation, leases, and what it costs |
| Operations | The alberto CLI, dead letters, error policy, telemetry |
| Backup and recovery | What is truth, what is derived, and what a restore invalidates |
| Configuration reference | Three-phase pipeline, all options, validation codes, custom backends |
| Async processing architecture | How the control loop actually works |
| Tenant sharding | Spreading a module's tenants over several databases |
| Message transports | Why no broker binding ships, and how to write the adapter |
| Migrating to 1.0 | Every breaking change on the road to 1.0, most recent first |
| Releasing | Versioning policy, milestones, release and backport process |
Repository layout
/src Packable core libraries
/apps Examples: Orders (run by .NET Aspire) and Payments (a library the Orders API reads from)
/tools The alberto operator CLI
/tests xUnit v3 unit + Testcontainers integration tests, and K6 load tests
Run the whole example stack (Postgres, migrations, and the Orders GraphQL API) with:
dotnet run --project apps/Alberto.AppHost
Project status
Alberto is pre-1.0 and under active testing. 0.1.0 is the first version published to
nuget.org.
- Expect breaking changes. The public API is not frozen until 1.0, and some breaks will land in the core append and projection APIs. Every one is recorded in CHANGELOG.md, with the road to 1.0 collected in docs/migrating-to-1.0.md. Pin an exact version and read the release notes before you move.
- Well tested, not yet well proven. Unit tests plus Testcontainers-backed PostgreSQL integration tests, all green. That is evidence the code does what its author intended, not that it has survived anyone else's production workload, which it has not.
- Please try it and report what breaks. Evaluation, prototypes and side projects are the workloads this release is asking for. Feedback now is worth far more than after 1.0 freezes the surface.
The multi-database tenant sharding feature is marked experimental ([Experimental("ALB9001")]
on all public sharding types), a step beyond the general pre-1.0 caveat: it ships and its tests
pass, but the API may change more sharply than the rest of the library.
The admin surface is deliberately not published. Alberto.Admin and Alberto.Admin.Postgres
build and are tested, but they stay off nuget.org until the GraphQL API, MCP server and console
that consume them ship. Releasing the abstraction at 1.0 would freeze it under semver before its
consumers exist.
Outbox claims are time-bounded and token-fenced: a relay crash leaves a recoverable processing
row, and a stale relay cannot overwrite a newer claim. Delivery remains at-least-once; see
docs/reactors-and-outbox.md.
Contributing
Issues go on the issue tracker. Before opening a pull request, read CONTRIBUTING.md, which covers the build, the public-API tracking files a change has to update, the code style, and the event deserialization rule. Participation is governed by the Code of Conduct.
Security vulnerabilities do not go on the issue tracker. See SECURITY.md.
Licence
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Configuration.Binder (>= 10.0.7)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.7)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.7)
- OpenTelemetry.Api (>= 1.15.3)
NuGet packages (8)
Showing the top 5 NuGet packages that depend on Alberto:
| Package | Downloads |
|---|---|
|
Alberto.Testing.Xunit
xUnit contract specifications for Alberto DCB backend implementations. Derive from these to run Alberto's own conformance suite against your event store, checkpoint store, state store, dead-letter store or outbox. |
|
|
Alberto.Telemetry
OpenTelemetry instrumentation for Alberto DCB event store. Provides traces and metrics for event processing. |
|
|
Alberto.EntityFramework
Entity Framework Core integration for Alberto DCB projections. Provides proper relational columns and indexes instead of JSONB storage. |
|
|
Alberto.Messaging
Transactional outbox pattern for Alberto DCB event store. Reliably publishes domain events as external messages to any transport. |
|
|
Alberto.InMemory
In-memory backend for the Alberto DCB event store: event log, checkpoint, state and dead-letter stores, all in process with no database. Intended for unit tests, samples and local development — state is discarded when the process exits. |
GitHub repositories
This package is not used by any popular GitHub repositories.