Skip to content

Instantly share code, notes, and snippets.

@heemskerkerik
Created January 16, 2017 07:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save heemskerkerik/f5a01ee7b05e39a92f76f65648135ef6 to your computer and use it in GitHub Desktop.
Save heemskerkerik/f5a01ee7b05e39a92f76f65648135ef6 to your computer and use it in GitHub Desktop.
A number of ways to implement an event-sourced base class
public interface IEventSourced
{
IReadOnlyCollection<object> AppendedEvents { get; }
void ReplayEvent(object @event);
}
public abstract class EventSourcedBase: IEventSourced
{
protected void AppendEvent<T>(T @event)
{
ApplyEvent(@event);
appendedEvents.AddLast(@event);
}
protected abstract void ApplyEvent(object @event);
IReadOnlyCollection<object> IEventSourced.AppendedEvents => appendedEvents;
void IEventSourced.ReplayEvent(object @event) => ApplyEvent(@event);
private readonly LinkedList<object> appendedEvents = new LinkedList<object>();
}
public abstract class ExplicitEventSourcedBase: EventSourcedBase
{
protected void RegisterEventType<T>(Action<T> applyMethod)
{
eventAppliers.Add(typeof(T), @event => applyMethod((T) @event));
}
protected override void ApplyEvent(object @event)
{
Action<object> applier;
if (!eventAppliers.TryGetValue(@event.GetType(), out applier))
{
throw new NotSupportedException($"The event type {@event.GetType()} has not been registered.");
}
applier(@event);
}
private readonly Dictionary<Type, Action<object>> eventAppliers = new Dictionary<Type, Action<object>>();
}
internal class ExplicitAppointment: ExplicitEventSourcedBase
{
public ExplicitAppointment()
{
RegisterEventType<AppointmentCanceled>(ApplyEvent);
}
public void CancelAppointment()
{
if (isCanceled)
{
throw new InvalidOperationException("The appointment has already been canceled.");
}
AppendEvent(new AppointmentCanceled());
}
private void ApplyEvent(AppointmentCanceled @event)
{
isCanceled = true;
}
private bool isCanceled = false;
}
public abstract class DynamicEventSourcedBase: EventSourcedBase
{
protected override void ApplyEvent(object @event)
{
dynamic dynamicThis = this;
dynamic dynamicEvent = @event;
dynamicThis.ApplyEvent(dynamicEvent);
}
}
internal class DynamicAppointment: DynamicEventSourcedBase
{
public void CancelAppointment()
{
if (isCanceled)
{
throw new InvalidOperationException("The appointment has already been canceled.");
}
AppendEvent(new AppointmentCanceled());
}
public void ApplyEvent(AppointmentCanceled @event)
{
isCanceled = true;
}
private bool isCanceled = false;
}
public abstract class ReflectionBasedEventSourcedBase: EventSourcedBase
{
protected ReflectionBasedEventSourcedBase()
{
DetectApplyMethods();
}
private void DetectApplyMethods()
{
foreach (var method in GetApplyMethodsFromType())
{
RegisterApplyMethod(method);
}
}
private IEnumerable<MethodInfo> GetApplyMethodsFromType()
{
return GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
.Where(method => method.Name == nameof(ApplyEvent) && method.GetParameters().Length == 1)
}
private void RegisterApplyMethod(MethodInfo method)
{
var eventType = method.GetParameters()[0].ParameterType;
applyMethods.Add(eventType, method);
}
protected override void ApplyEvent(object @event)
{
MethodInfo method;
if (!applyMethods.TryGetValue(@event.GetType(), out method))
{
throw new NotSupportedException($"The event type {@event.GetType()} has not been registered.");
}
method.Invoke(this, new object[] { @event });
}
private readonly Dictionary<Type, MethodInfo> applyMethods = new Dictionary<Type, MethodInfo>();
}
internal class ReflectionBasedAppointment: ReflectionBasedEventSourcedBase
{
public void CancelAppointment()
{
if (isCanceled)
{
throw new InvalidOperationException("The appointment has already been canceled.");
}
AppendEvent(new AppointmentCanceled());
}
private void ApplyEvent(AppointmentCanceled @event)
{
isCanceled = true;
}
private bool isCanceled = false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment