Last active
March 16, 2017 03:39
-
-
Save afifmohammed/90ed53f283885d2b832426d9abe90758 to your computer and use it in GitHub Desktop.
Aggregate correlations
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
void Main() | |
{ | |
var events = new Event[] | |
{ | |
new ItemAddedToCart(lead:"1", cart:"2", product:"44", title:"products/44/title/iphone-6s", when:DateTimeOffset.Now), | |
new ItemAddedToCart(lead:"1", cart:"2", product:"4", title:"products/4/title/iphone-5-SE", when:DateTimeOffset.Now), | |
new ItemRemovedFromCart(lead:"1", cart:"2", product:"44", when:DateTimeOffset.Now), | |
new OrderPlaced(customer:"1", order:"2", when:DateTimeOffset.Now.AddSeconds(123)), | |
new ItemShipped(order:"2", sku:"4", trackingid:"orders/2/skus/4/track/12", when:DateTimeOffset.Now.AddMinutes(33)), | |
new ItemAddedToCart(lead:"12", cart:"22", product:"42", title:"products/42/title/iphone-6s-plus", when:DateTimeOffset.Now.AddSeconds(12)), | |
new OrderPlaced(customer:"12", order:"22", when:DateTimeOffset.Now.AddSeconds(123)), | |
new ItemShipped(order:"22", sku:"42", trackingid:"orders/22/skus/42/track/32", when:DateTimeOffset.Now.AddMinutes(3)) | |
}; | |
var states = FoldEventsToStates<ItemShippedEmailState, ItemShippedEmailState.Identity>( | |
events, | |
ItemShippedEmailState.Mappers(), | |
ItemShippedEmailState.IdentifiedBy, | |
LoadStates, | |
() => new ItemShippedEmailState()); | |
states.Dump("states"); | |
} | |
struct EventContract : IEqualityComparer<EventContract> | |
{ | |
public static EventContract From<T>() where T : Event | |
{ | |
return new EventContract(typeof(T)); | |
} | |
public static EventContract From(Event e) | |
{ | |
return new EventContract(e.GetType()); | |
} | |
private readonly string typeFullName; | |
private EventContract(Type o) | |
{ | |
typeFullName = o.FullName; | |
} | |
public static implicit operator string(EventContract contract) | |
{ | |
return contract.typeFullName; | |
} | |
public override bool Equals(object other) | |
{ | |
if(other == null) return false; | |
if(ReferenceEquals(other, this)) return true; | |
if(!(other is EventContract)) return false; | |
return Equals(this, (EventContract)other); | |
} | |
public int GetHashCode(EventContract a) | |
{ | |
return typeFullName.GetHashCode(); | |
} | |
public bool Equals(EventContract a, EventContract b) | |
{ | |
return a.typeFullName.Equals(b.typeFullName); | |
} | |
public override int GetHashCode() | |
{ | |
return GetHashCode(this); | |
} | |
} | |
interface Event {} | |
delegate TState Transition<TState>(Event @event, TState state); | |
static IEnumerable<TState> FoldEventsToStates<TState, TIdentity>( | |
Event[] events, | |
IEnumerable<KeyValuePair<EventContract, Transition<TState>>> mappers, | |
Func<TState, TIdentity> stateIdentity, | |
Func<TIdentity[], IDictionary<TIdentity, TState>> loadStatesByIdentities, | |
Func<TState> newState) | |
=> FoldEventsToStates( | |
events, | |
mappers.ToDictionary(x => x.Key, x => x.Value), | |
stateIdentity, | |
loadStatesByIdentities, | |
newState); | |
static IEnumerable<TState> FoldEventsToStates<TState, TIdentity>( | |
Event[] events, | |
IDictionary<EventContract, Transition<TState>> mappers, | |
Func<TState, TIdentity> stateIdentity, | |
Func<TIdentity[], IDictionary<TIdentity, TState>> loadStatesByIdentities, | |
Func<TState> newState) | |
=> MapEventToState(events, mappers, newState) | |
.GroupBy(e_s_kvp => stateIdentity(e_s_kvp.Value)) | |
.ToDictionary(x => x.Key, e => e.Select(e_s_kvp => e_s_kvp.Key)) | |
.MapIdentityToState(loadStatesByIdentities) | |
.Select(s_es_kvp => FoldEventsToState( | |
s_es_kvp.Value.ToArray(), | |
mappers, | |
() => s_es_kvp.Key)); | |
static class Ex | |
{ | |
public static IDictionary<TState, IEnumerable<Event>> MapIdentityToState<TState, TIdentity>( | |
this IDictionary<TIdentity, IEnumerable<Event>> eventsByIdentity, | |
Func<TIdentity[], IDictionary<TIdentity, TState>> statesByIdentities) | |
{ | |
return statesByIdentities(eventsByIdentity.Keys.ToArray()) | |
.Where(r => eventsByIdentity.ContainsKey(r.Key)) | |
.Select(r => new { State = r.Value, Events = eventsByIdentity[r.Key] }) | |
.ToDictionary(r => r.State, r => r.Events); | |
} | |
} | |
static IDictionary<TIdentity, TState> StatebyIDentity<TState, TIdentity>( | |
TIdentity[] identities, | |
TState[] states, | |
Func<TState, TIdentity> stateIdentity) | |
{ | |
return states.Select(s => new {State = s, Identity = stateIdentity(s)}).ToDictionary(s => s.Identity, s => s.State); | |
} | |
static TState FoldEventsToState<TState>( | |
Event[] events, | |
IDictionary<EventContract, Transition<TState>> mappers, | |
Func<TState> getS) | |
{ | |
var s = getS(); | |
foreach (var e in events) | |
{ | |
var contract = EventContract.From(e); | |
Transition<TState> transition; | |
if(mappers.TryGetValue(contract, out transition)) | |
s = mappers[contract](e, s); | |
} | |
return s; | |
} | |
static IEnumerable<KeyValuePair<Event, TState>> MapEventToState<TState>( | |
Event[] events, | |
IDictionary<EventContract, Transition<TState>> mappers, | |
Func<TState> getS) | |
=> events | |
.Where(e => mappers.Any(m => m.Key == EventContract.From(e))) | |
.Select(e => new KeyValuePair<Event, TState>( | |
e, | |
mappers[EventContract.From(e)](e, getS()))); | |
static class F | |
{ | |
public static KeyValuePair<EventContract, Transition<S>> Map<E, S>(Func<E, Action<S>> map) where E : Event | |
=> new KeyValuePair<EventContract, Transition<S>>( | |
EventContract.From<E>(), | |
(e, t) => { map((E)e)(t); return t; }); | |
} | |
class ItemShippedEmailState | |
{ | |
public struct Identity | |
{ | |
public string OrderId { get; set; } | |
public string ProductId { get; set;} | |
} | |
public string OrderId; | |
public string ProductId; | |
public string ProductTitle; | |
public string ProductDescription; | |
public string TrackingId; | |
public DateTimeOffset? ShippedAt; | |
public static Identity IdentifiedBy(ItemShippedEmailState state) | |
=> new Identity { OrderId = state.OrderId, ProductId = state.ProductId }; | |
public static IEnumerable<KeyValuePair<EventContract, Transition<ItemShippedEmailState>>> Mappers() | |
{ | |
yield return F.Map<ItemAddedToCart, ItemShippedEmailState>(e => s => | |
{ | |
s.OrderId = e.CartId; | |
s.ProductId = e.ProductId; | |
s.ProductTitle = e.ProductTitle; | |
}); | |
yield return F.Map<ItemShipped, ItemShippedEmailState>(e => s => | |
{ | |
s.OrderId = e.OrderId; | |
s.ProductId = e.Sku; | |
s.ShippedAt = e.When; | |
s.TrackingId = e.TrackingId; | |
}); | |
} | |
} | |
//In a real scenario this would load the states from a database using the supplied identities | |
static IDictionary<ItemShippedEmailState.Identity, ItemShippedEmailState> LoadStates(ItemShippedEmailState.Identity[] identities) | |
{ | |
return StatebyIDentity( | |
identities, | |
new [] | |
{ | |
new ItemShippedEmailState {OrderId="2", ProductId="4", ProductDescription = "products/4/desc/duh"}, | |
new ItemShippedEmailState {OrderId="2", ProductId="44", ProductDescription = "products/44/desc/huh"}, | |
new ItemShippedEmailState {OrderId="22", ProductId="42", ProductDescription = "products/42/desc/no"} | |
}, | |
ItemShippedEmailState.IdentifiedBy); | |
} | |
class OrderPlaced : Event | |
{ | |
public OrderPlaced(string customer, string order, DateTimeOffset when) | |
{ | |
CustomerId = customer; | |
OrderId = order; | |
When = when; | |
} | |
public readonly string CustomerId; | |
public readonly string OrderId; | |
public readonly DateTimeOffset When; | |
public string What => nameof(OrderPlaced); | |
} | |
class ItemAddedToCart : Event | |
{ | |
public ItemAddedToCart(string lead, string cart, string product, string title, DateTimeOffset when) | |
{ | |
LeadId = lead; | |
CartId = cart; | |
ProductId = product; | |
ProductTitle = title; | |
When = when; | |
} | |
public readonly string LeadId; | |
public readonly string CartId; | |
public readonly string ProductId; | |
public readonly string ProductTitle; | |
public readonly DateTimeOffset When; | |
public string What => nameof(ItemAddedToCart); | |
} | |
class ItemRemovedFromCart : Event | |
{ | |
public ItemRemovedFromCart(string lead, string cart, string product, DateTimeOffset when) | |
{ | |
LeadId = lead; | |
CartId = cart; | |
ProductId = product; | |
When = when; | |
} | |
public readonly string LeadId; | |
public readonly string CartId; | |
public readonly string ProductId; | |
public readonly DateTimeOffset When; | |
public string What => nameof(ItemRemovedFromCart); | |
} | |
class ItemShipped : Event | |
{ | |
public ItemShipped(string order, string sku, string trackingid, DateTimeOffset when) | |
{ | |
OrderId = order; | |
Sku = sku; | |
TrackingId = trackingid; | |
When = when; | |
} | |
public readonly string OrderId; | |
public readonly string Sku; | |
public readonly string TrackingId; | |
public readonly DateTimeOffset When; | |
public string What => nameof(ItemShipped); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment