Skip to content

Commit

Permalink
Added dynamic CommandHandler and QueryHandler discovery to DI interfa…
Browse files Browse the repository at this point in the history
…ce for CQRS pattern.
  • Loading branch information
jasonmwebb-lv committed May 10, 2024
1 parent 31675e0 commit 0addb78
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using RCommon.ApplicationServices.ExecutionResults;
using RCommon.FluentValidation;
using System.Diagnostics;
using System.Reflection;

try
{
Expand All @@ -24,8 +25,13 @@
services.AddRCommon()
.WithCQRS<CqrsBuilder>(cqrs =>
{
cqrs.AddQueryHandler<TestQueryHandler, TestQuery, TestDto>();
cqrs.AddCommandHandler<TestCommandHandler, TestCommand, IExecutionResult>();
// You can do it this way which is pretty straight forward but verbose
//cqrs.AddQueryHandler<TestQueryHandler, TestQuery, TestDto>();
//cqrs.AddCommandHandler<TestCommandHandler, TestCommand, IExecutionResult>();
// Or this way which uses a little magic but is simple
cqrs.AddCommandHandlers((typeof(Program).GetTypeInfo().Assembly));
cqrs.AddQueryHandlers((typeof(Program).GetTypeInfo().Assembly));
})
.WithValidation<FluentValidationBuilder>(validation =>
{
Expand All @@ -37,7 +43,7 @@
options.ValidateQueries = true;
});
});
Console.WriteLine(services.GenerateServiceDescriptorsString());
services.AddTransient<ITestApplicationService, TestApplicationService>();
}).Build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
{
validation.AddValidatorsFromAssemblyContaining(typeof(TestDto));
});
Console.WriteLine(services.GenerateServiceDescriptorsString());
services.AddTransient<ITestApplicationService, TestApplicationService>();
}).Build();
Expand Down
116 changes: 115 additions & 1 deletion Src/RCommon.ApplicationServices/CqrsBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
using Microsoft.Extensions.DependencyInjection;
// The MIT License (MIT)
//
// Copyright (c) 2015-2024 Rasmus Mikkelsen
// https://github.com/eventflow/EventFlow
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


using Microsoft.Extensions.DependencyInjection;
using RCommon.ApplicationServices;
using RCommon.ApplicationServices.Commands;
using RCommon.ApplicationServices.ExecutionResults;
using RCommon.ApplicationServices.Queries;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;

Expand Down Expand Up @@ -58,5 +83,94 @@ public static void AddCommand<TCommand, TCommandHandler, TResult>(this ICqrsBuil
{
builder.Services.AddTransient<ICommandHandler<TResult, TCommand>, TCommandHandler>();
}

public static void AddCommandHandlers(this ICqrsBuilder builder, Assembly fromAssembly, Predicate<Type> predicate = null)
{
predicate = predicate ?? (t => true);
var commandHandlerTypes = fromAssembly
.GetTypes()
.Where(t => t.GetTypeInfo().GetInterfaces().Any(IsCommandHandlerInterface))
.Where(t => !t.HasConstructorParameterOfType(IsCommandHandlerInterface))
.Where(t => predicate(t));
AddCommandHandlers(builder, commandHandlerTypes);
}

public static void AddCommandHandlers(this ICqrsBuilder builder, params Type[] commandHandlerTypes)
{
AddCommandHandlers(builder, (IEnumerable<Type>)commandHandlerTypes);
}

public static void AddCommandHandlers(this ICqrsBuilder builder, IEnumerable<Type> commandHandlerTypes)
{
foreach (var commandHandlerType in commandHandlerTypes)
{
var t = commandHandlerType;
if (t.GetTypeInfo().IsAbstract) continue;
var handlesCommandTypes = t
.GetTypeInfo()
.GetInterfaces()
.Where(IsCommandHandlerInterface)
.ToList();
if (!handlesCommandTypes.Any())
{
throw new ArgumentException($"Type '{commandHandlerType.PrettyPrint()}' does not implement '{typeof(ICommandHandler<,>).PrettyPrint()}'");
}

foreach (var handlesCommandType in handlesCommandTypes)
{
builder.Services.AddTransient(handlesCommandType, t);
}
}
}

private static bool IsCommandHandlerInterface(this Type type)
{
return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(ICommandHandler<,>);
}

public static void AddQueryHandlers(this ICqrsBuilder builder, params Type[] queryHandlerTypes)
{
AddQueryHandlers(builder, (IEnumerable<Type>)queryHandlerTypes);
}

public static void AddQueryHandlers(this ICqrsBuilder builder, Assembly fromAssembly,
Predicate<Type> predicate = null)
{
predicate = predicate ?? (t => true);
var subscribeSynchronousToTypes = fromAssembly
.GetTypes()
.Where(t => t.GetTypeInfo().GetInterfaces().Any(IsQueryHandlerInterface))
.Where(t => !t.HasConstructorParameterOfType(IsQueryHandlerInterface))
.Where(t => predicate(t));
AddQueryHandlers(builder, subscribeSynchronousToTypes);
}

public static void AddQueryHandlers(this ICqrsBuilder builder, IEnumerable<Type> queryHandlerTypes)
{
foreach (var queryHandlerType in queryHandlerTypes)
{
var t = queryHandlerType;
if (t.GetTypeInfo().IsAbstract) continue;
var queryHandlerInterfaces = t
.GetTypeInfo()
.GetInterfaces()
.Where(IsQueryHandlerInterface)
.ToList();
if (!queryHandlerInterfaces.Any())
{
throw new ArgumentException($"Type '{t.PrettyPrint()}' is not an '{typeof(IQueryHandler<,>).PrettyPrint()}'");
}

foreach (var queryHandlerInterface in queryHandlerInterfaces)
{
builder.Services.AddTransient(queryHandlerInterface, t);
}
}
}

private static bool IsQueryHandlerInterface(this Type type)
{
return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(IQueryHandler<,>);
}
}
}
12 changes: 12 additions & 0 deletions Src/RCommon.Core/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,17 @@ private static string PrettyPrintRecursive(Type type, int depth)
? $"{nameParts[0]}<{new string(',', genericArguments.Length - 1)}>"
: $"{nameParts[0]}<{string.Join(",", genericArguments.Select(t => PrettyPrintRecursive(t, depth + 1)))}>";
}

public static bool HasConstructorParameterOfType(this Type type, Predicate<Type> predicate)
{
return type.GetTypeInfo().GetConstructors()
.Any(c => c.GetParameters()
.Any(p => predicate(p.ParameterType)));
}

public static bool IsAssignableTo<T>(this Type type)
{
return typeof(T).GetTypeInfo().IsAssignableFrom(type);
}
}
}

0 comments on commit 0addb78

Please sign in to comment.