diff --git a/README.md b/README.md index a7abc57..9543eaf 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,15 @@ Process management for Shuttle.Esb endpoints using Shuttle.Recall event sourcing [Shuttle.Esb Samples](https://github.com/Shuttle/Shuttle.Esb.Samples) -# Registration / Activation +# Configuration -The required components may be registered by calling `ComponentRegistryExtensions.RegisterProcessManagement(IComponentRegistry)`. +Add the process management services to the `IServiceCollection` as follows: -In order to activate the process managgement functionality you may call `ComponentResolverExtensions.ResolveProcessManagement(IComponentResolver)`. +```c# +services.AddProcessManagement(builder => { + builder.AddAssembly(assembly); + builder.AddAssembly("assemblyName"); +}); +``` +The `builder.AddAssembly()` method will result in all classes that implement `IProcessMessageAssessor` being added to the `IMessageHandlingAssessor` as well as registering the appropriate mappings in the `ProcessActivator` of the `IProcessMessageHandler<>` and `IProcessStartMessageHandler<>` interface implementations. \ No newline at end of file diff --git a/Shuttle.Esb.Process.Tests/DefaultProcessActivatorFixture.cs b/Shuttle.Esb.Process.Tests/DefaultProcessActivatorFixture.cs index b8404f9..7a40e31 100644 --- a/Shuttle.Esb.Process.Tests/DefaultProcessActivatorFixture.cs +++ b/Shuttle.Esb.Process.Tests/DefaultProcessActivatorFixture.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.Extensions.Options; using NUnit.Framework; namespace Shuttle.Esb.Process.Tests @@ -10,18 +11,20 @@ public class DefaultProcessActivatorFixture public void Should_be_able_to_request_mapping_registration() { var transportMessage = new TransportMessage(); - var activator = new DefaultProcessActivator(); - activator.RegisterMappings(); + var processManagementOptions = new ProcessManagementOptions(); + processManagementOptions.AssemblyNames.Add("Shuttle.Esb.Process.Tests"); + + var activator = new ProcessActivator(Options.Create(processManagementOptions)); Assert.IsFalse(activator.IsProcessMessage(transportMessage, new MockNullCommand())); - Assert.IsTrue(activator.IsProcessMessage(transportMessage, new MockRegisterOrderCommand())); - Assert.IsTrue(activator.IsProcessMessage(transportMessage, new MockEMailSentEvent())); - Assert.IsTrue(activator.IsProcessMessage(transportMessage, new MockCompleteOrderCommand())); + Assert.IsTrue(activator.IsProcessMessage(transportMessage, new MockRegisterOrder())); + Assert.IsTrue(activator.IsProcessMessage(transportMessage, new MockEMailSent())); + Assert.IsTrue(activator.IsProcessMessage(transportMessage, new MockCompleteOrder())); - Assert.IsTrue(activator.IsProcessMessage(transportMessage, new MockRegisterMemberCommand())); - Assert.IsTrue(activator.IsProcessMessage(transportMessage, new MockCompleteMemberRegistrationCommand())); + Assert.IsTrue(activator.IsProcessMessage(transportMessage, new MockRegisterMember())); + Assert.IsTrue(activator.IsProcessMessage(transportMessage, new MockCompleteMemberRegistration())); } [Test] @@ -32,14 +35,15 @@ public void Should_be_able_to_resolve_message_to_multiple_processes() CorrelationId = Guid.NewGuid().ToString() }; - var activator = new DefaultProcessActivator(); + var processManagementOptions = new ProcessManagementOptions(); + processManagementOptions.AssemblyNames.Add("Shuttle.Esb.Process.Tests"); - activator.RegisterMappings(); + var activator = new ProcessActivator(Options.Create(processManagementOptions)); - activator.RegisterResolver( + activator.RegisterResolver( (transport, message) => new MessageProcessType(typeof (MockOrderProcess), false)); - var instance = activator.Create(transportMessage, new MockEMailSentEvent()); + var instance = activator.Create(transportMessage, new MockEMailSent()); Assert.IsTrue(instance.GetType() == typeof (MockOrderProcess)); } @@ -48,34 +52,39 @@ public void Should_be_able_to_resolve_message_to_multiple_processes() public void Should_be_able_to_start_process() { var transportMessage = new TransportMessage(); - var activator = new DefaultProcessActivator(); - activator.RegisterMappings(); + var processManagementOptions = new ProcessManagementOptions(); + processManagementOptions.AssemblyNames.Add("Shuttle.Esb.Process.Tests"); + + var activator = new ProcessActivator(Options.Create(processManagementOptions)); Assert.IsTrue(typeof (MockOrderProcess) == - activator.Create(transportMessage, new MockRegisterOrderCommand()).GetType()); + activator.Create(transportMessage, new MockRegisterOrder()).GetType()); Assert.IsTrue(typeof (MockMemberRegistrationProcess) == - activator.Create(transportMessage, new MockRegisterMemberCommand()).GetType()); + activator.Create(transportMessage, new MockRegisterMember()).GetType()); } [Test] public void Should_throw_exception_when_creating_unknown_process() { var transportMessage = new TransportMessage(); - var activator = new DefaultProcessActivator(); - Assert.Throws(() => activator.Create(transportMessage, new MockRegisterMemberCommand())); + var activator = new ProcessActivator(Options.Create(new ProcessManagementOptions())); + + Assert.Throws(() => activator.Create(transportMessage, new MockNullCommand())); } [Test] public void Should_throw_exception_when_no_resolver_for_message_to_multiple_processes() { var transportMessage = new TransportMessage(); - var activator = new DefaultProcessActivator(); - activator.RegisterMappings(); + var processManagementOptions = new ProcessManagementOptions(); + processManagementOptions.AssemblyNames.Add("Shuttle.Esb.Process.Tests"); + + var activator = new ProcessActivator(Options.Create(processManagementOptions)); - Assert.Throws(() => activator.Create(transportMessage, new MockEMailSentEvent())); + Assert.Throws(() => activator.Create(transportMessage, new MockEMailSent())); } } } \ No newline at end of file diff --git a/Shuttle.Esb.Process.Tests/Mocks/MockCompleteMemberRegistration.cs b/Shuttle.Esb.Process.Tests/Mocks/MockCompleteMemberRegistration.cs new file mode 100644 index 0000000..0d2637d --- /dev/null +++ b/Shuttle.Esb.Process.Tests/Mocks/MockCompleteMemberRegistration.cs @@ -0,0 +1,6 @@ +namespace Shuttle.Esb.Process.Tests +{ + public class MockCompleteMemberRegistration + { + } +} \ No newline at end of file diff --git a/Shuttle.Esb.Process.Tests/Mocks/MockCompleteMemberRegistrationCommand.cs b/Shuttle.Esb.Process.Tests/Mocks/MockCompleteMemberRegistrationCommand.cs deleted file mode 100644 index 46f0b6f..0000000 --- a/Shuttle.Esb.Process.Tests/Mocks/MockCompleteMemberRegistrationCommand.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Shuttle.Esb.Process.Tests -{ - public class MockCompleteMemberRegistrationCommand - { - } -} \ No newline at end of file diff --git a/Shuttle.Esb.Process.Tests/Mocks/MockEMailSentEvent.cs b/Shuttle.Esb.Process.Tests/Mocks/MockCompleteOrder.cs similarity index 59% rename from Shuttle.Esb.Process.Tests/Mocks/MockEMailSentEvent.cs rename to Shuttle.Esb.Process.Tests/Mocks/MockCompleteOrder.cs index c3d1f1a..aa6bdd8 100644 --- a/Shuttle.Esb.Process.Tests/Mocks/MockEMailSentEvent.cs +++ b/Shuttle.Esb.Process.Tests/Mocks/MockCompleteOrder.cs @@ -1,6 +1,6 @@ namespace Shuttle.Esb.Process.Tests { - public class MockEMailSentEvent + public class MockCompleteOrder { } } \ No newline at end of file diff --git a/Shuttle.Esb.Process.Tests/Mocks/MockRegisterOrderCommand.cs b/Shuttle.Esb.Process.Tests/Mocks/MockEMailSent.cs similarity index 55% rename from Shuttle.Esb.Process.Tests/Mocks/MockRegisterOrderCommand.cs rename to Shuttle.Esb.Process.Tests/Mocks/MockEMailSent.cs index 1d484c4..97c49db 100644 --- a/Shuttle.Esb.Process.Tests/Mocks/MockRegisterOrderCommand.cs +++ b/Shuttle.Esb.Process.Tests/Mocks/MockEMailSent.cs @@ -1,6 +1,6 @@ namespace Shuttle.Esb.Process.Tests { - public class MockRegisterOrderCommand + public class MockEMailSent { } } \ No newline at end of file diff --git a/Shuttle.Esb.Process.Tests/Mocks/MockMemberRegistrationProcess.cs b/Shuttle.Esb.Process.Tests/Mocks/MockMemberRegistrationProcess.cs index 613cc04..3cb11ce 100644 --- a/Shuttle.Esb.Process.Tests/Mocks/MockMemberRegistrationProcess.cs +++ b/Shuttle.Esb.Process.Tests/Mocks/MockMemberRegistrationProcess.cs @@ -4,23 +4,23 @@ namespace Shuttle.Esb.Process.Tests { public class MockMemberRegistrationProcess : IProcessManager, - IProcessStartMessageHandler, - IProcessMessageHandler, - IProcessMessageHandler + IProcessStartMessageHandler, + IProcessMessageHandler, + IProcessMessageHandler { public Guid CorrelationId { get; set; } - public void ProcessMessage(IProcessHandlerContext context) + public void ProcessMessage(IProcessHandlerContext context) { throw new NotImplementedException(); } - public void ProcessMessage(IProcessHandlerContext context) + public void ProcessMessage(IProcessHandlerContext context) { throw new NotImplementedException(); } - public void ProcessMessage(IProcessHandlerContext context) + public void ProcessMessage(IProcessHandlerContext context) { throw new NotImplementedException(); } diff --git a/Shuttle.Esb.Process.Tests/Mocks/MockOrderProcess.cs b/Shuttle.Esb.Process.Tests/Mocks/MockOrderProcess.cs index 06b4ec5..f51d94c 100644 --- a/Shuttle.Esb.Process.Tests/Mocks/MockOrderProcess.cs +++ b/Shuttle.Esb.Process.Tests/Mocks/MockOrderProcess.cs @@ -4,23 +4,23 @@ namespace Shuttle.Esb.Process.Tests { public class MockOrderProcess : IProcessManager, - IProcessStartMessageHandler, - IProcessMessageHandler, - IProcessMessageHandler + IProcessStartMessageHandler, + IProcessMessageHandler, + IProcessMessageHandler { public Guid CorrelationId { get; set; } - public void ProcessMessage(IProcessHandlerContext context) + public void ProcessMessage(IProcessHandlerContext context) { throw new NotImplementedException(); } - public void ProcessMessage(IProcessHandlerContext context) + public void ProcessMessage(IProcessHandlerContext context) { throw new NotImplementedException(); } - public void ProcessMessage(IProcessHandlerContext context) + public void ProcessMessage(IProcessHandlerContext context) { throw new NotImplementedException(); } diff --git a/Shuttle.Esb.Process.Tests/Mocks/MockCompleteOrderCommand.cs b/Shuttle.Esb.Process.Tests/Mocks/MockRegisterMember.cs similarity index 55% rename from Shuttle.Esb.Process.Tests/Mocks/MockCompleteOrderCommand.cs rename to Shuttle.Esb.Process.Tests/Mocks/MockRegisterMember.cs index 6ec178d..122809c 100644 --- a/Shuttle.Esb.Process.Tests/Mocks/MockCompleteOrderCommand.cs +++ b/Shuttle.Esb.Process.Tests/Mocks/MockRegisterMember.cs @@ -1,6 +1,6 @@ namespace Shuttle.Esb.Process.Tests { - public class MockCompleteOrderCommand + public class MockRegisterMember { } } \ No newline at end of file diff --git a/Shuttle.Esb.Process.Tests/Mocks/MockRegisterMemberCommand.cs b/Shuttle.Esb.Process.Tests/Mocks/MockRegisterOrder.cs similarity index 54% rename from Shuttle.Esb.Process.Tests/Mocks/MockRegisterMemberCommand.cs rename to Shuttle.Esb.Process.Tests/Mocks/MockRegisterOrder.cs index 4d5c280..86ec1ca 100644 --- a/Shuttle.Esb.Process.Tests/Mocks/MockRegisterMemberCommand.cs +++ b/Shuttle.Esb.Process.Tests/Mocks/MockRegisterOrder.cs @@ -1,6 +1,6 @@ namespace Shuttle.Esb.Process.Tests { - public class MockRegisterMemberCommand + public class MockRegisterOrder { } } \ No newline at end of file diff --git a/Shuttle.Esb.Process.Tests/Shuttle.Esb.Process.Tests.csproj b/Shuttle.Esb.Process.Tests/Shuttle.Esb.Process.Tests.csproj index 5e22256..52d794d 100644 --- a/Shuttle.Esb.Process.Tests/Shuttle.Esb.Process.Tests.csproj +++ b/Shuttle.Esb.Process.Tests/Shuttle.Esb.Process.Tests.csproj @@ -8,8 +8,7 @@ - - + diff --git a/Shuttle.Esb.Process/.build/Shuttle.MSBuild.dll b/Shuttle.Esb.Process/.build/Shuttle.MSBuild.dll deleted file mode 100644 index 1713c42..0000000 Binary files a/Shuttle.Esb.Process/.build/Shuttle.MSBuild.dll and /dev/null differ diff --git a/Shuttle.Esb.Process/.build/Shuttle.MSBuild.targets b/Shuttle.Esb.Process/.build/Shuttle.MSBuild.targets deleted file mode 100644 index 070e08e..0000000 --- a/Shuttle.Esb.Process/.build/Shuttle.MSBuild.targets +++ /dev/null @@ -1,11 +0,0 @@ - - - - Shuttle.MSBuild.dll - - - - - - - diff --git a/Shuttle.Esb.Process/.build/package.msbuild b/Shuttle.Esb.Process/.build/package.msbuild deleted file mode 100644 index 173443c..0000000 --- a/Shuttle.Esb.Process/.build/package.msbuild +++ /dev/null @@ -1,68 +0,0 @@ - - - deployment - Shuttle.Esb.Process - https://www.nuget.org - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Shuttle.Esb.Process/.build/package.nuspec b/Shuttle.Esb.Process/.build/package.nuspec deleted file mode 100644 index c598d33..0000000 --- a/Shuttle.Esb.Process/.build/package.nuspec +++ /dev/null @@ -1,27 +0,0 @@ - - - - Shuttle.Esb.Process - {semver} - Eben Roux - Eben Roux - BSD-3-Clause - https://github.com/shuttle/Shuttle.Esb.Process - images\logo.png - - false - Shuttle.Esb process management using Shuttle.Recall event sourcing. - - Copyright (c) {year}, Eben Roux - shuttle shuttle.esb shuttle.recall process processmanager processmanagement saga - - - - - - - - - - - \ No newline at end of file diff --git a/Shuttle.Esb.Process/.package/AssemblyInfo.cs.template b/Shuttle.Esb.Process/.package/AssemblyInfo.cs.template new file mode 100644 index 0000000..2ac5441 --- /dev/null +++ b/Shuttle.Esb.Process/.package/AssemblyInfo.cs.template @@ -0,0 +1,22 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +#if NETFRAMEWORK +[assembly: AssemblyTitle(".NET Framework")] +#endif + +#if NETCOREAPP +[assembly: AssemblyTitle(".NET Core")] +#endif + +#if NETSTANDARD +[assembly: AssemblyTitle(".NET Standard")] +#endif + +[assembly: AssemblyVersion("#{SemanticVersionCore}#.0")] +[assembly: AssemblyCopyright("Copyright (c) #{Year}#, Eben Roux")] +[assembly: AssemblyProduct("Shuttle.Esb.Process")] +[assembly: AssemblyCompany("Eben Roux")] +[assembly: AssemblyConfiguration("Release")] +[assembly: AssemblyInformationalVersion("#{SemanticVersion}#")] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/Shuttle.Esb.Process/.package/Shuttle.NuGetPackager.MSBuild.dll b/Shuttle.Esb.Process/.package/Shuttle.NuGetPackager.MSBuild.dll new file mode 100644 index 0000000..8516f73 Binary files /dev/null and b/Shuttle.Esb.Process/.package/Shuttle.NuGetPackager.MSBuild.dll differ diff --git a/Shuttle.Esb.Process/.package/Shuttle.NuGetPackager.targets b/Shuttle.Esb.Process/.package/Shuttle.NuGetPackager.targets new file mode 100644 index 0000000..d714aa8 --- /dev/null +++ b/Shuttle.Esb.Process/.package/Shuttle.NuGetPackager.targets @@ -0,0 +1,12 @@ + + + + Shuttle.NuGetPackager.MSBuild.dll + + + + + + + + diff --git a/Shuttle.Esb.Process/.package/package.msbuild b/Shuttle.Esb.Process/.package/package.msbuild new file mode 100644 index 0000000..689692e --- /dev/null +++ b/Shuttle.Esb.Process/.package/package.msbuild @@ -0,0 +1,104 @@ + + + Shuttle.Esb.Process + https://www.nuget.org + Release + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Shuttle.Esb.Process/.package/package.nuspec b/Shuttle.Esb.Process/.package/package.nuspec new file mode 100644 index 0000000..e1a39bb --- /dev/null +++ b/Shuttle.Esb.Process/.package/package.nuspec @@ -0,0 +1,29 @@ + + + + + Shuttle.Esb.Process + 13.0.0 + Eben Roux + Eben Roux + BSD-3-Clause + false + images\logo.png + docs\README.md + + https://github.com/Shuttle/Shuttle.Esb.Process + Shuttle.Esb process management using Shuttle.Recall event sourcing. + Copyright (c) 2022, Eben Roux + shuttle.recall process processmanager processmanagement saga + + + + + + + + + + + + diff --git a/Shuttle.Esb.Process/.package/package.nuspec.template b/Shuttle.Esb.Process/.package/package.nuspec.template new file mode 100644 index 0000000..5b05657 --- /dev/null +++ b/Shuttle.Esb.Process/.package/package.nuspec.template @@ -0,0 +1,27 @@ + + + + + Shuttle.Esb.Process + #{SemanticVersion}# + Eben Roux + Eben Roux + BSD-3-Clause + false + images\logo.png + docs\README.md + + https://github.com/Shuttle/Shuttle.Esb.Process + Shuttle.Esb process management using Shuttle.Recall event sourcing. + Copyright (c) #{Year}#, Eben Roux + shuttle.recall process processmanager processmanagement saga + +#{Dependencies}# + + + + + + + + diff --git a/Shuttle.Esb.Process/ComponentRegistryExtensions.cs b/Shuttle.Esb.Process/ComponentRegistryExtensions.cs deleted file mode 100644 index 69eeea3..0000000 --- a/Shuttle.Esb.Process/ComponentRegistryExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Shuttle.Core.Container; -using Shuttle.Core.Contract; - -namespace Shuttle.Esb.Process -{ - public static class ComponentRegistryExtensions - { - public static void RegisterProcessManagement(this IComponentRegistry registry) - { - Guard.AgainstNull(registry, nameof(registry)); - - if (!registry.IsRegistered()) - { - registry.AttemptRegisterInstance(ProcessSection.Configuration()); - } - - registry.AttemptRegister(); - registry.AttemptRegister(); - } - } -} \ No newline at end of file diff --git a/Shuttle.Esb.Process/ComponentResolverExtensions.cs b/Shuttle.Esb.Process/ComponentResolverExtensions.cs deleted file mode 100644 index 115188f..0000000 --- a/Shuttle.Esb.Process/ComponentResolverExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Shuttle.Core.Container; -using Shuttle.Core.Contract; - -namespace Shuttle.Esb.Process -{ - public static class ComponentResolverExtensions - { - public static void ResolveProcessManagement(this IComponentResolver resolver) - { - Guard.AgainstNull(resolver, "resolver"); - - if (resolver.Resolve() is DefaultProcessActivator processActivator) - { - processActivator.RegisterMappings(); - } - } - } -} \ No newline at end of file diff --git a/Shuttle.Esb.Process/IProcessConfiguration.cs b/Shuttle.Esb.Process/IProcessConfiguration.cs deleted file mode 100644 index 6f392be..0000000 --- a/Shuttle.Esb.Process/IProcessConfiguration.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Shuttle.Esb.Process -{ - public interface IProcessConfiguration - { - string ProviderName { get; set; } - string ConnectionString { get; set; } - } -} \ No newline at end of file diff --git a/Shuttle.Esb.Process/DefaultProcessActivator.cs b/Shuttle.Esb.Process/ProcessActivator.cs similarity index 76% rename from Shuttle.Esb.Process/DefaultProcessActivator.cs rename to Shuttle.Esb.Process/ProcessActivator.cs index ebfe8d1..23375c1 100644 --- a/Shuttle.Esb.Process/DefaultProcessActivator.cs +++ b/Shuttle.Esb.Process/ProcessActivator.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.Extensions.Options; using Shuttle.Core.Contract; using Shuttle.Core.Reflection; namespace Shuttle.Esb.Process { - public class DefaultProcessActivator : IProcessActivator + public class ProcessActivator : IProcessActivator { private static readonly List EmptyMappings = new List(); private static readonly object Padlock = new object(); @@ -17,23 +20,32 @@ public class DefaultProcessActivator : IProcessActivator new Dictionary>(); private readonly Func _processFactoryFunction; + private readonly ReflectionService _reflectionService = new ReflectionService(); - public DefaultProcessActivator() + public ProcessActivator(IOptions processManagementOptions) { + Guard.AgainstNull(processManagementOptions, nameof(processManagementOptions)); + Guard.AgainstNull(processManagementOptions.Value, nameof(processManagementOptions.Value)); + _processFactoryFunction = type => (IProcessManager) Activator.CreateInstance(type); - } - public DefaultProcessActivator(Func processFactoryFunction) - { - Guard.AgainstNull(processFactoryFunction, "processFactoryFunction"); + var assemblies = new List(); - _processFactoryFunction = processFactoryFunction; + assemblies.AddRange((processManagementOptions.Value.AssemblyNames ?? Enumerable.Empty()).Any() + ? processManagementOptions.Value.AssemblyNames.Select(Assembly.Load) + : new ReflectionService().GetRuntimeAssemblies()); + + foreach (var assembly in assemblies) + { + RegisterMappings(assembly, typeof(IProcessMessageHandler<>), false); + RegisterMappings(assembly, typeof(IProcessStartMessageHandler<>), true); + } } public IProcessActivator RegisterResolver( Func resolver) { - Guard.AgainstNull(resolver, "resolver"); + Guard.AgainstNull(resolver, nameof(resolver)); _resolvers.Add(typeof (TMessageType), resolver); @@ -70,8 +82,8 @@ public IProcessActivator RegisterProcessStartMessage(Type messageType, Type proc public bool IsProcessMessage(TransportMessage transportMessage, object message) { - Guard.AgainstNull(transportMessage, "transportMessage"); - Guard.AgainstNull(message, "message"); + Guard.AgainstNull(transportMessage, nameof(transportMessage)); + Guard.AgainstNull(message, nameof(message)); var messageType = message.GetType(); @@ -96,8 +108,8 @@ public bool IsProcessMessage(TransportMessage transportMessage, object message) public IProcessManager Create(TransportMessage transportMessage, object message) { - Guard.AgainstNull(transportMessage, "transportMessage"); - Guard.AgainstNull(message, "message"); + Guard.AgainstNull(transportMessage, nameof(transportMessage)); + Guard.AgainstNull(message, nameof(message)); var messageType = message.GetType(); List mappings; @@ -166,8 +178,8 @@ public IProcessManager Create(TransportMessage transportMessage, object message) private void RegisterProcessMessage(Type messageType, Type processType, bool isStartedByMessage) { - Guard.AgainstNull(messageType, "messageType"); - Guard.AgainstNull(processType, "processType"); + Guard.AgainstNull(messageType, nameof(messageType)); + Guard.AgainstNull(processType, nameof(processType)); lock (Padlock) { @@ -185,17 +197,9 @@ private Func FindResolver(Type mes return !_resolvers.ContainsKey(messageType) ? null : _resolvers[messageType]; } - public void RegisterMappings() + private void RegisterMappings(Assembly assembly, Type interfaceType, bool isStartedByMessage) { - RegisterMappings(typeof (IProcessMessageHandler<>), false); - RegisterMappings(typeof (IProcessStartMessageHandler<>), true); - } - - private void RegisterMappings(Type interfaceType, bool isStartedByMessage) - { - var reflectionService = new ReflectionService(); - - var types = reflectionService.GetTypesAssignableTo(interfaceType); + var types = _reflectionService.GetTypesAssignableTo(interfaceType, assembly); foreach (var type in types) { diff --git a/Shuttle.Esb.Process/ProcessConfiguration.cs b/Shuttle.Esb.Process/ProcessConfiguration.cs deleted file mode 100644 index 69472b8..0000000 --- a/Shuttle.Esb.Process/ProcessConfiguration.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Shuttle.Esb.Process -{ - public class ProcessConfiguration : IProcessConfiguration - { - public string ProviderName { get; set; } - public string ConnectionString { get; set; } - } -} \ No newline at end of file diff --git a/Shuttle.Esb.Process/ProcessHandlerContext.cs b/Shuttle.Esb.Process/ProcessHandlerContext.cs index ebcd45b..30248ae 100644 --- a/Shuttle.Esb.Process/ProcessHandlerContext.cs +++ b/Shuttle.Esb.Process/ProcessHandlerContext.cs @@ -1,17 +1,16 @@ using System.Threading; -using Shuttle.Core.Pipelines; -using Shuttle.Core.Threading; +using Shuttle.Core.Contract; using Shuttle.Recall; namespace Shuttle.Esb.Process { public class ProcessHandlerContext : HandlerContext, IProcessHandlerContext where T : class { - public ProcessHandlerContext(ITransportMessageFactory transportMessageFactory, - IPipelineFactory pipelineFactory, ISubscriptionManager subscriptionManager, TransportMessage transportMessage, - T message, CancellationToken cancellationToken, EventStream stream) : - base(transportMessageFactory, pipelineFactory, subscriptionManager, transportMessage, message, cancellationToken) + public ProcessHandlerContext(EventStream stream, IMessageSender messageSender, TransportMessage transportMessage, + T message, CancellationToken cancellationToken) : + base(messageSender, transportMessage, message, cancellationToken) { + Guard.AgainstNull(stream, nameof(stream)); Stream = stream; } diff --git a/Shuttle.Esb.Process/ProcessManagementBuilder.cs b/Shuttle.Esb.Process/ProcessManagementBuilder.cs new file mode 100644 index 0000000..aeda131 --- /dev/null +++ b/Shuttle.Esb.Process/ProcessManagementBuilder.cs @@ -0,0 +1,48 @@ +using System; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Shuttle.Core.Contract; + +namespace Shuttle.Esb.Process +{ + public class ProcessManagementBuilder + { + private ProcessManagementOptions _processManagementOptions = new ProcessManagementOptions(); + + public IServiceCollection Services { get; } + + public ProcessManagementBuilder(IServiceCollection services) + { + Guard.AgainstNull(services, nameof(services)); + + Services = services; + } + + public ProcessManagementOptions Options + { + get => _processManagementOptions; + set => _processManagementOptions = value ?? throw new ArgumentNullException(nameof(value)); + } + + public ProcessManagementBuilder AddAssembly(Assembly assembly) + { + Guard.AgainstNull(assembly, nameof(assembly)); + + AddAssembly(assembly.FullName); + + return this; + } + + private ProcessManagementBuilder AddAssembly(string name) + { + Guard.AgainstNullOrEmptyString(name, nameof(name)); + + if (!Options.AssemblyNames.Contains(name)) + { + Options.AssemblyNames.Add(name); + } + + return this; + } + } +} \ No newline at end of file diff --git a/Shuttle.Esb.Process/ProcessManagementOptions.cs b/Shuttle.Esb.Process/ProcessManagementOptions.cs new file mode 100644 index 0000000..b9ce22d --- /dev/null +++ b/Shuttle.Esb.Process/ProcessManagementOptions.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Shuttle.Esb.Process +{ + public class ProcessManagementOptions + { + public const string SectionName = "Shuttle:ProcessManagement"; + + public string ConnectionStringName { get; set; } + + public List AssemblyNames { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/Shuttle.Esb.Process/ProcessManagementOptionsValidator.cs b/Shuttle.Esb.Process/ProcessManagementOptionsValidator.cs new file mode 100644 index 0000000..57f751d --- /dev/null +++ b/Shuttle.Esb.Process/ProcessManagementOptionsValidator.cs @@ -0,0 +1,34 @@ +using System; +using System.Reflection; +using Microsoft.Extensions.Options; +using Shuttle.Core.Contract; + +namespace Shuttle.Esb.Process +{ + public class ProcessManagementOptionsValidator : IValidateOptions + { + public ValidateOptionsResult Validate(string name, ProcessManagementOptions options) + { + Guard.AgainstNull(options, nameof(options)); + + if (string.IsNullOrWhiteSpace(options.ConnectionStringName)) + { + return ValidateOptionsResult.Fail(Resources.ConnectionStringNameException); + } + + foreach (var assemblyName in options.AssemblyNames) + { + try + { + Assembly.Load(assemblyName); + } + catch + { + return ValidateOptionsResult.Fail(string.Format(Resources.AssemblyNameException, assemblyName)); + } + } + + return ValidateOptionsResult.Success; + } + } +} \ No newline at end of file diff --git a/Shuttle.Esb.Process/ProcessMessageHandlerInvoker.cs b/Shuttle.Esb.Process/ProcessMessageHandlerInvoker.cs index 9d70462..d9b1cf2 100644 --- a/Shuttle.Esb.Process/ProcessMessageHandlerInvoker.cs +++ b/Shuttle.Esb.Process/ProcessMessageHandlerInvoker.cs @@ -1,5 +1,8 @@ using System; -using Shuttle.Core.Container; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.Extensions.Options; using Shuttle.Core.Contract; using Shuttle.Core.Data; using Shuttle.Core.Pipelines; @@ -12,52 +15,66 @@ namespace Shuttle.Esb.Process public class ProcessMessageHandlerInvoker : IMessageHandlerInvoker { private readonly IDatabaseContextFactory _databaseContextFactory; - private readonly IMessageHandlerInvoker _defaultMessageHandlerInvoker; + private readonly IMessageHandlerInvoker _messageHandlerInvoker; private readonly IEventStore _eventStore; - private readonly IPipelineFactory _pipelineFactory; + private readonly IMessageSender _messageSender; private readonly IProcessActivator _processActivator; - private readonly IProcessConfiguration _processConfiguration; - private readonly ISubscriptionManager _subscriptionManager; - private readonly ITransportMessageFactory _transportMessageFactory; - - public ProcessMessageHandlerInvoker(IComponentResolver resolver, IProcessConfiguration processConfiguration, - IProcessActivator processActivator, ITransportMessageFactory transportMessageFactory, - IPipelineFactory pipelineFactory, ISubscriptionManager subscriptionManager, - IDatabaseContextFactory databaseContextFactory, IEventStore eventStore, - IMessageHandlingAssessor messageHandlingAssessor) + private readonly string _providerName; + private readonly string _connectionString; + + public ProcessMessageHandlerInvoker(IServiceProvider serviceProvider, IOptionsMonitor connectionStringOptions, IOptions processManagementOptions, IMessageSender messageSender, IProcessActivator processActivator, IDatabaseContextFactory databaseContextFactory, IEventStore eventStore, IMessageHandlingAssessor messageHandlingAssessor) { - Guard.AgainstNull(resolver, nameof(resolver)); - Guard.AgainstNull(processConfiguration, nameof(processConfiguration)); + Guard.AgainstNull(serviceProvider, nameof(serviceProvider)); + Guard.AgainstNull(connectionStringOptions, nameof(connectionStringOptions)); + Guard.AgainstNull(processManagementOptions, nameof(processManagementOptions)); + Guard.AgainstNull(processManagementOptions.Value, nameof(processManagementOptions.Value)); + Guard.AgainstNull(messageSender, nameof(messageSender)); Guard.AgainstNull(processActivator, nameof(processActivator)); - Guard.AgainstNull(transportMessageFactory, nameof(transportMessageFactory)); - Guard.AgainstNull(pipelineFactory, nameof(pipelineFactory)); - Guard.AgainstNull(subscriptionManager, nameof(subscriptionManager)); Guard.AgainstNull(databaseContextFactory, nameof(databaseContextFactory)); Guard.AgainstNull(eventStore, nameof(eventStore)); Guard.AgainstNull(messageHandlingAssessor, nameof(messageHandlingAssessor)); - _transportMessageFactory = transportMessageFactory; - _pipelineFactory = pipelineFactory; - _subscriptionManager = subscriptionManager; + _messageSender = messageSender; _databaseContextFactory = databaseContextFactory; _eventStore = eventStore; - _processConfiguration = processConfiguration; _processActivator = processActivator; - _defaultMessageHandlerInvoker = new DefaultMessageHandlerInvoker(resolver, pipelineFactory, - subscriptionManager, transportMessageFactory); + _messageHandlerInvoker = new MessageHandlerInvoker(serviceProvider, messageSender); + + var options = connectionStringOptions.Get(processManagementOptions.Value.ConnectionStringName); - foreach (var type in new ReflectionService().GetTypesAssignableTo()) + if (options == null) { - try - { - var specificationInstance = Activator.CreateInstance(type); + throw new InvalidOperationException(string.Format( + Core.Data.Resources.ConnectionStringMissingException, + processManagementOptions.Value.ConnectionStringName)); + } - messageHandlingAssessor.RegisterAssessor((ISpecification) specificationInstance); - } - catch + _providerName = options.ProviderName; + _connectionString = options.ConnectionString; + + var reflectionService = new ReflectionService(); + + var assemblies = new List(); + + assemblies.AddRange((processManagementOptions.Value.AssemblyNames ?? Enumerable.Empty()).Any() + ? processManagementOptions.Value.AssemblyNames.Select(Assembly.Load) + : new ReflectionService().GetRuntimeAssemblies()); + + foreach (var assembly in assemblies) + { + foreach (var type in reflectionService.GetTypesAssignableTo(assembly)) { - throw new ProcessException(string.Format(Resources.MissingProcessAssessorConstructor, - type.AssemblyQualifiedName)); + try + { + var specificationInstance = Activator.CreateInstance(type); + + messageHandlingAssessor.RegisterAssessor((ISpecification)specificationInstance); + } + catch + { + throw new ProcessException(string.Format(Resources.MissingProcessAssessorConstructor, + type.AssemblyQualifiedName)); + } } } } @@ -70,15 +87,14 @@ public MessageHandlerInvokeResult Invoke(IPipelineEvent pipelineEvent) if (!_processActivator.IsProcessMessage(transportMessage, message)) { - return _defaultMessageHandlerInvoker.Invoke(pipelineEvent); + return _messageHandlerInvoker.Invoke(pipelineEvent); } var processInstance = _processActivator.Create(transportMessage, message); EventStream stream; - using (_databaseContextFactory.Create(_processConfiguration.ProviderName, - _processConfiguration.ConnectionString)) + using (_databaseContextFactory.Create(_providerName, _connectionString)) { stream = _eventStore.Get(processInstance.CorrelationId); } @@ -98,13 +114,11 @@ public MessageHandlerInvokeResult Invoke(IPipelineEvent pipelineEvent) messageType.FullName)); } - var handlerContext = Activator.CreateInstance(contextType, _transportMessageFactory, _pipelineFactory, - _subscriptionManager, transportMessage, message, state.GetCancellationToken(), stream); + var handlerContext = Activator.CreateInstance(contextType, stream, _messageSender, transportMessage, message, state.GetCancellationToken()); method.Invoke(processInstance, new[] {handlerContext}); - using (_databaseContextFactory.Create(_processConfiguration.ProviderName, - _processConfiguration.ConnectionString)) + using (_databaseContextFactory.Create(_providerName, _connectionString)) { _eventStore.Save(stream); } diff --git a/Shuttle.Esb.Process/ProcessSection.cs b/Shuttle.Esb.Process/ProcessSection.cs deleted file mode 100644 index c76d1e7..0000000 --- a/Shuttle.Esb.Process/ProcessSection.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Configuration; -using Shuttle.Core.Configuration; - -namespace Shuttle.Esb.Process -{ - public class ProcessSection : ConfigurationSection - { - [ConfigurationProperty("connectionStringName", IsRequired = false, DefaultValue = "Process")] - public string ConnectionStringName => (string) this["connectionStringName"]; - - public static ProcessConfiguration Configuration() - { - var section = ConfigurationSectionProvider.Open("shuttle", "process"); - var configuration = new ProcessConfiguration(); - - var connectionStringName = "Process"; - - if (section != null) - { - connectionStringName = section.ConnectionStringName; - } - - var settings = ConfigurationManager.ConnectionStrings[connectionStringName]; - - configuration.ConnectionString = settings.ConnectionString; - configuration.ProviderName = settings.ProviderName; - - return configuration; - } - } -} \ No newline at end of file diff --git a/Shuttle.Esb.Process/Properties/AssemblyInfo.cs b/Shuttle.Esb.Process/Properties/AssemblyInfo.cs index 21c8c8b..55eacad 100644 --- a/Shuttle.Esb.Process/Properties/AssemblyInfo.cs +++ b/Shuttle.Esb.Process/Properties/AssemblyInfo.cs @@ -1,22 +1,22 @@ using System.Reflection; using System.Runtime.InteropServices; -#if NET461 -[assembly: AssemblyTitle(".NET Framework 4.6.1")] +#if NETFRAMEWORK +[assembly: AssemblyTitle(".NET Framework")] #endif -#if NETCOREAPP2_1 -[assembly: AssemblyTitle(".NET Core 2.1")] +#if NETCOREAPP +[assembly: AssemblyTitle(".NET Core")] #endif -#if NETSTANDARD2_0 -[assembly: AssemblyTitle(".NET Standard 2.0")] +#if NETSTANDARD +[assembly: AssemblyTitle(".NET Standard")] #endif -[assembly: AssemblyVersion("12.0.2.0")] -[assembly: AssemblyCopyright("Copyright © Eben Roux 2019")] +[assembly: AssemblyVersion("13.0.0.0")] +[assembly: AssemblyCopyright("Copyright (c) 2022, Eben Roux")] [assembly: AssemblyProduct("Shuttle.Esb.Process")] -[assembly: AssemblyCompany("Shuttle")] +[assembly: AssemblyCompany("Eben Roux")] [assembly: AssemblyConfiguration("Release")] -[assembly: AssemblyInformationalVersion("12.0.2")] -[assembly: ComVisible(false)] +[assembly: AssemblyInformationalVersion("13.0.0")] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/Shuttle.Esb.Process/Resources.Designer.cs b/Shuttle.Esb.Process/Resources.Designer.cs index 2bcc4c2..44e8597 100644 --- a/Shuttle.Esb.Process/Resources.Designer.cs +++ b/Shuttle.Esb.Process/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Shuttle.Esb.Process { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { @@ -60,6 +60,24 @@ internal Resources() { } } + /// + /// Looks up a localized string similar to Could not load assembly from name '{0}'.. + /// + public static string AssemblyNameException { + get { + return ResourceManager.GetString("AssemblyNameException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Option 'ConnectionStringName' has not been specified.. + /// + public static string ConnectionStringNameException { + get { + return ResourceManager.GetString("ConnectionStringNameException", resourceCulture); + } + } + /// /// Looks up a localized string similar to Process type '{0}' cannot process message type '{1}' since the correlation id of '{2}' is not a valid guid.. /// diff --git a/Shuttle.Esb.Process/Resources.resx b/Shuttle.Esb.Process/Resources.resx index e787be8..b757d3e 100644 --- a/Shuttle.Esb.Process/Resources.resx +++ b/Shuttle.Esb.Process/Resources.resx @@ -119,26 +119,33 @@ Process type '{0}' cannot process message type '{1}' since the correlation id of '{2}' is not a valid guid. - {0} = full type name of the process type, {1} = full type name of the message, {2} = the correlation id of the transport message + 0 = full type name of the process type, 1 = full type name of the message, 2 = the correlation id of the transport message Process type with AssemblyQualifiedName '{0}' could not be instantiated. - {0} = assembly qualified name of process type + 0 = assembly qualified name of process type Message type '{0}' maps to more than one process type. You will need to register a resolver to determine which type of process to instantiate. - {0} = full type name of the message + 0 = full type name of the message Message type '{0}' does not map to any process type. You will need to register a resolver to determine which type of process to instantiate or add a mapping. - {0} = full type name of the message + 0 = full type name of the message Process assessor type with AssemblyQualifiedName '{0}' does not have a default constructor. - {0} = assembly qualified name of assessor type + 0 = assembly qualified name of assessor type Handler type '{0}' does not have the required ProcessMessage method that handles message type '{1}'. - {0} = handler full type name, {1} = message full type name + 0 = handler full type name, 1 = message full type name + + + Option 'ConnectionStringName' has not been specified. + + + Could not load assembly from name '{0}'. + 0 = assembly name that failed to load \ No newline at end of file diff --git a/Shuttle.Esb.Process/ServiceCollectionExtensions.cs b/Shuttle.Esb.Process/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..d8723a9 --- /dev/null +++ b/Shuttle.Esb.Process/ServiceCollectionExtensions.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Shuttle.Core.Contract; + +namespace Shuttle.Esb.Process +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddProcessManagement(this IServiceCollection services, Action builder = null) + { + Guard.AgainstNull(services, nameof(services)); + + var processManagementBuilder = new ProcessManagementBuilder(services); + + builder?.Invoke(processManagementBuilder); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton, ProcessManagementOptionsValidator>(); + + services.AddOptions().Configure(options => + { + options.ConnectionStringName = processManagementBuilder.Options.ConnectionStringName; + options.AssemblyNames = processManagementBuilder.Options.AssemblyNames; + }); + + return services; + } + } +} \ No newline at end of file diff --git a/Shuttle.Esb.Process/Shuttle.ESB.Process.csproj b/Shuttle.Esb.Process/Shuttle.ESB.Process.csproj index 2289fce..01f7073 100644 --- a/Shuttle.Esb.Process/Shuttle.ESB.Process.csproj +++ b/Shuttle.Esb.Process/Shuttle.ESB.Process.csproj @@ -6,16 +6,17 @@ - - - - + + + + + - - - + + +