Skip to content

Commit

Permalink
Big rewrite using datom-like attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
halgari committed Dec 13, 2023
1 parent ada3004 commit 06bc06a
Show file tree
Hide file tree
Showing 30 changed files with 504 additions and 179 deletions.
17 changes: 17 additions & 0 deletions src/NexusMods.EventSourcing.Abstractions/ACollectionAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace NexusMods.EventSourcing.Abstractions;

public class ACollectionAttribute<TOwner, TType>(string name) : IAttribute
where TOwner : IEntity
{
public bool IsScalar => false;
public Type Owner => typeof(TOwner);
public string Name => name;
public IAccumulator CreateAccumulator()
{
throw new NotImplementedException();
}

public Type Type => typeof(TType);
}
12 changes: 12 additions & 0 deletions src/NexusMods.EventSourcing.Abstractions/AEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace NexusMods.EventSourcing.Abstractions;

/// <summary>
/// The base class for all entities.
/// </summary>
public abstract class AEntity(IEntityContext context, EntityId id) : IEntity
{
public EntityId Id => id;

public IEntityContext Context => context;

}
43 changes: 43 additions & 0 deletions src/NexusMods.EventSourcing.Abstractions/AScalarAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;

namespace NexusMods.EventSourcing.Abstractions;

/// <summary>
/// A scalar attribute that can be exposed on an entity.
/// </summary>
public abstract class AScalarAttribute<TOwner, TType>(string attrName) : IAttribute
{
/// <inheritdoc />
public bool IsScalar => false;

/// <inheritdoc />
public Type Owner => typeof(TOwner);

/// <inheritdoc />
public string Name => attrName;

/// <inheritdoc />
public IAccumulator CreateAccumulator()
{
return new Accumulator<TType>();
}

private class Accumulator<TVal> : IAccumulator
{
private TVal _value = default! ;
public void Add(object value)
{
_value = (TVal) value;
}

public object Get()
{
return _value!;
}
}

/// <summary>
/// The data type of the attribute.
/// </summary>
public Type AttributeType => typeof(TType);
}
21 changes: 21 additions & 0 deletions src/NexusMods.EventSourcing.Abstractions/AttributeDefinition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;

namespace NexusMods.EventSourcing.Abstractions;

/// <summary>
/// An attribute definition for an entity.
/// </summary>
/// <param name="attrName"></param>
/// <typeparam name="TOwner"></typeparam>
/// <typeparam name="TType"></typeparam>
public class AttributeDefinition<TOwner, TType>(string attrName) : AScalarAttribute<TOwner, TType>(attrName)
where TOwner : IEntity
{
/// <summary>
/// Gets the value of the attribute for the given entity.
/// </summary>
/// <param name="owner"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public TType Get(TOwner owner) => (TType)owner.Context.GetAccumulator(owner.Id, this).Get();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace NexusMods.EventSourcing.Abstractions;

public class EntityAttributeDefinition<TOwner, TType>(string attrName) : AttributeDefinition<TOwner, EntityId<TType>>(attrName)
where TOwner : AEntity
where TType : IEntity
{
public TType GetEntity(TOwner owner) => throw new NotImplementedException();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace NexusMods.EventSourcing.Abstractions;

public class EntityCollectionAttributeDefinition<TOwner, TEntity>(string name) : ACollectionAttribute<TOwner, TEntity>(name) where TOwner : IEntity
where TEntity : IEntity
{

}
18 changes: 18 additions & 0 deletions src/NexusMods.EventSourcing.Abstractions/EntityId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,24 @@ public readonly partial struct EntityId
/// <returns></returns>
public static EntityId<T> NewId() => new(EntityId.NewId());


/// <summary>
/// Gets the <see cref="EntityId{T}"/> from the specified <paramref name="id"/>.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static EntityId<T> From(Guid id) => new(EntityId.From(id));



/// <summary>
/// Gets the <see cref="EntityId{T}"/> from the specified <paramref name="id"/>.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static EntityId<T> From(string id) => From(Guid.Parse(id));


/// <summary>
/// Creates a new instance of <see cref="EntityId{T}"/>.
/// </summary>
Expand Down
19 changes: 19 additions & 0 deletions src/NexusMods.EventSourcing.Abstractions/IAccumulator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace NexusMods.EventSourcing.Abstractions;

/// <summary>
/// An accumulator is used to accumulate values from events.
/// </summary>
public interface IAccumulator
{
/// <summary>
/// Adds a value to the accumulator.
/// </summary>
/// <param name="value"></param>
public void Add(object value);

/// <summary>
/// Gets the accumulated value.
/// </summary>
/// <returns></returns>
public object Get();
}
33 changes: 33 additions & 0 deletions src/NexusMods.EventSourcing.Abstractions/IAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;

namespace NexusMods.EventSourcing.Abstractions;

/// <summary>
/// Marker interface for attributes that can be exposed on an entity.
/// </summary>
public interface IAttribute
{
/// <summary>
/// True if the attribute is a scalar, false if it is a collection.
/// </summary>
public bool IsScalar { get; }

/// <summary>
/// The data type of the entity that owns the attribute.
/// </summary>
public Type Owner { get; }

/// <summary>
/// The name of the attribute, needs to be unique in a given entity but not unique across entities.
/// </summary>
public string Name { get; }


/// <summary>
/// Creates a new accumulator for the attribute.
/// </summary>
/// <returns></returns>
public IAccumulator CreateAccumulator();

}

20 changes: 19 additions & 1 deletion src/NexusMods.EventSourcing.Abstractions/IEntity.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
using System;

namespace NexusMods.EventSourcing.Abstractions;

/// <summary>
/// The base interface for all entities.
/// </summary>
public interface IEntity
{
/// <summary>
/// The globally unique identifier of the entity.
/// </summary>
public EntityId Id { get; }
}

/// <summary>
/// The context this entity belongs to.
/// </summary>
public IEntityContext Context { get; }


/// <summary>
/// The type descriptor for all entities. Emitted by the <see cref="IEventContext.New{TType}"/> method.
/// </summary>
public static readonly AttributeDefinition<IEntity, Type> TypeAttribute = new("$Type");
}
38 changes: 15 additions & 23 deletions src/NexusMods.EventSourcing.Abstractions/IEntityContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,31 @@ namespace NexusMods.EventSourcing.Abstractions;
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.
/// Gets the entity with the specified id.
/// </summary>
/// <param name="event"></param>
/// <param name="id"></param>
/// <typeparam name="TEntity"></typeparam>
/// <returns></returns>
public ValueTask Transact(IEvent @event);
public TEntity Get<TEntity>(EntityId<TEntity> id) where TEntity : IEntity;

/// <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.
/// Transacts a new event into the context.
/// </summary>
/// <param name="transactionId"></param>
/// <param name="entity"></param>
/// <typeparam name="TEvent"></typeparam>
/// <returns></returns>
public ValueTask Advance(TransactionId transactionId);
public ValueTask Add<TEvent>(TEvent entity) where TEvent : IEvent;


/// <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.
/// Gets the value of the attribute for the given entity.
/// </summary>
/// <param name="ownerId"></param>
/// <param name="attributeDefinition"></param>
/// <typeparam name="TType"></typeparam>
/// <typeparam name="TOwner"></typeparam>
/// <returns></returns>
public ValueTask Advance();

IAccumulator GetAccumulator<TType, TOwner>(EntityId ownerId, AttributeDefinition<TOwner,TType> attributeDefinition) where TOwner : IEntity;

}
7 changes: 0 additions & 7 deletions src/NexusMods.EventSourcing.Abstractions/IEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,4 @@ public interface IEvent
/// 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);
}
33 changes: 24 additions & 9 deletions src/NexusMods.EventSourcing.Abstractions/IEventContext.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks;

namespace NexusMods.EventSourcing.Abstractions;
Expand All @@ -7,21 +8,35 @@ namespace NexusMods.EventSourcing.Abstractions;
/// </summary>
public interface IEventContext
{
/// <summary>
/// Emits a new value for the given attribute on the given entity
/// </summary>
/// <param name="entity"></param>
/// <param name="attr"></param>
/// <param name="value"></param>
/// <typeparam name="TOwner"></typeparam>
/// <typeparam name="TVal"></typeparam>
public void Emit<TOwner, TVal>(EntityId<TOwner> entity, AttributeDefinition<TOwner, TVal> attr, TVal value)
where TOwner : IEntity;

/// <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.
/// Emits a new member value for the given attribute on the given entity
/// </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;
/// <param name="attr"></param>
/// <param name="value"></param>
/// <typeparam name="TOwner"></typeparam>
/// <typeparam name="TVal"></typeparam>
public void Emit<TOwner, TVal>(EntityId<TOwner> entity, MultiEntityAttributeDefinition<TOwner, TVal> attr,
EntityId<TVal> value)
where TOwner : IEntity
where TVal : 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.
/// Emits the type attribute for the given entity so that polymorphic queries can be performed
/// </summary>
/// <param name="id"></param>
/// <typeparam name="T"></typeparam>
public ValueTask<T> Retrieve<T>(EntityId<T> id) where T : IEntity;
/// <typeparam name="TType"></typeparam>
/// <exception cref="NotImplementedException"></exception>
public void New<TType>(EntityId<TType> id) where TType : IEntity;
}
2 changes: 1 addition & 1 deletion src/NexusMods.EventSourcing.Abstractions/IEventStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public interface IEventStore
{
public ValueTask Add<T>(T eventEntity) where T : IEvent;

public ValueTask EventsForEntity<TEntity, TIngester>(EntityId<TEntity> entityId, TIngester ingester)
public void EventsForEntity<TEntity, TIngester>(EntityId<TEntity> entityId, TIngester ingester)
where TEntity : IEntity
where TIngester : IEventIngester;
}
34 changes: 34 additions & 0 deletions src/NexusMods.EventSourcing.Abstractions/ITransaction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Threading.Tasks;

namespace NexusMods.EventSourcing.Abstractions;

/// <summary>
/// A interface for a transaction that can be used to add new events to storage.
/// </summary>
public interface ITransaction : IDisposable
{
/// <summary>
/// Confirms the transaction and commits the changes to the underlying storage.
/// </summary>
/// <returns></returns>
public ValueTask CommitAsync();

/// <summary>
/// Gets the current state of an entity.
/// </summary>
/// <param name="entityId"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T Retrieve<T>(EntityId<T> entityId) where T : IEntity;

/// <summary>
/// Adds a new event to the transaction, this will also update the current
/// entity states
/// </summary>
/// <param name="entityId"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public ValueTask Add<T>(T eventToAdd) where T : IEvent;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;

namespace NexusMods.EventSourcing.Abstractions;

public class MultiEntityAttributeDefinition<TOwner, TType>(string name) :
ACollectionAttribute<TOwner, EntityId<TType>>(name) where TOwner : IEntity
where TType : IEntity
{
public IEnumerable<TType> GetAll(TOwner owner) => throw new NotImplementedException();
}
Loading

0 comments on commit 06bc06a

Please sign in to comment.