Skip to content

Commit

Permalink
Merge pull request #1031 from aksio-insurtech:feature/projection-deri…
Browse files Browse the repository at this point in the history
…vatives

Feature/projection-derivatives
  • Loading branch information
einari authored Dec 3, 2023
2 parents 762bfb5 + cc2cc49 commit 9c5745c
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 21 deletions.
18 changes: 18 additions & 0 deletions Source/Clients/DotNET/Events/TypeIsNotAnEventType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Aksio Insurtech. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Aksio.Cratis.Events;

/// <summary>
/// Exception that gets thrown when a type is not an event type.
/// </summary>
public class TypeIsNotAnEventType : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="TypeIsNotAnEventType"/> class.
/// </summary>
/// <param name="type">Type that is not an event type.</param>
public TypeIsNotAnEventType(Type type) : base($"Type '{type.AssemblyQualifiedName}' is not an event type")
{
}
}
27 changes: 25 additions & 2 deletions Source/Clients/DotNET/Projections/ProjectionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class ProjectionBuilder<TModel, TBuilder> : IProjectionBuilder<TModel, TB
protected readonly Dictionary<EventType, FromDefinition> _fromDefinitions = new();
protected readonly Dictionary<PropertyPath, ChildrenDefinition> _childrenDefinitions = new();
protected readonly Dictionary<EventType, JoinDefinition> _joinDefinitions = new();
protected readonly List<FromAnyDefinition> _fromAnyDefinitions = new();
protected AllDefinition _allDefinition = new(new Dictionary<PropertyPath, string>(), false);
protected JsonObject _initialValues = (JsonObject)JsonNode.Parse("{}")!;
protected EventType? _removedWithEvent;
Expand Down Expand Up @@ -77,16 +78,38 @@ public TBuilder WithInitialValues(Func<TModel> initialValueProviderCallback)
/// <inheritdoc/>
public TBuilder From<TEvent>(Action<IFromBuilder<TModel, TEvent>> builderCallback)
{
var type = typeof(TEvent);

if (!type.IsEventType(_eventTypes.AllClrTypes))
{
throw new TypeIsNotAnEventType(typeof(TEvent));
}

var eventTypes = type.GetEventTypes(_eventTypes.AllClrTypes).Select(_ => _eventTypes.GetEventTypeFor(_)).ToArray();

var builder = new FromBuilder<TModel, TEvent, TBuilder>(this);
builderCallback(builder);
var eventType = _eventTypes.GetEventTypeFor(typeof(TEvent));
_fromDefinitions[eventType] = builder.Build();
var fromDefinition = builder.Build();

if (eventTypes.Length > 1)
{
_fromAnyDefinitions.Add(new FromAnyDefinition(eventTypes, fromDefinition));
}
else
{
_fromDefinitions[eventTypes[0]] = fromDefinition;
}
return (this as TBuilder)!;
}

/// <inheritdoc/>
public TBuilder Join<TEvent>(Action<IJoinBuilder<TModel, TEvent>> builderCallback)
{
if (!typeof(TEvent).IsEventType(_eventTypes.AllClrTypes))
{
throw new TypeIsNotAnEventType(typeof(TEvent));
}

var builder = new JoinBuilder<TModel, TEvent, TBuilder>(this);
builderCallback(builder);
var eventType = _eventTypes.GetEventTypeFor(typeof(TEvent));
Expand Down
1 change: 1 addition & 0 deletions Source/Clients/DotNET/Projections/ProjectionBuilderFor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public ProjectionDefinition Build()
_fromDefinitions,
_joinDefinitions,
_childrenDefinitions,
_fromAnyDefinitions,
_allDefinition,
null,
_removedWithEvent == default ? default : new RemovedWithDefinition(_removedWithEvent));
Expand Down
69 changes: 50 additions & 19 deletions Source/Kernel/Engines/Projections/ProjectionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,34 +116,23 @@ async Task<IProjection> CreateProjectionFrom(

var valueProvider = _eventValueProviderExpressionResolvers.Resolve(schemaProperty!, projectionDefinition.FromEventProperty.PropertyExpression);
projection.Event
.WhereEventTypeEquals(projectionDefinition.FromEventProperty.Event)
.WhereEventTypeEquals(projectionDefinition.FromEventProperty.Event)
.AddChildFromEventProperty(childrenAccessorProperty, valueProvider);
}

var propertyMappersForAllEventTypes = projectionDefinition.All.Properties.Select(kvp => ResolvePropertyMapper(projection, childrenAccessorProperty + kvp.Key, kvp.Value));
foreach (var (eventType, fromDefinition) in projectionDefinition.From)
{
var joinExpressions = projectionDefinition.Join.Where(join => fromDefinition.Properties.Any(from => join.Value.On == from.Key));
var propertyMappers = fromDefinition.Properties.Select(kvp => ResolvePropertyMapper(projection, childrenAccessorProperty + kvp.Key, kvp.Value)).ToList();
propertyMappers.AddRange(propertyMappersForAllEventTypes);
var projected = projection.Event
.WhereEventTypeEquals(eventType)
.Project(
childrenAccessorProperty,
actualIdentifiedByProperty,
propertyMappers);
SetupFromSubscription(projectionDefinition, childrenAccessorProperty, actualIdentifiedByProperty, projection, propertyMappersForAllEventTypes, eventType, fromDefinition);
}

if (joinExpressions.Any())
if (projectionDefinition.FromAny is not null)
{
foreach (var fromAnyDefinition in projectionDefinition.FromAny)
{
foreach (var (joinEventType, joinDefinition) in joinExpressions)
foreach (var eventType in fromAnyDefinition.EventTypes)
{
var joinPropertyMappers = joinDefinition.Properties.Select(kvp => ResolvePropertyMapper(projection, childrenAccessorProperty + kvp.Key, kvp.Value)).ToArray();
projected = projected
.ResolveJoin(_eventProvider, joinEventType, joinDefinition.On)
.Project(
childrenAccessorProperty,
actualIdentifiedByProperty,
joinPropertyMappers);
SetupFromSubscription(projectionDefinition, childrenAccessorProperty, actualIdentifiedByProperty, projection, propertyMappersForAllEventTypes, eventType, fromAnyDefinition.From);
}
}
}
Expand Down Expand Up @@ -181,6 +170,40 @@ async Task<IProjection> CreateProjectionFrom(
return projection;
}

void SetupFromSubscription(
ProjectionDefinition projectionDefinition,
PropertyPath childrenAccessorProperty,
PropertyPath actualIdentifiedByProperty,
Projection projection,
IEnumerable<PropertyMapper<AppendedEvent, ExpandoObject>> propertyMappersForAllEventTypes,
EventType eventType,
FromDefinition fromDefinition)
{
var joinExpressions = projectionDefinition.Join.Where(join => fromDefinition.Properties.Any(from => join.Value.On == from.Key));
var propertyMappers = fromDefinition.Properties.Select(kvp => ResolvePropertyMapper(projection, childrenAccessorProperty + kvp.Key, kvp.Value)).ToList();
propertyMappers.AddRange(propertyMappersForAllEventTypes);
var projected = projection.Event
.WhereEventTypeEquals(eventType)
.Project(
childrenAccessorProperty,
actualIdentifiedByProperty,
propertyMappers);

if (joinExpressions.Any())
{
foreach (var (joinEventType, joinDefinition) in joinExpressions)
{
var joinPropertyMappers = joinDefinition.Properties.Select(kvp => ResolvePropertyMapper(projection, childrenAccessorProperty + kvp.Key, kvp.Value)).ToArray();
projected = projected
.ResolveJoin(_eventProvider, joinEventType, joinDefinition.On)
.Project(
childrenAccessorProperty,
actualIdentifiedByProperty,
joinPropertyMappers);
}
}
}

PropertyMapper<AppendedEvent, ExpandoObject> ResolvePropertyMapper(IProjection projection, PropertyPath propertyPath, string expression)
{
var schemaProperty = projection.Model.Schema.GetSchemaPropertyForPropertyPath(propertyPath);
Expand All @@ -202,6 +225,14 @@ void ResolveEventsForProjection(IProjection projection, IProjection[] childProje
var eventsForProjection = projectionDefinition.From.Select(kvp => GetEventTypeWithKeyResolverFor(projection, kvp.Key, kvp.Value.Key, actualIdentifiedByProperty, hasParent, kvp.Value.ParentKey)).ToList();
eventsForProjection.AddRange(projectionDefinition.Join.Select(kvp => GetEventTypeWithKeyResolverFor(projection, kvp.Key, kvp.Value.Key, actualIdentifiedByProperty)));

if (projectionDefinition.FromAny is not null)
{
foreach (var fromAnyDefinition in projectionDefinition.FromAny)
{
eventsForProjection.AddRange(fromAnyDefinition.EventTypes.Select(eventType => GetEventTypeWithKeyResolverFor(projection, eventType, fromAnyDefinition.From.Key, actualIdentifiedByProperty, hasParent, fromAnyDefinition.From.ParentKey)));
}
}

if (projectionDefinition.FromEventProperty is not null)
{
eventsForProjection.Add(new EventTypeWithKeyResolver(projectionDefinition.FromEventProperty.Event, KeyResolvers.FromEventSourceId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public record ChildrenDefinition(
From,
Join,
Children,
Enumerable.Empty<FromAnyDefinition>(),
All,
FromEventProperty,
RemovedWith);
16 changes: 16 additions & 0 deletions Source/Kernel/Shared/Projections/Definitions/FromAnyDefinition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Aksio Insurtech. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Aksio.Cratis.Events;

namespace Aksio.Cratis.Projections.Definitions;

/// <summary>
/// Represents the definition from for a set of events.
/// </summary>
/// <param name="EventTypes">Collection of <see cref="EventType"/> for the definition.</param>
/// <param name="From">The from definition associated.</param>
/// <remarks>
/// This is typically representing event types that are deriving from a common base type.
/// </remarks>
public record FromAnyDefinition(IEnumerable<EventType> EventTypes, FromDefinition From);
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace Aksio.Cratis.Projections.Definitions;
/// <param name="From">All the <see cref="FromDefinition"/> for <see cref="EventType">event types</see>.</param>
/// <param name="Join">All the <see cref="JoinDefinition"/> for <see cref="EventType">event types</see>.</param>
/// <param name="Children">All the <see cref="ChildrenDefinition"/> for properties on model.</param>
/// <param name="FromAny">All the <see cref="FromAnyDefinition"/> for <see cref="EventType">event types</see>.</param>
/// <param name="All">The full <see cref="AllDefinition"/>.</param>
/// <param name="FromEventProperty">Optional <see cref="FromEventPropertyDefinition"/> definition.</param>
/// <param name="RemovedWith">The definition of what removes a child, if any.</param>
Expand All @@ -33,6 +34,7 @@ public record ProjectionDefinition(
IDictionary<EventType, FromDefinition> From,
IDictionary<EventType, JoinDefinition> Join,
IDictionary<PropertyPath, ChildrenDefinition> Children,
IEnumerable<FromAnyDefinition> FromAny,
AllDefinition All,
FromEventPropertyDefinition? FromEventProperty = default,
RemovedWithDefinition? RemovedWith = default,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@ IEnumerable<ProjectionDefinition> CreateProjectionDefinitions() =>
new Dictionary<EventType, FromDefinition>(),
new Dictionary<EventType, JoinDefinition>(),
new Dictionary<PropertyPath, ChildrenDefinition>(),
Enumerable.Empty<FromAnyDefinition>(),
new AllDefinition(new Dictionary<PropertyPath, string>(), false))).ToArray();
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ async Task Establish()
new Dictionary<EventType, FromDefinition>(),
new Dictionary<EventType, JoinDefinition>(),
new Dictionary<PropertyPath, ChildrenDefinition>(),
Enumerable.Empty<FromAnyDefinition>(),
new AllDefinition(new Dictionary<PropertyPath, string>(), false));

pipeline_definition = new ProjectionPipelineDefinition(projection_definition.Identifier, Enumerable.Empty<ProjectionSinkDefinition>());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ void Establish()
new Dictionary<EventType, FromDefinition>(),
new Dictionary<EventType, JoinDefinition>(),
new Dictionary<PropertyPath, ChildrenDefinition>(),
Enumerable.Empty<FromAnyDefinition>(),
new AllDefinition(new Dictionary<PropertyPath, string>(), false));

pipeline_definition = new ProjectionPipelineDefinition(projection_definition.Identifier, Enumerable.Empty<ProjectionSinkDefinition>());
Expand Down

0 comments on commit 9c5745c

Please sign in to comment.