Skip to content

Commit

Permalink
Limiting Joins on children to be oriented around the IdentifiedBy pro…
Browse files Browse the repository at this point in the history
…perty - supporting arbitrary On() will be in conflict with how we identify the children today
  • Loading branch information
einari committed Aug 20, 2024
1 parent 4a261da commit d5e711a
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@ public void Define(IProjectionBuilderFor<Group> builder) => builder
.From<UserAddedToGroup>(b => b
.UsingKey(e => e.UserId))
.Join<UserCreated>(j => j
.On(u => u.UserId)
.Set(m => m.Name).To(e => e.Name)));
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ public void Define(IProjectionBuilderFor<Group> builder) => builder
.From<UserAddedToGroup>(b => b
.UsingKey(e => e.UserId))
.Join<UserCreated>(j => j
.On(u => u.UserId)
.Set(m => m.Name).To(e => e.Name))
.Join<SystemUserCreated>(j => j
.On(u => u.UserId)
.Set(m => m.Name).To(e => e.Name)));
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,5 @@ public void Define(IProjectionBuilderFor<User> builder) => builder
.From<UserAddedToGroup>(b => b
.UsingParentKey(e => e.UserId))
.Join<GroupCreated>(j => j
.On(g => g.GroupId)
.Set(m => m.GroupName).To(e => e.Name)));
}
7 changes: 7 additions & 0 deletions Source/Clients/DotNET/Projections/ChildrenBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,15 @@ public class ChildrenBuilder<TParentModel, TChildModel>(
// TODO: This is not used, but it should be - figure out what the purpose was. The FromEventProperty method is called from ModelPropertiesBuilder
EventType? _fromEventPropertyEventType;
IEventValueExpression? _fromEventPropertyExpression;

#pragma warning restore IDE0052 // Remove unread private members

/// <inheritdoc/>
public bool HasIdentifiedBy => _identifiedBy.IsSet;

/// <inheritdoc/>
public PropertyPath GetIdentifiedBy() => _identifiedBy;

/// <inheritdoc/>
public IChildrenBuilder<TParentModel, TChildModel> IdentifiedBy(PropertyPath propertyPath)
{
Expand Down
19 changes: 18 additions & 1 deletion Source/Clients/DotNET/Projections/IChildrenBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,29 @@

namespace Cratis.Chronicle.Projections;

/// <summary>
/// Defines the base interface for children builders.
/// </summary>
public interface IChildrenBuilder
{
/// <summary>
/// Gets whether or not the builder has identified by.
/// </summary>
bool HasIdentifiedBy { get; }

/// <summary>
/// Gets the property path that identifies the child model in the collection within the parent.
/// </summary>
/// <returns><see cref="PropertyPath"/> for the identified by.</returns>
PropertyPath GetIdentifiedBy();
}

/// <summary>
/// Defines the builder for building out a child relationship on a model.
/// </summary>
/// <typeparam name="TParentModel">Parent model type.</typeparam>
/// <typeparam name="TChildModel">Child model type.</typeparam>
public interface IChildrenBuilder<TParentModel, TChildModel> : IProjectionBuilder<TChildModel, IChildrenBuilder<TParentModel, TChildModel>>
public interface IChildrenBuilder<TParentModel, TChildModel> : IProjectionBuilder<TChildModel, IChildrenBuilder<TParentModel, TChildModel>>, IChildrenBuilder
{
/// <summary>
/// Sets the property that identifies the child model in the collection within the parent.
Expand Down
33 changes: 27 additions & 6 deletions Source/Clients/DotNET/Projections/JoinBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@ namespace Cratis.Chronicle.Projections;
/// <typeparam name="TModel">Model to build for.</typeparam>
/// <typeparam name="TEvent">Event to build for.</typeparam>
/// <typeparam name="TParentBuilder">Type of parent builder.</typeparam>
/// <remarks>
/// Initializes a new instance of the <see cref="JoinBuilder{TModel, TEvent, TParentBuilder}"/>.
/// </remarks>
/// <param name="projectionBuilder">The parent <see cref="IProjectionBuilder{TModel, TParentBuilder}"/>.</param>
public class JoinBuilder<TModel, TEvent, TParentBuilder>(IProjectionBuilder<TModel, TParentBuilder> projectionBuilder)
: ModelPropertiesBuilder<TModel, TEvent, IJoinBuilder<TModel, TEvent>, TParentBuilder>(projectionBuilder), IJoinBuilder<TModel, TEvent>
where TParentBuilder : class
{
readonly IProjectionBuilder<TModel, TParentBuilder> _projectionBuilder = projectionBuilder;
PropertyPath? _on;

/// <inheritdoc/>
Expand All @@ -33,7 +31,14 @@ public IJoinBuilder<TModel, TEvent> On<TProperty>(Expression<Func<TModel, TPrope
/// <inheritdoc/>
public JoinDefinition Build()
{
ThrowIfMissingOn();
ThrowIfMissingOnForRootProjection();
ThrowIfOnSpecifiedForChildProjection();
ThrowIfMissingIdentifiedByForChildProjection();

if (_on is null && _projectionBuilder is IChildrenBuilder childrenBuilder)
{
_on = childrenBuilder.GetIdentifiedBy();
}

return new()
{
Expand All @@ -43,11 +48,27 @@ public JoinDefinition Build()
};
}

void ThrowIfMissingOn()
void ThrowIfMissingOnForRootProjection()
{
if (_on is null)
if (_on is null && _projectionBuilder is not IChildrenBuilder)
{
throw new MissingOnPropertyExpressionWhenJoiningWithEvent(typeof(TModel), typeof(TEvent));
}
}

void ThrowIfOnSpecifiedForChildProjection()
{
if (_on is not null && _projectionBuilder is IChildrenBuilder)
{
throw new OnPropertyShouldNotBeSpecifiedForChildJoin(typeof(TModel), typeof(TEvent));
}
}

void ThrowIfMissingIdentifiedByForChildProjection()
{
if (_on is null && _projectionBuilder is IChildrenBuilder childrenBuilder && !childrenBuilder.HasIdentifiedBy)
{
throw new MissingIdentifiedByPropertyExpressionWhenJoiningWithEvent(typeof(TModel), typeof(TEvent));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Chronicle.Projections;

/// <summary>
/// Exception that is thrown when the on property expression is missing for the root projection.
/// </summary>
/// <param name="modelType">Type of model joining with event.</param>
/// <param name="eventType">Type of event that is being joined.</param>
public class MissingIdentifiedByPropertyExpressionWhenJoiningWithEvent(Type modelType, Type eventType)
: Exception($"Missing the identified by property expression for model of type '{modelType.FullName}' when joining with event of type '{eventType.FullName}'. Use the `.IdentifiedBy()` method on the child builder.");
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ namespace Cratis.Chronicle.Projections;
/// <summary>
/// Exception that gets thrown when missing the on property expression when joining with specific event type.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="MissingOnPropertyExpressionWhenJoiningWithEvent"/> class.
/// </remarks>
/// <param name="modelType">Type of model joining with event.</param>
/// <param name="eventType">Type of event that is being joined.</param>
public class MissingOnPropertyExpressionWhenJoiningWithEvent(Type modelType, Type eventType)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Chronicle.Projections;

/// <summary>
/// Exception that gets thrown when on property should not be specified for child join.
/// </summary>
/// <param name="modelType">Type of model joining with event.</param>
/// <param name="eventType">Type of event that is being joined.</param>
public class OnPropertyShouldNotBeSpecifiedForChildJoin(Type modelType, Type eventType)
: Exception($"On property should not be specified for child join for model of type '{modelType.FullName}' when joining with event of type '{eventType.FullName}', it will implicitly use what is set using the `IdentifiedBy()");

0 comments on commit d5e711a

Please sign in to comment.