Skip to content

Instantly share code, notes, and snippets.

@afifmohammed
Last active March 16, 2017 03:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save afifmohammed/90ed53f283885d2b832426d9abe90758 to your computer and use it in GitHub Desktop.
Save afifmohammed/90ed53f283885d2b832426d9abe90758 to your computer and use it in GitHub Desktop.
Aggregate correlations
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