EventSourcingDotNet.Serialization.Json
0.31.0-pre.0.3
See the version list below for details.
dotnet add package EventSourcingDotNet.Serialization.Json --version 0.31.0-pre.0.3
NuGet\Install-Package EventSourcingDotNet.Serialization.Json -Version 0.31.0-pre.0.3
<PackageReference Include="EventSourcingDotNet.Serialization.Json" Version="0.31.0-pre.0.3" />
<PackageVersion Include="EventSourcingDotNet.Serialization.Json" Version="0.31.0-pre.0.3" />
<PackageReference Include="EventSourcingDotNet.Serialization.Json" />
paket add EventSourcingDotNet.Serialization.Json --version 0.31.0-pre.0.3
#r "nuget: EventSourcingDotNet.Serialization.Json, 0.31.0-pre.0.3"
#:package EventSourcingDotNet.Serialization.Json@0.31.0-pre.0.3
#addin nuget:?package=EventSourcingDotNet.Serialization.Json&version=0.31.0-pre.0.3&prerelease
#tool nuget:?package=EventSourcingDotNet.Serialization.Json&version=0.31.0-pre.0.3&prerelease
EventSourcingDotNet
Event Sourcing made easy.
A storage agnostic library to implement event sourced systems.
Getting Started
Define your aggregate state and ID
Aggregate ID
Create a readonly ID record struct for each of your aggregates.
It must implement the IAggregateId
interface to be accepted as an aggregate ID.
The static AggregateName
property and the AsString()
method are used to compose the stream name. You should not use dashes in your AggregateName
because some providers, e.g. Event Store DB, will split the stream name at the first dash to provide a by-category stream.
public readonly record struct MyAggregateId(Guid Id) : IAggregateId
{
public static string AggregateName => "myAggregate";
public string AsString() => Id.ToString();
}
Aggregate State
Create a state record representing the current state of your aggregate.
It must implement the generic IAggregateState<TAggregatId>
interface to be accepted as an aggregate state.
The generic type argument TAggregateId
is the aggregate ID specified above.
public sealed record MyAggregate : IAggregateState<MyAggregateId>
{
public int MyValue { get; init; }
}
Aggregate State Rules
The state record should be immutable.
Some storage or snapshot providers, e.g. the In-Memory snapshot provider, may keep a reference to the latest version of the aggregate. Mutations of the aggregate state may lead to an inconsistent aggregate state.The state record must provide a public parameterless constructor.
To create a new instance of the aggregate the aggregate repository and the snapshot providers must be able to create a new instance of the state record so that it can rely on a defined initial state prior to replay the events.
Define your events
Create a record for each and every event happening on your aggregates.
An event must implement the IDomainEvent<TAggregateId, TState>
interface to be accepted as a valid event.
Each event must implement the Apply(TState)
method to update the aggregate state.
public sealed record SomethingHappened : IDomainEvent<SomeState>
{
public SomeState Apply(SomeState state)
{
// return updated state here
}
}
Event validation
The IDomainEvent<TAggregateId, TState>
interface provides an additional Validate
method allowing to implement logic whether an event should be fired, skipped or raise an error.
The validation happens before the event is applied to the aggregate state and added to the uncommitted events.
The default implementation always returns EventValidationResult.Fire
.
public sealed record SomethingHappened : IDomainEvent<SomeState>
{
// Apply method removed for brevity
public EventValidationResult Validate(SomeState state)
{
// do some validation logic here...
return EventValidationResult.Fire;
}
}
You can return the following validation results:
EventValidationResult.Fire
The event will be applied to the aggregate state and added to the uncommitted events collection.EventValidationResult.Skip
The event will be skipped. It will not be applied to the aggregate state and not added to the uncommitted events collection.
Use this validation result when the aggregate state will not be affected by the event and you want to avoid extra effect-less events in the event streamEventValidationResult.Fail(Exception)
The event cannot be applied in the current state of the aggregate or it would lead to an inconsistent state.
The method takes an Exception parameter which expresses why this event cannot be applied.
Update Aggregate State
Aggregates are updated by getting the current version from the aggregate repository, adding some new events and then saving the aggregate back to the event store.
Avoid to run long running tasks after getting the aggregate from the repository and storing it back as it increases the risk of running into a concurrency issue.
All Event Store providers use optimistic concurrency checks to avoid update conflicts.
private readonly IAggregateRepository<SomeId, SomeState> _repository;
public async Task DoSomething(SomeRequest request)
{
var aggregate = await _repository.GetByIdAsync(request.Id);
aggregate = aggregate.AddEvent(new SomethingHappened());
await _repository.SaveAsync(aggregate);
}
Alternatively there is a shorthand extension method allowing to retrieve, update and save the aggregate in one statement:
IAggregateRepository<TAggregateId, TState>.UpdateAsync(TAggregateId aggregateId, params IDomainEvent<TState>[] events);
Configuration using Dependency Injection
The library uses Microsoft Dependency Injection. It provides a set of extension methods and builders to allow fluent dependency injection configuration. You can configure providers globally and/or by aggregate.
Single Aggregate Registration
Register an aggregate using In-Memory event storage provider:
You must add a reference to EventSourcingDotNet.InMemory package.
Provider Configuration by Aggregate:
services.AddEventSourcing(builder =>
{
builder.AddAggregate<MyAggregateId, MyAggregateState>()
.UseInMemoryEventStore();
}
Global Provider Configuration:
services.AddEventSourcing(builder =>
{
builder.UseInMemoryEventStore();
builder.AddAggregate<MyAggregateId, MyAggregateState>();
builder.AddAggregate<MyOtherAggregateId, MyOtherAggregateState>();
}
Register an aggregate using EventStoreDB storage provider:
You must add a reference to EventSourcingDotNet.EventStore package.
Provider Configuration by Aggregate:
sevrices.AddEventSourcing(builder =>
{
builder.AddAggregate<MyAggregateId, MyAggregateState>()
.UseEventStore("esdb://localhost:2113");
}
Global Provider Configuration:
sevrices.AddEventSourcing(builder =>
{
builder.UseEventStore("esdb://localhost:2113");
builder.AddAggregate<MyAggregateId, MyAggregateState>();
}
All aggregates use the same event store configuration. At this time it is not possible to configure different configurations for individual aggregates.
Aggregate Registration using Assembly Scanning
It is possible to register all aggregates in one or more assemblies with the same provider configuration.
You can use the same aggregate builder methods to configure storage providers as described above.
There are two Builder methods to collect the aggregate types using assembly scanning:
Scan(params Type[] assemblyMarkerTypes)
Scan(params Assembly[] assemblies)
Example code to configure In-Memory Storage provider for all types found using assembly scanning:
services.AddEventSourcing(builder =>
{
builder.Scan(typeof(TypeInAssembly))
.UseInMemoryEventStore()
}
It is possible to scan the assembly for aggregate types and then override the configuration of individual aggregates.
services.AddEventSourcing(builder =>
{
builder.Scan(typeof(TypeInAssembly))
.UseEventStore();
builder.AddAggregate<MyAggregateId, MyAggregateType>()
.UseInMemoryEventStore();
}
Snapshot provider configuration
You can specify a snapshot provider for each single-aggregate or assembly scanning registration. Currently there is only an In-Memory snapshot provider available.
services.AddEventSourcing(builder =>
{
builder.AddAggregate<MyAggregateId, MyAggregateState>()
.UseEventStore()
.UseInMemorySnapshotProvider();
}
Be careful when using the In-Memory snapshot provider. It keeps the current state of every aggregate in memory. Use it to avoid a full replay of all events when loading the aggregate from the repository when write performance is critical. It should not be used if there are many aggregates of the same type.
As shown in the example above it is possible to use a mix of event storage and snapshot providers.
Crypto Shredding
The Library has built-in support for crypto shredding.
Event property encryption requires an implementation of an IEncryptionKeyStore
to store the encryption keys and a ICryptoProvider
to provide a symmetric encryption.
By default, the built-in crypto provider AesCryptoProvider
is used to encrypt the values using AES algorithm and PKCS7 padding.
To encrypt a property in an event, it can be marked with an EncryptAttribute
.
public sealed record MyEvent(
[property: Encrypt] string MyValue)
: IDomainEvent<MyAggregateId, MyAggregateState>;
Or
public sealed record MyEvent: IDomainEvent<MyAggregateId, MyAggregateState>
{
[Encrypt]
public string MyValue { get; init; }
}
Encrypted property names will be prefixed with a # to indicate an encrypted value. The value is encoded using Base64 encoding.
The event MyEvent
shown above will be serialized as:
{
"#myValue": "eyJpdiI6IjJ3YXE3OFRGTTRjNkovQXUvVHdDZWc9PSIsImN5cGhlciI6ImpKOW5jaXlNTkQ1WG9wanR1b3Qxc0E9PSJ9"
}
To use the file based encryption key provider register it using extenstion method:
IServiceCollection AddFileEncryptionKeyStore(
this IServiceCollection services,
IConfigurationSection configurationSection);
Supported Data Stores
Event Storage Providers
- In-Memory
- Event Store DB
Snapshot Providers
- In-Memory
Planned Snapshot Providers
- Redis
- A variety of No-SQL Document Databases
Encryption Key Store Providers
- File Storage
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
-
net7.0
- EventSourcingDotNet (>= 0.31.0-pre.0.3)
- Microsoft.Extensions.Logging.Abstractions (>= 7.0.0)
- Newtonsoft.Json (>= 13.0.2)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on EventSourcingDotNet.Serialization.Json:
Package | Downloads |
---|---|
EventSourcingDotNet.EventStore
EventStoreDB provider for EventSourcingDotNet |
|
EventSourcingDotNet.KurrentDB
KurrentDB provider for EventSourcingDotNet |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
1.2.0-pre.0.1 | 435 | 7/21/2025 |
1.1.0 | 113 | 7/16/2025 |
1.1.0-pre.0.1 | 111 | 7/16/2025 |
1.1.0-pre.0 | 108 | 7/16/2025 |
1.0.0 | 120 | 7/16/2025 |
1.0.0-rc1.17 | 80 | 7/11/2025 |
1.0.0-rc1.16 | 83 | 7/11/2025 |
1.0.0-rc1.15 | 127 | 5/25/2025 |
1.0.0-rc1.14 | 126 | 5/25/2025 |
1.0.0-rc1.6 | 127 | 8/12/2023 |
1.0.0-rc1.5 | 120 | 8/12/2023 |
1.0.0-rc1 | 240 | 4/4/2023 |
0.31.0-pre.0.43 | 145 | 4/4/2023 |
0.31.0-pre.0.40 | 146 | 4/2/2023 |
0.31.0-pre.0.39 | 135 | 3/30/2023 |
0.31.0-pre.0.37 | 144 | 3/30/2023 |
0.31.0-pre.0.35 | 151 | 3/28/2023 |
0.31.0-pre.0.33 | 145 | 3/28/2023 |
0.31.0-pre.0.23 | 150 | 3/28/2023 |
0.31.0-pre.0.22 | 142 | 3/5/2023 |
0.31.0-pre.0.20 | 140 | 3/4/2023 |
0.31.0-pre.0.17 | 138 | 3/4/2023 |
0.31.0-pre.0.15 | 145 | 2/16/2023 |
0.31.0-pre.0.13 | 146 | 2/16/2023 |
0.31.0-pre.0.10 | 153 | 2/15/2023 |
0.31.0-pre.0.7 | 145 | 2/15/2023 |
0.31.0-pre.0.6 | 164 | 1/30/2023 |
0.31.0-pre.0.5 | 162 | 1/4/2023 |
0.31.0-pre.0.4 | 157 | 1/4/2023 |
0.31.0-pre.0.3 | 162 | 1/4/2023 |
0.31.0-pre.0.1 | 158 | 1/4/2023 |
0.30.1 | 493 | 1/4/2023 |
0.30.0 | 362 | 12/30/2022 |
0.30.0-pre.0.12 | 154 | 12/30/2022 |
0.30.0-pre.0.9 | 150 | 12/13/2022 |
0.30.0-pre.0.8 | 153 | 12/11/2022 |
0.30.0-pre.0.2 | 151 | 12/9/2022 |
0.29.1-pre.0.1 | 149 | 12/9/2022 |
0.29.0 | 366 | 12/8/2022 |
0.29.0-pre.0.52 | 150 | 12/8/2022 |
0.29.0-pre.0.51 | 144 | 12/8/2022 |
0.29.0-pre.0.43 | 152 | 12/4/2022 |
0.29.0-pre.0.42 | 147 | 12/4/2022 |
0.29.0-pre.0.41 | 164 | 11/27/2022 |
0.29.0-pre.0 | 151 | 11/27/2022 |