Created
January 16, 2017 07:49
-
-
Save heemskerkerik/f5a01ee7b05e39a92f76f65648135ef6 to your computer and use it in GitHub Desktop.
A number of ways to implement an event-sourced base class
This file contains hidden or 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
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