Skip to content

Instantly share code, notes, and snippets.

@jrgcubano
Last active March 19, 2019 09:57
Show Gist options
  • Save jrgcubano/1f2536d322813b5d542d2310b33d9ea8 to your computer and use it in GitHub Desktop.
Save jrgcubano/1f2536d322813b5d542d2310b33d9ea8 to your computer and use it in GitHub Desktop.
ValueObject and Reflection to get aggregate apply methods on static constructor
// Via https://github.com/eventflow/EventFlow/blob/e0f60416d3598117aa8c2c2dc706d42604eedbd1/Source/EventFlow/Aggregates/AggregateRoot.cs
public static class TypeExtensions
{
internal static IReadOnlyDictionary<Type, Action<T, IAggregateEvent>> GetAggregateEventApplyMethods<TAggregate, TIdentity, T>(this Type type)
where TAggregate : IAggregateRoot<TIdentity>
where TIdentity : IIdentity
{
var aggregateEventType = typeof(IAggregateEvent<TAggregate, TIdentity>);
return type
.GetTypeInfo()
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(mi =>
{
if (mi.Name != "Apply") return false;
var parameters = mi.GetParameters();
return
parameters.Length == 1 &&
aggregateEventType.GetTypeInfo().IsAssignableFrom(parameters[0].ParameterType);
})
.ToDictionary(
mi => mi.GetParameters()[0].ParameterType,
mi => ReflectionHelper.CompileMethodInvocation<Action<T, IAggregateEvent>>(type, "Apply", mi.GetParameters()[0].ParameterType));
}
}
}
// ReflectionHelper on https://github.com/eventflow/EventFlow/blob/develop/Source/EventFlow/Core/ReflectionHelper.cs
public abstract class AggregateRoot<TAggregate, TIdentity> : IAggregateRoot<TIdentity>
where TAggregate : AggregateRoot<TAggregate, TIdentity>
where TIdentity : IIdentity
{
private static readonly IReadOnlyDictionary<Type, Action<TAggregate, IAggregateEvent>> ApplyMethods;
static AggregateRoot()
{
ApplyMethods = typeof(TAggregate).GetAggregateEventApplyMethods<TAggregate, TIdentity, TAggregate>();
}
protected virtual void ApplyEvent(IAggregateEvent<TAggregate, TIdentity> aggregateEvent)
{
Action<TAggregate, IAggregateEvent> applyMethod;
if (!ApplyMethods.TryGetValue(eventType, out applyMethod))
throw new NotImplementedException(...)
applyMethod(this as TAggregate, aggregateEvent);
Version++;
}
}
public abstract class ValueObject
{
static readonly ConcurrentDictionary<Type, IReadOnlyCollection<PropertyInfo>> TypeProperties =
new ConcurrentDictionary<Type, IReadOnlyCollection<PropertyInfo>>();
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj)) return true;
if (ReferenceEquals(null, obj)) return false;
if (GetType() != obj.GetType()) return false;
var other = obj as ValueObject;
return other != null && GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());
}
public override int GetHashCode()
{
unchecked
{
return GetEqualityComponents()
.Aggregate(17, (current, obj) => current * 23 + (obj?.GetHashCode() ?? 0));
}
}
public static bool operator ==(ValueObject left, ValueObject right) => Equals(left, right);
public static bool operator !=(ValueObject left, ValueObject right) => !Equals(left, right);
public override string ToString()
{
return $"{{{string.Join(", ", GetProperties().Select(f => $"{f.Name}: {f.GetValue(this)}"))}}}";
}
protected virtual IEnumerable<object> GetEqualityComponents()
{
return GetProperties().Select(x => x.GetValue(this));
}
protected virtual IEnumerable<PropertyInfo> GetProperties()
{
return TypeProperties.GetOrAdd(
GetType(),
t => t
.GetTypeInfo()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.OrderBy(p => p.Name)
.ToList());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment