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

Add event-driven projection support to event sourcing #87

Merged
merged 2 commits into from
Apr 9, 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
2 changes: 1 addition & 1 deletion src/Neuroglia.Core/Extensions/ICollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

using System.Collections;

namespace Neuroglia;
namespace Neuroglia.Collections;

/// <summary>
/// Defines extensions for <see cref="ICollection"/>s
Expand Down
32 changes: 32 additions & 0 deletions src/Neuroglia.Core/Extensions/IDictionaryExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright © 2021-Present Neuroglia SRL. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"),
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Neuroglia;

/// <summary>
/// Defines extensions for <see cref="IDictionary{TKey, TValue}"/> instances
/// </summary>
public static class IDictionaryExtensions
{

/// <summary>
/// Gets the value at the specified key
/// </summary>
/// <typeparam name="TKey">The type of the <see cref="IDictionary{TKey, TValue}"/> keys</typeparam>
/// <typeparam name="TValue">The type of the <see cref="IDictionary{TKey, TValue}"/> values</typeparam>
/// <param name="dictionary">The extended <see cref="IDictionary{TKey, TValue}"/></param>
/// <param name="key">The key of the value to get</param>
/// <returns>The value with the specified key</returns>
public static TValue Get<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key) => dictionary[key];

}
25 changes: 25 additions & 0 deletions src/Neuroglia.Core/Extensions/ObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,29 @@ public static class ObjectExtensions
return expando;
}

/// <summary>
/// Gets the value returned by the specified property
/// </summary>
/// <param name="source">The extended object</param>
/// <param name="name">The name of the property to get</param>
/// <returns>The value of the specified property</returns>
/// <remarks>This method is used to dynamically get the property of an object, specifically when building an expression, which does not allow dynamic operations</remarks>
public static object GetProperty(this object source, string name)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentException.ThrowIfNullOrWhiteSpace(name);
var property = source.GetType().GetProperty(name) ?? throw new MissingMemberException($"Failed to find a property with the specified name '{name}'", name);
return property.GetValue(source)!;
}

/// <summary>
/// Gets the value returned by the specified property
/// </summary>
/// <typeparam name="T">The type of the property to get</typeparam>
/// <param name="source">The extended object</param>
/// <param name="name">The name of the property to get</param>
/// <returns>The value of the specified property</returns>
/// <remarks>This method is used to dynamically get the property of an object, specifically when building an expression, which does not allow dynamic operations</remarks>
public static T GetProperty<T>(this object source, string name) => (T)source.GetProperty(name);

}
2 changes: 1 addition & 1 deletion src/Neuroglia.Core/PropertyPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public MemberExpression ToExpression(Expression target)
/// </summary>
/// <param name="input">The input to parse</param>
/// <returns>A new <see cref="PropertyPath"/> based on the specified input</returns>
public static PropertyPath Parse(string input) => new PropertyPath(input);
public static PropertyPath Parse(string input) => new(input);

/// <summary>
/// Attempts to parse the specified input into a new <see cref="PropertyPath"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright © 2021-Present Neuroglia SRL. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"),
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Neuroglia.Data.Infrastructure.EventSourcing.Services;

namespace Neuroglia.Data.Infrastructure.EventSourcing;

/// <summary>
/// Defines constants and statics used to help expressing event-driven projections
/// </summary>
public static class Projection
{

/// <summary>
/// Placeholder method used by <see cref="IProjectionBuilder{TState}"/> implementations to link a processed event to a specific stream
/// </summary>
/// <param name="stream">The stream to link the processed event to</param>
/// <param name="e">The processed event</param>
public static void LinkEventTo(string stream, IEventRecord e)
{
ArgumentException.ThrowIfNullOrWhiteSpace(stream);
ArgumentNullException.ThrowIfNull(e);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright © 2021-Present Neuroglia SRL. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"),
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System.Linq.Expressions;

namespace Neuroglia.Data.Infrastructure.EventSourcing.Services;

/// <summary>
/// Defines the fundamentals of a service used to build event-driven projections
/// </summary>
/// <typeparam name="TState">The type of the state of the event-driven projection to create</typeparam>
public interface IProjectionBuilder<TState>
{

/// <summary>
/// Configures the <see cref="Func{TResult}"/> used to create the projection's initial state
/// </summary>
/// <param name="factory">The <see cref="Func{TResult}"/> used to create the projection's initial state</param>
/// <returns>The configured <see cref="IProjectionBuilder{TState}"/></returns>
IProjectionBuilder<TState> Given(Expression<Func<object>> factory);

/// <summary>
/// Configures the predicate used to to filter incoming event records based on the current projection state
/// </summary>
/// <param name="predicate">A <see cref="Func{T1, T2, TResult}"/> used to to filter incoming event records based on the current projection state</param>
/// <returns>The configured <see cref="IProjectionBuilder{TState}"/></returns>
IProjectionBuilder<TState> When(Expression<Func<TState, IEventRecord, bool>> predicate);

/// <summary>
/// Configures an <see cref="Action{T1, T2}"/> to be performed on the projection state when a matching event record is processed
/// </summary>
/// <param name="action">An <see cref="Action{T1, T2}"/> to be performed on the projection state when a matching event record is processed</param>
/// <returns>The configured <see cref="IProjectionBuilder{TState}"/></returns>
IProjectionBuilder<TState> Then(Expression<Action<TState, IEventRecord>> action);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright © 2021-Present Neuroglia SRL. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"),
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Neuroglia.Data.Infrastructure.EventSourcing.Services;

/// <summary>
/// Defines the fundamentals of a service used to create and retrieve event-driven projections
/// </summary>
public interface IProjectionManager
{

/// <summary>
/// Creates a new event-driven projection
/// </summary>
/// <typeparam name="TState">The type of the state of the projection to create</typeparam>
/// <param name="name">The name of the projection to create</param>
/// <param name="setup">An <see cref="Action{T}"/> used to build and configure the event-driven projection to create</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
/// <returns>A new awaitable <see cref="Task"/></returns>
Task CreateAsync<TState>(string name, Action<IProjectionSourceBuilder<TState>> setup, CancellationToken cancellationToken = default);

/// <summary>
/// Retrieves the current state of the event-driven projection with the specified name
/// </summary>
/// <typeparam name="TState">The type of the projection's state</typeparam>
/// <param name="name">The name of the event-driven projection to get the current state of</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
/// <returns>The current state of the specified event-driven projection</returns>
Task<TState> GetStateAsync<TState>(string name, CancellationToken cancellationToken = default);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright © 2021-Present Neuroglia SRL. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"),
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Neuroglia.Data.Infrastructure.EventSourcing.Services;

/// <summary>
/// Defines the fundamentals of a service used to build a new event-driven projection source
/// </summary>
/// <typeparam name="TState">The type of the state of the projection to build the source for</typeparam>
public interface IProjectionSourceBuilder<TState>
{

/// <summary>
/// Builds a new event-driven projection that processes events from the specified stream
/// </summary>
/// <param name="name">The name of the stream from which events will be processed by the projection</param>
/// <returns>A new <see cref="IProjectionBuilder{TState}"/> implementation used to configure the projection to build</returns>
IProjectionBuilder<TState> FromStream(string name);

}
Loading
Loading