Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Time-keyed projections #239

Merged
merged 3 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions Source/Projections/Builder/KeySelectorBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ public class KeySelectorBuilder
/// Select projection key from the <see cref="EventSourceId"/>.
/// </summary>
/// <returns>A <see cref="KeySelector"/>.</returns>
public static KeySelector KeyFromEventSource() => KeySelector.EventSource;
public static KeySelector KeyFromEventSource => KeySelector.EventSource;

/// <summary>
/// Select projection key from the <see cref="PartitionId"/>.
/// </summary>
/// <returns>A <see cref="KeySelector"/>.</returns>
public static KeySelector KeyFromPartitionId() => KeySelector.Partition;
public static KeySelector KeyFromPartitionId => KeySelector.Partition;

/// <summary>
/// Select projection key from a property of the event.
Expand All @@ -46,4 +46,21 @@ public static KeySelector StaticKey(Key staticKey)
/// <returns>A <see cref="KeySelector"/>.</returns>
public static KeySelector KeyFromEventOccurred(OccurredFormat occurredFormat)
=> KeySelector.Occurred(occurredFormat);

/// <summary>
/// Select projection key from a property of the event.
/// </summary>
/// <param name="selectorExpression">The property on the event.</param>
/// <param name="occurredFormat">The date time format.</param>
/// <returns>A <see cref="KeySelector"/>.</returns>
public static KeySelector KeyFromPropertyAndEventOccurred(KeySelectorExpression selectorExpression, OccurredFormat occurredFormat)
=> KeySelector.PropertyAndOccured(selectorExpression, occurredFormat);

/// <summary>
/// Select projection key from when an event occurred.
/// </summary>
/// <param name="occurredFormat">The date time format.</param>
/// <returns>A <see cref="KeySelector"/>.</returns>
public static KeySelector KeyFromEventSourceIdAndOccurred(OccurredFormat occurredFormat)
=> KeySelector.EventSourceAndOccured(occurredFormat);
}
15 changes: 15 additions & 0 deletions Source/Projections/Builder/KeySelectorBuilder{TEvent}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Linq.Expressions;
using Dolittle.SDK.Events;

namespace Dolittle.SDK.Projections.Builder;

Expand All @@ -29,4 +30,18 @@ public KeySelector KeyFromProperty<TProperty>(Expression<Func<TEvent, TProperty>

throw new KeySelectorExpressionWasNotAMemberExpression();
}

/// <summary>
/// Select projection key from a property of the event.
/// </summary>
/// <typeparam name="TProperty">The property type.</typeparam>
/// <param name="function">The function for getting the projection key (id).</param>
/// <returns>A <see cref="KeySelector"/>.</returns>
/// <exception cref="KeySelectorExpressionWasNotAMemberExpression">Is thrown when the provided property expression is not a member expression.</exception>
public KeySelector KeyFromFunction(Func<TEvent, EventContext, Key> function)
{
if (function is null) throw new ArgumentNullException(nameof(function));

return KeySelector.ByFunction(function);
}
}
22 changes: 22 additions & 0 deletions Source/Projections/IKeySelector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Dolittle. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Dolittle.SDK.Events;

namespace Dolittle.SDK.Projections;

/// <summary>
/// Use this interface to define projection key selectors.
/// Instances of this interface MUST be thread safe and stateless, as they are used as singletons.
/// </summary>
/// <typeparam name="TEvent">The mapped event type</typeparam>
public interface IKeySelector<TEvent> where TEvent : class
{
/// <summary>
/// Map to a <see cref="Key"/> from an event and context.
/// </summary>
/// <param name="event"></param>
/// <param name="eventContext"></param>
/// <returns>The projection key</returns>
Key Selector(TEvent @event, EventContext eventContext);
}
24 changes: 0 additions & 24 deletions Source/Projections/Internal/ProjectionsProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,28 +103,4 @@ protected override RetryProcessingState GetRetryProcessingStateFromRequest(Handl
/// <inheritdoc/>
protected override EventHandlerResponse CreateResponseFromFailure(ProcessorFailure failure)
=> new() { Failure = failure };

static ProjectionEventSelector CreateProjectionEventSelector(EventSelector eventSelector)
{
static ProjectionEventSelector WithEventType(EventSelector eventSelector, Action<ProjectionEventSelector> callback)
{
var message = new ProjectionEventSelector();
callback(message);
message.EventType = eventSelector.EventType.ToProtobuf();
return message;
}

return eventSelector.KeySelector.Type switch
{
KeySelectorType.EventSourceId => WithEventType(eventSelector, _ => _.EventSourceKeySelector = new EventSourceIdKeySelector()),
KeySelectorType.PartitionId => WithEventType(eventSelector, _ => _.PartitionKeySelector = new PartitionIdKeySelector()),
KeySelectorType.Property => WithEventType(eventSelector,
_ => _.EventPropertyKeySelector = new EventPropertyKeySelector { PropertyName = eventSelector.KeySelector.Expression ?? string.Empty }),
KeySelectorType.Static => WithEventType(eventSelector,
_ => _.StaticKeySelector = new StaticKeySelector { StaticKey = eventSelector.KeySelector.StaticKey ?? string.Empty }),
KeySelectorType.EventOccurred => WithEventType(eventSelector,
_ => _.EventOccurredKeySelector = new EventOccurredKeySelector { Format = eventSelector.KeySelector.OccurredFormat ?? string.Empty }),
_ => throw new UnknownKeySelectorType(eventSelector.KeySelector.Type)
};
}
}
5 changes: 2 additions & 3 deletions Source/Projections/KeyFromEventOccurredAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Dolittle.SDK.Projections.Builder;

namespace Dolittle.SDK.Projections;

Expand All @@ -15,7 +14,7 @@ public class KeyFromEventOccurredAttribute : Attribute, IKeySelectorAttribute
/// <summary>
/// Initializes a new instance of the <see cref="KeyFromEventOccurredAttribute"/> class.
/// </summary>
/// <param name="occurredFormat">The name of the property.</param>
/// <param name="occurredFormat">The date time format.</param>
public KeyFromEventOccurredAttribute(string occurredFormat) => OccurredFormat = occurredFormat;

/// <summary>
Expand All @@ -24,5 +23,5 @@ public class KeyFromEventOccurredAttribute : Attribute, IKeySelectorAttribute
public OccurredFormat OccurredFormat { get; }

/// <inheritdoc/>
public KeySelector KeySelector => KeySelectorBuilder.KeyFromEventOccurred(OccurredFormat);
public KeySelector KeySelector => KeySelector.Occurred(OccurredFormat);
}
31 changes: 31 additions & 0 deletions Source/Projections/KeyFromEventSourceAndOccurredAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Dolittle. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;

namespace Dolittle.SDK.Projections;

/// <summary>
/// Decorates a projection method with the <see cref="KeySelectorType.Property" />.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class KeyFromEventSourceAndOccurredAttribute : Attribute, IKeySelectorAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="KeyFromEventSourceAndOccurredAttribute"/> class.
/// </summary>
/// <param name="propertyName">The name of the property.</param>
/// <param name="occurredFormat">The date time format.</param>
public KeyFromEventSourceAndOccurredAttribute(string occurredFormat)
{
OccurredFormat = occurredFormat;
}

/// <summary>
/// Gets the <see cref="OccurredFormat" />.
/// </summary>
public OccurredFormat OccurredFormat { get; }

/// <inheritdoc/>
public KeySelector KeySelector => KeySelector.EventSourceAndOccured(OccurredFormat);
}
5 changes: 2 additions & 3 deletions Source/Projections/KeyFromEventSourceAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Dolittle.SDK.Projections.Builder;

namespace Dolittle.SDK.Projections;

Expand All @@ -13,5 +12,5 @@ namespace Dolittle.SDK.Projections;
public class KeyFromEventSourceAttribute : Attribute, IKeySelectorAttribute
{
/// <inheritdoc/>
public KeySelector KeySelector { get; } = KeySelectorBuilder.KeyFromEventSource();
}
public KeySelector KeySelector => KeySelector.EventSource;
}
23 changes: 23 additions & 0 deletions Source/Projections/KeyFromFunctionAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Dolittle. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;

namespace Dolittle.SDK.Projections;

static class KeySelectorInstance<TKeySelector, TEvent> where TKeySelector : IKeySelector<TEvent>, new() where TEvent : class
{
public static TKeySelector Mapper { get; } = new();
public static KeySelector Instance { get; } = KeySelector.ByFunction<TEvent>(Mapper.Selector);
}

/// <summary>
/// Decorates a projection method with the <see cref="KeySelectorType.Function" />.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class KeyFromFunctionAttribute<TKeySelector, TEvent> : Attribute, IKeySelectorAttribute
where TKeySelector : IKeySelector<TEvent>, new() where TEvent : class
{
/// <inheritdoc/>
public KeySelector KeySelector => KeySelectorInstance<TKeySelector, TEvent>.Instance;
}
5 changes: 2 additions & 3 deletions Source/Projections/KeyFromPartitionAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Dolittle.SDK.Projections.Builder;

namespace Dolittle.SDK.Projections;

Expand All @@ -13,5 +12,5 @@ namespace Dolittle.SDK.Projections;
public class KeyFromPartitionAttribute : Attribute, IKeySelectorAttribute
{
/// <inheritdoc/>
public KeySelector KeySelector { get; } = KeySelectorBuilder.KeyFromPartitionId();
}
public KeySelector KeySelector => KeySelector.Partition;
}
37 changes: 37 additions & 0 deletions Source/Projections/KeyFromPropertyAndOccurredAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Dolittle. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;

namespace Dolittle.SDK.Projections;

/// <summary>
/// Decorates a projection method with the <see cref="KeySelectorType.Property" />.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class KeyFromPropertyAndOccurredAttribute : Attribute, IKeySelectorAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="KeyFromPropertyAttribute"/> class.
/// </summary>
/// <param name="propertyName">The name of the property.</param>
/// <param name="occurredFormat">The date time format.</param>
public KeyFromPropertyAndOccurredAttribute(string propertyName, string occurredFormat)
{
Expression = propertyName;
OccurredFormat = occurredFormat;
}

/// <summary>
/// Gets the <see cref="KeySelector" />.
/// </summary>
public KeySelectorExpression Expression { get; }

/// <summary>
/// Gets the <see cref="OccurredFormat" />.
/// </summary>
public OccurredFormat OccurredFormat { get; }

/// <inheritdoc/>
public KeySelector KeySelector => KeySelector.PropertyAndOccured(Expression, OccurredFormat);
}
3 changes: 1 addition & 2 deletions Source/Projections/KeyFromPropertyAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Dolittle.SDK.Projections.Builder;

namespace Dolittle.SDK.Projections;

Expand All @@ -24,5 +23,5 @@ public class KeyFromPropertyAttribute : Attribute, IKeySelectorAttribute
public KeySelectorExpression Expression { get; }

/// <inheritdoc/>
public KeySelector KeySelector => KeySelectorBuilder.KeyFromProperty(Expression);
public KeySelector KeySelector => KeySelector.Property(Expression);
}
50 changes: 39 additions & 11 deletions Source/Projections/KeySelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,71 @@ namespace Dolittle.SDK.Projections;
/// </summary>
public class KeySelector
{
KeySelector(KeySelectorType type, KeySelectorExpression expression, Key staticKey, OccurredFormat occurredFormat)
KeySelector(KeySelectorType type, KeySelectorExpression expression, Key staticKey, OccurredFormat occurredFormat, Func<object, EventContext, Key>? function)
{
Type = type;
Expression = expression;
StaticKey = staticKey;
OccurredFormat = occurredFormat;
Function = function;
}

public Func<object, EventContext, Key>? Function { get; }

/// <summary>
/// Creates a <see cref="KeySelectorType.PartitionId"/> <see cref="KeySelector"/>.
/// </summary>
/// <returns>The <see cref="KeySelector"/>.</returns>
public static KeySelector Partition { get; } = new(KeySelectorType.PartitionId, "", "", "");
public static KeySelector Partition { get; } = new(KeySelectorType.PartitionId, "", "", "", null);

/// <summary>
/// Creates a <see cref="KeySelectorType.EventSourceId"/> <see cref="KeySelector"/>.
/// </summary>
/// <returns>The <see cref="KeySelector"/>.</returns>
public static KeySelector EventSource { get; } = new(KeySelectorType.EventSourceId, "", "", "");
public static KeySelector EventSource { get; } = new(KeySelectorType.EventSourceId, "", "", "", null);

public static KeySelector ByFunction<TEvent>(Func<TEvent, EventContext, Key> function) where TEvent : class
{
return new KeySelector(KeySelectorType.Function, "", "", "", (evt, eventContext) => function((TEvent)evt, eventContext));
}

/// <summary>
/// Creates a <see cref="KeySelectorType.Property"/> <see cref="KeySelector"/>.
/// </summary>
/// <param name="expression">The <see cref="KeySelectorExpression"/>.</param>
/// <returns>The <see cref="KeySelector"/>.</returns>
public static KeySelector Property(KeySelectorExpression expression) => new(KeySelectorType.Property, expression, "", "");
public static KeySelector Property(KeySelectorExpression expression) => new(KeySelectorType.Property, expression, "", "", null);

/// <summary>
/// Creates a <see cref="KeySelectorType.Property"/> <see cref="KeySelector"/>.
/// </summary>
/// <param name="key">The static <see cref="Key"/>.</param>
/// <returns>The <see cref="KeySelector"/>.</returns>
public static KeySelector Static(Key key) => new(KeySelectorType.Static, "", key, "");
public static KeySelector Static(Key key) => new(KeySelectorType.Static, "", key, "", null);

/// <summary>
/// Creates a <see cref="KeySelectorType.Property"/> <see cref="KeySelector"/>.
/// Creates a <see cref="KeySelectorType.EventOccurred"/> <see cref="KeySelector"/>.
/// </summary>
/// <param name="occurredFormat">The <see cref="OccurredFormat"/>.</param>
/// <returns>The <see cref="KeySelector"/>.</returns>
public static KeySelector Occurred(OccurredFormat occurredFormat) => new(KeySelectorType.EventOccurred, "", "", occurredFormat, null);

/// <summary>
/// Creates a <see cref="KeySelectorType.PropertyAndEventOccurred"/> <see cref="KeySelector"/>.
/// </summary>
/// <param name="expression">The <see cref="KeySelectorExpression"/>.</param>
/// <param name="occurredFormat">The <see cref="OccurredFormat"/>.</param>
/// <returns>The <see cref="KeySelector"/>.</returns>
public static KeySelector Occurred(OccurredFormat occurredFormat) => new(KeySelectorType.EventOccurred, "", "", occurredFormat);
public static KeySelector PropertyAndOccured(KeySelectorExpression expression, OccurredFormat occurredFormat) =>
new(KeySelectorType.PropertyAndEventOccurred, expression, "", occurredFormat, null);

/// <summary>
/// Creates a <see cref="KeySelectorType.EventSourceIdAndOccurred"/> <see cref="KeySelector"/>.
/// </summary>
/// <param name="occurredFormat">The <see cref="OccurredFormat"/>.</param>
/// <returns>The <see cref="KeySelector"/>.</returns>
public static KeySelector EventSourceAndOccured(OccurredFormat occurredFormat) =>
new(KeySelectorType.EventSourceIdAndOccurred, "", "", occurredFormat, null);

/// <summary>
/// Gets the <see cref="KeySelectorType" />.
Expand All @@ -73,18 +98,21 @@ public class KeySelector
/// </summary>
public OccurredFormat OccurredFormat { get; }

public Key GetKey(object evt, EventContext eventContext)
{
return Type switch
public Key GetKey(object evt, EventContext eventContext) =>
Type switch
{
KeySelectorType.PartitionId => eventContext.EventSourceId.Value,
KeySelectorType.EventSourceId => eventContext.EventSourceId.Value,
KeySelectorType.Static => StaticKey,
KeySelectorType.EventOccurred => eventContext.Occurred.ToString(OccurredFormat.Value, CultureInfo.InvariantCulture),
KeySelectorType.Property => GetProperty(evt, Expression),
KeySelectorType.Function => Function!.Invoke(evt, eventContext),
KeySelectorType.EventSourceIdAndOccurred =>
$"{eventContext.EventSourceId.Value}_{eventContext.Occurred.ToString(OccurredFormat.Value, CultureInfo.InvariantCulture)}",
KeySelectorType.PropertyAndEventOccurred =>
$"{GetProperty(evt, Expression)}_{eventContext.Occurred.ToString(OccurredFormat.Value, CultureInfo.InvariantCulture)}",
_ => eventContext.EventSourceId.Value
};
}

static string GetProperty(object evt, KeySelectorExpression expression)
{
Expand Down
Loading
Loading