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

Simplify Dependency Injection of Projections using IProjectionOf<TReadModel> #124

Merged
merged 10 commits into from
Jan 28, 2022
10 changes: 5 additions & 5 deletions Samples/ASP.NET/Customers/CustomersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ namespace Customers;
[Route("/api/customers")]
public class CustomerController : ControllerBase
{
readonly IProjectionStore _projections;
readonly IProjectionOf<DishesEaten> _dishesEaten;

public CustomerController(IProjectionStore projections)
public CustomerController(IProjectionOf<DishesEaten> dishesEaten)
{
_projections = projections;
_dishesEaten = dishesEaten;
}

[HttpGet("{customer}")]
public async Task<string[]> GetDishesEaten([FromRoute]string customer)
{
var state = await _projections
.Get<DishesEaten>(customer, HttpContext.RequestAborted)
var state = await _dishesEaten
.Get(customer, HttpContext.RequestAborted)
.ConfigureAwait(false);

return state.Dishes;
Expand Down
2 changes: 1 addition & 1 deletion Source/Aggregates/IAggregateOf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ public interface IAggregateOf<TAggregateRoot>
/// <param name="eventSourceId">The <see cref="EventSourceId"/> of the aggregate to get.</param>
/// <returns>The <see cref="IAggregateRootOperations{TAggregate}"/> for the <typeparamref name="TAggregateRoot"/> <see cref="AggregateRoot"/> class.</returns>
IAggregateRootOperations<TAggregateRoot> Get(EventSourceId eventSourceId);
}
}
6 changes: 3 additions & 3 deletions Source/Embeddings/Builder/EmbeddingsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ public IEmbeddingBuilder Create(EmbeddingId embeddingId)
}

/// <inheritdoc />
public IEmbeddingsBuilder Register<TProjection>()
where TProjection : class, new()
=> Register(typeof(TProjection));
public IEmbeddingsBuilder Register<TEmbedding>()
where TEmbedding : class, new()
=> Register(typeof(TEmbedding));

/// <inheritdoc />
public IEmbeddingsBuilder Register(Type type)
Expand Down
7 changes: 7 additions & 0 deletions Source/Projections/Builder/IUnregisteredProjections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Threading;
using Dolittle.SDK.Common;
using Dolittle.SDK.DependencyInversion;
using Dolittle.SDK.Events.Processing;
using Dolittle.SDK.Projections.Store;
using Dolittle.SDK.Projections.Store.Converters;
Expand All @@ -15,6 +16,12 @@ namespace Dolittle.SDK.Projections.Builder;
/// </summary>
public interface IUnregisteredProjections : IUniqueBindings<ProjectionModelId, IProjection>
{

/// <summary>
/// Gets the callback for configuring the <see cref="ITenantScopedProviders"/>.
/// </summary>
ConfigureTenantServices AddTenantScopedServices { get; }

/// <summary>
/// Registers projections.
/// </summary>
Expand Down
46 changes: 23 additions & 23 deletions Source/Projections/Builder/ProjectionSignatures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,19 @@ public delegate ProjectionResult<TReadModel> SyncProjectionSignature<TReadModel,
/// <param name="instance">The instance of the projection to invoke the method on.</param>
/// <param name="event">The event to handle.</param>
/// <param name="projectionContext">The <see cref="ProjectionContext" />.</param>
public delegate Task TaskProjectionMethodSignature<TProjection>(TProjection instance, object @event, ProjectionContext projectionContext)
where TProjection : class, new();
public delegate Task TaskProjectionMethodSignature<TReadModel>(TReadModel instance, object @event, ProjectionContext projectionContext)
where TReadModel : class, new();

/// <summary>
/// Represents the signature for a projection on-method.
/// </summary>
/// <typeparam name="TProjection">The <see cref="Type" /> of the projection.</typeparam>
/// <typeparam name="TReadModel">The <see cref="Type" /> of the projection.</typeparam>
/// <typeparam name="TEvent">The <see cref="Type" /> of the event.</typeparam>
/// <param name="instance">The instance of the projection to invoke the method on.</param>
/// <param name="event">The event to handle.</param>
/// <param name="projectionContext">The <see cref="ProjectionContext" />.</param>
public delegate Task TaskProjectionMethodSignature<TProjection, TEvent>(TProjection instance, TEvent @event, ProjectionContext projectionContext)
where TProjection : class, new()
public delegate Task TaskProjectionMethodSignature<TReadModel, TEvent>(TReadModel instance, TEvent @event, ProjectionContext projectionContext)
where TReadModel : class, new()
where TEvent : class;

/// <summary>
Expand All @@ -91,61 +91,61 @@ public delegate Task TaskProjectionMethodSignature<TProjection, TEvent>(TProject
/// <param name="instance">The instance of the projection to invoke the method on.</param>
/// <param name="event">The event to handle.</param>
/// <param name="projectionContext">The <see cref="ProjectionContext" />.</param>
public delegate Task<ProjectionResultType> TaskResultProjectionMethodSignature<TProjection>(TProjection instance, object @event, ProjectionContext projectionContext)
where TProjection : class, new();
public delegate Task<ProjectionResultType> TaskResultProjectionMethodSignature<TReadModel>(TReadModel instance, object @event, ProjectionContext projectionContext)
where TReadModel : class, new();

/// <summary>
/// Represents the signature for a projection on-method.
/// </summary>
/// <typeparam name="TProjection">The <see cref="Type" /> of the projection.</typeparam>
/// <typeparam name="TReadModel">The <see cref="Type" /> of the projection.</typeparam>
/// <typeparam name="TEvent">The <see cref="Type" /> of the event.</typeparam>
/// <param name="instance">The instance of the projection to invoke the method on.</param>
/// <param name="event">The event to handle.</param>
/// <param name="projectionContext">The <see cref="ProjectionContext" />.</param>
public delegate Task<ProjectionResultType> TaskResultProjectionMethodSignature<TProjection, TEvent>(TProjection instance, TEvent @event, ProjectionContext projectionContext)
where TProjection : class, new()
public delegate Task<ProjectionResultType> TaskResultProjectionMethodSignature<TReadModel, TEvent>(TReadModel instance, TEvent @event, ProjectionContext projectionContext)
where TReadModel : class, new()
where TEvent : class;

/// <summary>
/// Represents the signature for a projection on-method.
/// </summary>
/// <typeparam name="TProjection">The <see cref="Type" /> of the projection.</typeparam>
/// <typeparam name="TReadModel">The <see cref="Type" /> of the projection.</typeparam>
/// <param name="instance">The instance of the projection to invoke the method on.</param>
/// <param name="event">The event to handle.</param>
/// <param name="projectionContext">The <see cref="ProjectionContext" />.</param>
public delegate void SyncProjectionMethodSignature<TProjection>(TProjection instance, object @event, ProjectionContext projectionContext)
where TProjection : class, new();
public delegate void SyncProjectionMethodSignature<TReadModel>(TReadModel instance, object @event, ProjectionContext projectionContext)
where TReadModel : class, new();

/// <summary>
/// Represents the signature for a projection on-method.
/// </summary>
/// <typeparam name="TProjection">The <see cref="Type" /> of the projection.</typeparam>
/// <typeparam name="TReadModel">The <see cref="Type" /> of the projection.</typeparam>
/// <typeparam name="TEvent">The <see cref="Type" /> of the event.</typeparam>
/// <param name="instance">The instance of the projection to invoke the method on.</param>
/// <param name="event">The event to handle.</param>
/// <param name="projectionContext">The <see cref="ProjectionContext" />.</param>
public delegate void SyncProjectionMethodSignature<TProjection, TEvent>(TProjection instance, TEvent @event, ProjectionContext projectionContext)
where TProjection : class, new()
public delegate void SyncProjectionMethodSignature<TReadModel, TEvent>(TReadModel instance, TEvent @event, ProjectionContext projectionContext)
where TReadModel : class, new()
where TEvent : class;

/// <summary>
/// Represents the signature for a projection on-method.
/// </summary>
/// <typeparam name="TProjection">The <see cref="Type" /> of the projection.</typeparam>
/// <typeparam name="TReadModel">The <see cref="Type" /> of the projection.</typeparam>
/// <param name="instance">The instance of the projection to invoke the method on.</param>
/// <param name="event">The event to handle.</param>
/// <param name="projectionContext">The <see cref="ProjectionContext" />.</param>
public delegate ProjectionResultType SyncResultProjectionMethodSignature<TProjection>(TProjection instance, object @event, ProjectionContext projectionContext)
where TProjection : class, new();
public delegate ProjectionResultType SyncResultProjectionMethodSignature<TReadModel>(TReadModel instance, object @event, ProjectionContext projectionContext)
where TReadModel : class, new();

/// <summary>
/// Represents the signature for a projection on-method.
/// </summary>
/// <typeparam name="TProjection">The <see cref="Type" /> of the projection.</typeparam>
/// <typeparam name="TReadModel">The <see cref="Type" /> of the projection.</typeparam>
/// <typeparam name="TEvent">The <see cref="Type" /> of the event.</typeparam>
/// <param name="instance">The instance of the projection to invoke the method on.</param>
/// <param name="event">The event to handle.</param>
/// <param name="projectionContext">The <see cref="ProjectionContext" />.</param>
public delegate ProjectionResultType SyncResultProjectionMethodSignature<TProjection, TEvent>(TProjection instance, TEvent @event, ProjectionContext projectionContext)
where TProjection : class, new()
where TEvent : class;
public delegate ProjectionResultType SyncResultProjectionMethodSignature<TReadModel, TEvent>(TReadModel instance, TEvent @event, ProjectionContext projectionContext)
where TReadModel : class, new()
where TEvent : class;
37 changes: 37 additions & 0 deletions Source/Projections/Builder/UnregisteredProjections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Reflection;
using System.Threading;
using Dolittle.Runtime.Events.Processing.Contracts;
using Dolittle.SDK.Common;
using Dolittle.SDK.Common.Model;
using Dolittle.SDK.DependencyInversion;
using Dolittle.SDK.Events;
using Dolittle.SDK.Events.Processing;
using Dolittle.SDK.Events.Processing.Internal;
using Dolittle.SDK.Projections.Internal;
using Dolittle.SDK.Projections.Store;
using Dolittle.SDK.Projections.Store.Converters;
using Dolittle.SDK.Tenancy;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Dolittle.SDK.Projections.Builder;
Expand All @@ -28,7 +34,11 @@ public UnregisteredProjections(IUniqueBindings<ProjectionModelId, IProjection> p
: base(projections)
{
ReadModelTypes = readModelTypes;
AddTenantScopedServices = AddToContainer;
jakhog marked this conversation as resolved.
Show resolved Hide resolved
}

/// <inheritdoc />
public ConfigureTenantServices AddTenantScopedServices { get; }

/// <inheritdoc />
public void Register(
Expand Down Expand Up @@ -68,4 +78,31 @@ static EventProcessor<ProjectionId, ProjectionRegistrationRequest, ProjectionReq
projectionConverter,
loggerFactory.CreateLogger(processorType)) as EventProcessor<ProjectionId, ProjectionRegistrationRequest, ProjectionRequest, ProjectionResponse>;
}

void AddToContainer(TenantId tenantId, IServiceCollection serviceCollection)
{
foreach (var projection in Values)
{
serviceCollection.AddSingleton(
typeof(IProjectionOf<>).MakeGenericType(projection.ProjectionType),
serviceProvider => GetOfMethodForReadModel(projection.ProjectionType).Invoke(
serviceProvider.GetRequiredService<IProjectionStore>(),
new object[]
{
projection.Identifier,
projection.ScopeId
}));
}
}

static MethodInfo GetOfMethodForReadModel(Type readModelType)
=> typeof(IProjectionStore).GetMethod(
nameof(IProjectionStore.Of),
new[]
{
typeof(ProjectionId),
typeof(ScopeId)
})
?.MakeGenericMethod(readModelType);

}
2 changes: 1 addition & 1 deletion Source/Projections/OnAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ public OnAttribute(string eventTypeId, uint generation = 0)
/// Gets the <see cref="Events.EventType" />.
/// </summary>
public EventType EventType { get; }
}
}
26 changes: 13 additions & 13 deletions Source/Projections/Store/CurrentState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@
namespace Dolittle.SDK.Projections.Store;

/// <summary>
/// Represents the current projection state.
/// Represents the current state of a projection read model.
/// </summary>
/// <typeparam name="TProjection">The <see cref="System.Type" /> of the projection.</typeparam>
public class CurrentState<TProjection>
where TProjection : class, new()
/// <typeparam name="TReadModel">The <see cref="System.Type" /> of the projection.</typeparam>
public class CurrentState<TReadModel>
where TReadModel : class, new()
{
/// <summary>
/// Initializes a new instance of the <see cref="CurrentState{TProjection}"/> class.
/// Initializes a new instance of the <see cref="CurrentState{TReadModel}"/> class.
/// </summary>
/// <param name="state">The current <typeparamref name="TProjection"/> state.</param>
/// <param name="state">The current <typeparamref name="TReadModel"/> state.</param>
/// <param name="type">The <see cref="CurrentStateType" />.</param>
/// <param name="key">The <see cref="Key" />.</param>
public CurrentState(TProjection state, CurrentStateType type, Key key)
public CurrentState(TReadModel state, CurrentStateType type, Key key)
{
State = state;
WasCreatedFromInitialState = type switch
Expand All @@ -33,18 +33,18 @@ public CurrentState(TProjection state, CurrentStateType type, Key key)
public bool WasCreatedFromInitialState { get; }

/// <summary>
/// Gets the state.
/// Gets the current state of the projection read model.
/// </summary>
public TProjection State { get; }
public TReadModel State { get; }

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

/// <summary>
/// Implicitly converts a <see cref="CurrentState{TProjection}" /> to the underlying <typeparamref name="TProjection"/>.
/// Implicitly converts a <see cref="CurrentState{TReadModel}" /> to the underlying <typeparamref name="TReadModel"/>.
/// </summary>
/// <param name="currentState">The <see cref="CurrentState{TProjection}" /> to convert.</param>
public static implicit operator TProjection(CurrentState<TProjection> currentState) => currentState.State;
}
/// <param name="currentState">The <see cref="CurrentState{TReadModel}" /> to convert.</param>
public static implicit operator TReadModel(CurrentState<TReadModel> currentState) => currentState.State;
}
51 changes: 51 additions & 0 deletions Source/Projections/Store/IProjectionOf.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Dolittle. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Dolittle.SDK.Events;

namespace Dolittle.SDK.Projections.Store;

/// <summary>
/// Defines a system that knows about a projection.
/// </summary>
/// <typeparam name="TReadModel">The <see cref="Type" /> of the projection read model.</typeparam>
public interface IProjectionOf<TReadModel>
woksin marked this conversation as resolved.
Show resolved Hide resolved
where TReadModel : class, new()
{
/// <summary>
/// Gets the <see cref="ProjectionId"/> identifier.
/// </summary>
ProjectionId Identifier { get; }

/// <summary>
/// Gets the <see cref="ScopeId"/>.
/// </summary>
ScopeId Scope { get; }

/// <summary>
/// Gets the projection read model by key.
/// </summary>
/// <param name="key">The <see cref="Key" /> of the projection.</param>
/// <param name="cancellation">The <see cref="CancellationToken" />.</param>
/// <returns>A <see cref="Task" /> that, when resolved, returns the <typeparamref name="TReadModel"/> read model.</returns>
Task<TReadModel> Get(Key key, CancellationToken cancellation = default);

/// <summary>
/// Gets the projection state by key.
/// </summary>
/// <param name="key">The <see cref="Key" /> of the projection.</param>
/// <param name="cancellation">The <see cref="CancellationToken" />.</param>
/// <returns>A <see cref="Task{TResult}" /> that, when resolved, returns the <see cref="CurrentState{TReadModel}"/> of <typeparamref name="TReadModel"/>.</returns>
Task<CurrentState<TReadModel>> GetState(Key key, CancellationToken cancellation = default);

/// <summary>
/// Gets all projection read models.
/// </summary>
/// <param name="cancellation">The <see cref="CancellationToken" />.</param>
/// <returns>A <see cref="Task{TResult}" /> that, when resolved, returns the <see cref="IEnumerable{T}" /> of <typeparamref name="TReadModel" />.</returns>
Task<IEnumerable<TReadModel>> GetAll(CancellationToken cancellation = default);
}
Loading