generated from Nexus-Mods/NexusMods.App.Template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
22 changed files
with
499 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
using System; | ||
using TransparentValueObjects; | ||
|
||
namespace NexusMods.EventSourcing.Abstractions; | ||
|
||
[ValueObject<Guid>] | ||
public readonly partial struct EntityId | ||
{ | ||
public EntityId<T> Cast<T>() where T : IEntity => new(this); | ||
|
||
} | ||
|
||
|
||
/// <summary> | ||
/// A strongly typed <see cref="EntityId"/> for a specific <see cref="IEntity"/>. | ||
/// </summary> | ||
/// <typeparam name="T"></typeparam> | ||
public readonly struct EntityId<T> where T : IEntity | ||
{ | ||
/// <summary> | ||
/// Creates a new instance of <see cref="EntityId{T}"/>. | ||
/// </summary> | ||
/// <returns></returns> | ||
public static EntityId<T> NewId() => new(EntityId.NewId()); | ||
|
||
/// <summary> | ||
/// Creates a new instance of <see cref="EntityId{T}"/>. | ||
/// </summary> | ||
/// <param name="id"></param> | ||
public EntityId(EntityId id) => Value = id; | ||
|
||
/// <summary> | ||
/// The underlying value. | ||
/// </summary> | ||
public readonly EntityId Value; | ||
|
||
/// <inheritdoc /> | ||
public override string ToString() | ||
{ | ||
return typeof(T).Name + "<" + Value.Value + ">"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace NexusMods.EventSourcing.Abstractions; | ||
|
||
public interface IEntity | ||
{ | ||
public EntityId Id { get; } | ||
} | ||
|
47 changes: 47 additions & 0 deletions
47
src/NexusMods.EventSourcing.Abstractions/IEntityContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
using System.Threading.Tasks; | ||
|
||
namespace NexusMods.EventSourcing.Abstractions; | ||
|
||
/// <summary> | ||
/// A context for working with entities and events. Multiple contexts can be used to work with different sets of entities | ||
/// as of different transaction ids. | ||
/// </summary> | ||
public interface IEntityContext | ||
{ | ||
/// <summary> | ||
/// Adds the event to the event store, and advances the "as of" transaction id to the transaction id of the event. | ||
/// </summary> | ||
/// <param name="event"></param> | ||
/// <returns></returns> | ||
public ValueTask Transact(IEvent @event); | ||
|
||
/// <summary> | ||
/// Get the entity with the given id from the context, the entity will be up-to-date as of the current "as of" transaction id. | ||
/// </summary> | ||
/// <param name="entityId"></param> | ||
/// <typeparam name="T"></typeparam> | ||
/// <returns></returns> | ||
public ValueTask<T> Retrieve<T>(EntityId<T> entityId) where T : IEntity; | ||
|
||
/// <summary> | ||
/// The current "as of" transaction id. The entities in this context are up-to-date as of this transaction id. | ||
/// </summary> | ||
public TransactionId AsOf { get; } | ||
|
||
/// <summary> | ||
/// Advances the "as of" transaction id to the given transaction id, all objects in this context will be updated | ||
/// to reflect the new transaction id. | ||
/// </summary> | ||
/// <param name="transactionId"></param> | ||
/// <returns></returns> | ||
public ValueTask Advance(TransactionId transactionId); | ||
|
||
/// <summary> | ||
/// Advances the "as of" transaction id to the most recent transaction id, all objects in this context will be updated | ||
/// to reflect the new transaction id. | ||
/// </summary> | ||
/// <returns></returns> | ||
public ValueTask Advance(); | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using MemoryPack; | ||
|
||
namespace NexusMods.EventSourcing.Abstractions; | ||
|
||
/// <summary> | ||
/// A single event that can be applied to an entity. | ||
/// </summary> | ||
[MemoryPackable(GenerateType.NoGenerate)] | ||
public interface IEvent | ||
{ | ||
/// <summary> | ||
/// Applies the event to the entities attached to the event. | ||
/// </summary> | ||
ValueTask Apply<T>(T context) where T : IEventContext; | ||
|
||
/// <summary> | ||
/// When called, the handler should be called for each entity that was modified by this event. Not for | ||
/// those which are referenced, but not modified. | ||
/// </summary> | ||
/// <param name="handler"></param> | ||
void ModifiedEntities(Action<EntityId> handler); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System.Threading.Tasks; | ||
|
||
namespace NexusMods.EventSourcing.Abstractions; | ||
|
||
/// <summary> | ||
/// This is the context interface passed to event handlers, it allows the handler to attach new entities to the context | ||
/// </summary> | ||
public interface IEventContext | ||
{ | ||
|
||
/// <summary> | ||
/// Attach an entity to the context, this entity will be tracked by the context and should only be used in events | ||
/// that intend to create an entity from scratch. | ||
/// </summary> | ||
/// <param name="entityId"></param> | ||
/// <param name="entity"></param> | ||
/// <typeparam name="TEntity"></typeparam> | ||
public void AttachEntity<TEntity>(EntityId<TEntity> entityId, TEntity entity) where TEntity : IEntity; | ||
|
||
/// <summary> | ||
/// Retrieve an entity from the context, this may require the context to load the entity via replaying | ||
/// the events up to the current transaction. | ||
/// </summary> | ||
/// <param name="id"></param> | ||
/// <typeparam name="T"></typeparam> | ||
public ValueTask<T> Retrieve<T>(EntityId<T> id) where T : IEntity; | ||
} |
12 changes: 12 additions & 0 deletions
12
src/NexusMods.EventSourcing.Abstractions/IEventIngester.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
|
||
namespace NexusMods.EventSourcing.Abstractions; | ||
|
||
/// <summary> | ||
/// A mostly internal interface that is used to ingest events from the event store. | ||
/// </summary> | ||
public interface IEventIngester | ||
{ | ||
public ValueTask Ingest(IEvent @event); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
using System.Threading.Tasks; | ||
|
||
namespace NexusMods.EventSourcing.Abstractions; | ||
|
||
|
||
public interface IEventStore | ||
{ | ||
public ValueTask Add<T>(T eventEntity) where T : IEvent; | ||
|
||
public ValueTask EventsForEntity<TEntity, TIngester>(EntityId<TEntity> entityId, TIngester ingester) | ||
where TEntity : IEntity | ||
where TIngester : IEventIngester; | ||
} |
12 changes: 12 additions & 0 deletions
12
src/NexusMods.EventSourcing.Abstractions/NexusMods.EventSourcing.Abstractions.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<RootNamespace>NexusMods.EventSourcing.Abstractions</RootNamespace> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="MemoryPack.Core" Version="1.10.0" /> | ||
<PackageReference Include="TransparentValueObjects" PrivateAssets="all" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/> | ||
</ItemGroup> | ||
|
||
<Import Project="$([MSBuild]::GetPathOfFileAbove('NuGet.Build.props', '$(MSBuildThisFileDirectory)../'))" /> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using TransparentValueObjects; | ||
|
||
namespace NexusMods.EventSourcing.Abstractions; | ||
|
||
/// <summary> | ||
/// A transaction id. In the context of this library, a transaction is a set of events that are applied to the entities | ||
/// that they reference. The transaction id is a monotonic increasing number that is used to order the events over an | ||
/// abstract idea of time. Transaction X is always considered to have happened before transaction X + 1. | ||
/// </summary> | ||
[ValueObject<ulong>] | ||
public readonly partial struct TransactionId | ||
{ | ||
|
||
/// <summary> | ||
/// Get the next transaction id. | ||
/// </summary> | ||
/// <returns></returns> | ||
public TransactionId Next() => new(Value + 1); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,9 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<RootNamespace>NexusMods.EventSourcing</RootNamespace> | ||
<RootNamespace>NexusMods.EventSourcing</RootNamespace> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<PackageReference Include="TransparentValueObjects" PrivateAssets="all" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/> | ||
</ItemGroup> | ||
<Import Project="$([MSBuild]::GetPathOfFileAbove('NuGet.Build.props', '$(MSBuildThisFileDirectory)../'))" /> | ||
</Project> |
38 changes: 38 additions & 0 deletions
38
tests/NexusMods.EventSourcing.Tests/BasicFunctionalityTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
using NexusMods.EventSourcing.Abstractions; | ||
using NexusMods.EventSourcing.Tests.Contexts; | ||
using NexusMods.EventSourcing.Tests.DataObjects; | ||
using NexusMods.EventSourcing.Tests.Events; | ||
|
||
namespace NexusMods.EventSourcing.Tests; | ||
|
||
public class BasicFunctionalityTests | ||
{ | ||
private readonly TestContext _ctx; | ||
public BasicFunctionalityTests(TestContext ctx) | ||
{ | ||
_ctx = ctx; | ||
} | ||
|
||
[Fact] | ||
public async void CanApplyEvents() | ||
{ | ||
var newId = EntityId<CountedEntity>.NewId(); | ||
await _ctx.Transact(new CreateCountedEntity | ||
{ | ||
Name = "Test", | ||
Id = newId, | ||
InitialCount = 0 | ||
}); | ||
var entity = await _ctx.Retrieve(newId); | ||
entity.Count.Should().Be(0); | ||
await _ctx.Transact(new IncrementCount {Entity = newId, Increment = 1}); | ||
entity.Count.Should().Be(1); | ||
await _ctx.Transact(new IncrementCount {Entity = newId, Increment = 1}); | ||
entity.Count.Should().Be(2); | ||
|
||
_ctx.ResetCache(); | ||
entity = await _ctx.Retrieve(newId); | ||
entity.Count.Should().Be(2); | ||
|
||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
tests/NexusMods.EventSourcing.Tests/Contexts/InMemoryEventStore.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
using MemoryPack; | ||
using MemoryPack.Formatters; | ||
using NexusMods.EventSourcing.Abstractions; | ||
using NexusMods.EventSourcing.Tests.Events; | ||
|
||
namespace NexusMods.EventSourcing.Tests.Contexts; | ||
|
||
public class InMemoryEventStore : IEventStore | ||
{ | ||
private readonly Dictionary<EntityId,IList<byte[]>> _events = new(); | ||
|
||
public InMemoryEventStore() | ||
{ | ||
var formatter = new DynamicUnionFormatter<IEvent>(new[] | ||
{ | ||
( (ushort)1, typeof(CreateCountedEntity)), | ||
( (ushort)2, typeof(IncrementCount)) | ||
}); | ||
MemoryPackFormatterProvider.Register(formatter); | ||
} | ||
|
||
public ValueTask Add<T>(T entity) where T : IEvent | ||
{ | ||
lock (_events) | ||
{ | ||
var data = MemoryPackSerializer.Serialize(entity); | ||
entity.ModifiedEntities(id => | ||
{ | ||
if (!_events.TryGetValue(id, out var value)) | ||
{ | ||
value = new List<byte[]>(); | ||
_events.Add(id, value); | ||
} | ||
value.Add(data); | ||
}); | ||
} | ||
return ValueTask.CompletedTask; | ||
} | ||
|
||
|
||
public ValueTask EventsForEntity<TEntity, TIngester>(EntityId<TEntity> entityId, TIngester ingester) | ||
where TEntity : IEntity where TIngester : IEventIngester | ||
{ | ||
foreach (var data in _events[entityId.Value]) | ||
{ | ||
var @event = MemoryPackSerializer.Deserialize<IEvent>(data)!; | ||
ingester.Ingest(@event); | ||
} | ||
return ValueTask.CompletedTask; | ||
} | ||
} |
Oops, something went wrong.