Code Monkey home page Code Monkey logo

jamesrandall / azurefromthetrenches.commanding Goto Github PK

View Code? Open in Web Editor NEW
45.0 6.0 10.0 3.44 MB

A configuration based commanding and mediator framework that supports command dispatch and execution in-process, over HTTP or over Azure Storage Queues. Written to .NET Standard 2.0 and supports many popular runtimes including .NET Core and .NET 4.6.x.

Home Page: http://www.azurefromthetrenches.com

License: MIT License

C# 65.52% PowerShell 0.43% HTML 0.22% JavaScript 17.78% Liquid 0.91% CSS 15.13%
dispatch dispatch-commands asp mediator command netstandard20

azurefromthetrenches.commanding's Introduction

AzureFromTheTrenches.Commanding

Build Status

Note that full documentation including guides and an API reference can be found on the help site.

I also have a series on moving from "onion layer" architecture to making use of a mediated command pattern in a series of posts on my blog.

Introduction

AzureFromTheTrenches.Commanding is a configuration based asynchronous command mediator framework with a number of key design goals:

  • To provide a highly performant mediator for simple usage
  • To support evolution across a projects lifecycle allowing for easy decomposition from a modular-monolith to a fully distributed micro-service architecture
  • To provide a none-leaking abstraction over command dispatch and execution semantics
  • To reduce boilerplate code - simplistically less code means less errors and less to maintain

To support these goals the framework supports .NET Standard 2.0 (and higher) and so can be used in a wide variety of scenarios and a number of fully optional extension packages are available to enable:

  • Building a REST API directly from commands using a configuration based approach
  • Dispatching commands to queues (Service Bus Queues and Topics, and Azure Storage)
  • Dispatching commands to event hubs
  • Using queues as a source for executing commands
  • Caching commands based on signatures in local memory caches or Redis caches

You don't need to take advantage of that functionality but you can, if you want, adopt it over time without changing your core code.

Getting Started

Firstly install the nuget package for commanding:

Install-Package AzureFromTheTrenches.Commanding

As an example let's create a command that adds two numbers together and returns a result:

public class MathResult
{
    public int Value { get; set; }
}

public class AddCommand : ICommand<MathResult>
{
    public int FirstNumber { get; set; }

    public int SecondNumber { get; set; }
}

Commands are acted on by handlers and our add handler looks like this:

public AddCommandHandler : ICommandHandler<AddCommand, MathResult>
{
    public Task<MathResult> ExecuteAsync(AddCommand command, MathResult previousResult)
    {
        return new MathResult {
            Value = command.FirstNumber + command.SecondNumber
        };
    }
}

Having defined our command, result and handler, we need to register these with the commanding system. If you're just writing a console app you can do this in Program.cs but for more realistic usage you'd do this where you configure your IoC container - it's handy to think of command registrations as just another part of your applications configuration, besides which you'll need access to the container. The example below demonstrates registration with the Microsoft ASP.Net Core Service Provider:

// First register the commanding framework with the IoC container
IServiceProvider serviceProvider = null;
IServiceCollection serviceCollection = new ServiceCollection();
CommandingDependencyResolverAdapter adapter = new CommandingDependencyResolverAdapter(
    (fromType, toInstance) => services.AddSingleton(fromType, toInstance),
    (fromType, toType) => services.AddTransient(fromType, toType),
    (resolveTo) => _serviceProvider.GetService(resolveTo));
ICommandRegistry registry = adapter.AddCommanding();
serviceProvider = serviceCollection.BuildServiceProvider();

// Now register our handler
registry.Register<AddCommandHandler>();

The CommandingDependencyResolverAdapter class is an adapter that allows the framework to be registed with any IoC container and the AddCommanding method registers the injectable commaning interfaces and returns an ICommandRegistry interface that allows you to register handlers - you only need to register a handler, the framework will figure out the rest and registration uses a fluent API style for concise readable code.

To dispatch our command we need to get hold of the ICommandDispatcher interface and send the command. We'll do that and output the result to the console:

ICommandDispatcher commandDispatcher = dependencyResolver.ServiceProvider.GetService<ICommandDispatcher>();
MathResult mathResult = await commandDispatcher.DispatchAsync(new AddCommand { FirstNumber = 5, SecondNumber = 6});
Console.WriteLine(mathResult.Value); // hopefully says 11

And for simple usage that's it. This example is a bit contrived as we're resolving dependencies by hand and theres a lot of boilerplate to add two numbers together but in real world scenarios all you really need to do is register your command handlers in the appropriate place, for example if you're using ASP.Net Core then all the dependency injection boilerplate is in place.

Samples

  1. Simple in memory command execution https://github.com/JamesRandall/AzureFromTheTrenches.Commanding/tree/master/Samples/InMemoryCommanding

  2. Dispatching commands over HTTP https://github.com/JamesRandall/AzureFromTheTrenches.Commanding/tree/master/Samples/HttpCommanding.Client

  3. Executing commands in response to HTTP requests (ASP.Net Core) https://github.com/JamesRandall/AzureFromTheTrenches.Commanding/tree/master/Samples/HttpCommanding.Web

  4. Dispatching to and executing from Azure Storage queues https://github.com/JamesRandall/AzureFromTheTrenches.Commanding/tree/master/Samples/AzureStorageQueueCommanding

  5. Azure storage auditing https://github.com/JamesRandall/AzureFromTheTrenches.Commanding/tree/master/Samples/AzureStorageAuditing

  6. Azure event hub auditing https://github.com/JamesRandall/AzureFromTheTrenches.Commanding/tree/master/Samples/AzureEventHubAuditing

Advanced Usage

Further usage scenarios can be found in the wiki including:

Support

If you get stuck log a GitHub issue or catch me over on Twitter at @azuretrenches and I'll help if I can.

azurefromthetrenches.commanding's People

Contributors

cjrpriest avatar jamesrandall avatar yodasmydad avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

azurefromthetrenches.commanding's Issues

possible bug in registering handlers

Please see this gist for a repo - https://gist.github.com/cjrpriest/b536bb69630fd8ff60b1e0089b5abae1

I get this exception thown on line 37:

System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
   at System.ThrowHelper.ThrowKeyNotFoundException()
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at AzureFromTheTrenches.Commanding.Implementation.CommandExecuter.<ExecuteCommandWithHandlers>d__10`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at AzureFromTheTrenches.Commanding.Implementation.CommandExecuter.<ExecuteAsync>d__9`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at AzureFromTheTrenches.Commanding.Implementation.CommandExecuter.<ExecuteAsync>d__9`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at AzureFromTheTrenches.Commanding.Implementation.CommandDispatcher.<DispatchAsync>d__6`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at AzureFromTheTrenches.Commanding.Implementation.CommandDispatcher.<DispatchAsync>d__5.MoveNext()

Thanks :)

Error executing commandDispatcher.DispatchAsync()

Hi,

Gett error when I try to execute method:
await _commandDispatcher.DispatchAsync(getStandardOrganizationQuery);

Error:
System.Private.CoreLib: Exception while executing function: LoadWorkflows. AzureFromTheTrenches.Commanding: Error occurred during command execution. AzureFromTheTrenches.Commanding: A handler of type ImportAsAService.Application.StandardOrganizations.Queries.GetStandardOrganization.GetStandardOrganizationQueryHandler is registered but resolution returned null. Please check IoC configuration.

I have implemented Azure function v3 in NET Core 3.1.

My startup.cs file:

[assembly: FunctionsStartup(typeof(Startup))]

namespace ImportAsAService.Service
{
    internal class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var serviceProvider = builder.Services.BuildServiceProvider();
            var adapter = new CommandingDependencyResolverAdapter(
                (fromType, toInstance) => builder.Services.AddSingleton(fromType, toInstance),
                (fromType, toType) => builder.Services.AddTransient(fromType, toType),
                (resolveTo) => serviceProvider.GetService(resolveTo));
            var registry = adapter.AddCommanding();

            registry.Register<GetStandardOrganizationQueryHandler>();

            ... other service registration
        }
    }
}

I don't understand what I'm missing here. Please help me.

Thank you,
Indulis

Request: Do no discover abstract CommandHandler classes

Could you please add the condition !x.IsAbstract() in CommandRegistry:

    public ICommandRegistry Discover(params Assembly[] assemblies)
        {
            Type commandHandlerBase = typeof(ICommandHandlerBase);
            foreach (Assembly assembly in assemblies)
            {
                Type[] handlers = assembly.GetTypes().Where(x => !x.IsAbstract() && commandHandlerBase.IsAssignableFrom(x)).ToArray();
                foreach (Type handler in handlers)
                {
                    Register(handler);
                }
            }
            return this;
        }

I'm using some abstract CommandHandler base classes with unspecified generic parameters that will crash FunctionMonkey when registered to the CommandRegistry.

Request: Make CommandRegistry.RegisterHandler public

More of a request than an issue.

There is some code I'm experimenting with in FunctionMonkey, but the roadblock Im running into is in: AzureFromTheTrenches.Commanding.Implementation.CommandRegistry
The RegisterHandler method is private and the Register methods that calls it assume .Single in some places that I want multiple (one class handling a generic cookie cutter abstraction of different handlers for REST). The easiest solution (and I have tested with reflection) would be to expose the RegisterHandler method publically in the interface. Thoughts?

Awesome library BTW. Im having more fun with FunctionMonkey(and by proxy this commanding lib) than I have in a long time.

How to use this with multiple projects using the HostingStartup attribute?

Have a look here for info on the new HostingStartup attribute

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/platform-specific-configuration?view=aspnetcore-2.2

In short, I have several projects within a solution that all use this as (to me) it's a great way to add startup code to separate projects. I have a X.Core project where I'd like to setup the framework like below.

        // Add the Commanding Framework
        var adapter = new CommandingDependencyResolverAdapter(
            (fromType, toInstance) => services.AddSingleton(fromType, toInstance),
            (fromType, toType) => services.AddTransient(fromType, toType),
            resolveTo => _serviceProvider.GetService(resolveTo));
        var registry = adapter.AddCommanding();

Then need a way of getting the ICommandRegistry via the IWebHostBuilder builder I guess, so I can register query handlers from the other projects.

GettingStartedSample compilation error

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
1> at FunctionMonkey.Compiler.HandlebarsHelpers.HttpVerbsHelper.HelperFunction(TextWriter writer, Object context, Boolean toLowerCase, Object[] parameters) in C:\wip\myOpenSource\functionMonkey\Source\FunctionMonkey.Compiler\HandlebarsHelpers\HttpVerbsHelper.cs:line 28
1> at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid5[T0,T1,T2,T3,T4](CallSite site, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
1> at FunctionMonkey.Compiler.HandlebarsHelpers.HttpVerbsHelper.<>c.b__0_1(TextWriter writer, Object context, Object[] parameters) in C:\wip\myOpenSource\functionMonkey\Source\FunctionMonkey.Compiler\HandlebarsHelpers\HttpVerbsHelper.cs:line 15
1> at lambda_method(Closure , TextWriter , Object )
1> at HandlebarsDotNet.Handlebars.HandlebarsEnvironment.<>c__DisplayClass7_0.b__0(Object context)
1> at FunctionMonkey.Compiler.Implementation.JsonCompiler.Compile(IReadOnlyCollection`1 functionDefinitions, OpenApiOutputModel openApiOutputModel, String outputBinaryFolder, String outputNamespaceName) in C:\wip\myOpenSource\functionMonkey\Source\FunctionMonkey.Compiler\Implementation\JsonCompiler.cs:line 34
1> at FunctionMonkey.Compiler.Implementation.FunctionCompiler.Compile() in C:\wip\myOpenSource\functionMonkey\Source\FunctionMonkey.Compiler\Implementation\FunctionCompiler.cs:line 71
1> at FunctionMonkey.Compiler.Program.Main(String[] args) in C:\wip\myOpenSource\functionMonkey\Source\FunctionMonkey.Compiler\Program.cs:line 44

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.