8

Paramore Brighter: DRY with Custom Decorated Command Handlers

 4 years ago
source link: https://colinmackay.scot/2018/03/18/paramore-brighter-dry-with-custom-decorated-command-handlers/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

DRY with Custom Decorated Command Handlers – The Blog of Colin MackaySkip to content

You may wish to add similar functionality to many (or all) command handlers. The typical example is logging. You can decorate a command handler in a similar way to the policies I showed in previous posts to add common functionality. I’ve used this technique to guard the handler from invalid command arguments/parameters (essentially a validator), and for ensuring that we ping our APM (Application Performance Management) tool when a command completes. I’ll use the latter to demonstrate creating a custom decorator and handler to initiate this common code.

Paramore Brighter Command Processor will look for any attributes derived from RequestHandlerAttribute that are added to the Handle method on your command handler class. It will then use them to build a pipeline for your command.

So, in the example here, our attribute class looks like this:

public class HeartbeatAttribute : RequestHandlerAttribute
{
    public HeartbeatAttribute(int step, HandlerTiming timing = HandlerTiming.After) : base(step, timing)
    {
    }

    public override Type GetHandlerType()
    {
        return typeof(HeartbeatHandler<>);
    }
}

We are deriving from RequestHandlerAttribute, and it has an abstract method that you need to implement. GetHandlerType() returns the type of handler that needs to be instantiated to handle the common task.

The RequestHandlerAttribute class also takes two arguments for its constructor that you either need to capture from users of your attribute or supply yourself. It takes a step and a timing parameter. Since we’ve already talked about step in a previous post we’ll move on to talking about timing.

The two options for timing are Before and After. In the previous examples the timing has been implicitly set to Before because the handler needed perform actions before your target handler (the one that you decorated). If you set the timing to After it only actions after your target handler.

In the example here, the timing is set After because we want to make sure that the the handler completed correctly before our handler runs. So, if it throws an exception then our heartbeat handler won’t run. If you need to perform an action before and after, then set the timing to Before, and perform actions before the call to base.Handle() and after the call.

Our heartbeat handler looks like this:

public class HeartbeatHandler<TRequest> : RequestHandler<TRequest> where TRequest : class, IRequest
{
    public override TRequest Handle(TRequest command)
    {
        // We would probably call a heartbeat service at this point.
        // But for demonstration we'll just write to the console.

        Console.WriteLine($"Heartbeat pulsed for {command.GetType().FullName}");
        string jsonString = JsonConvert.SerializeObject(command);
        Console.WriteLine(jsonString);

        return base.Handle(command);
    }
}

The important thing, as will all handlers, is to remember the call to the base.Handle() which ensures the pipeline is continued.

The target handler decoration looks like this:

[FallbackPolicy(step:1, backstop:true, circuitBreaker:false)]
[UsePolicy(policy: "GreetingRetryPolicy", step:2)]
[Heartbeat(step:3)]
public override SalutationCommand Handle(SalutationCommand command)
{
    // Stuff to handle the command.

    return base.Handle(command);
}

The first two decorators are from previous posts (Retrying Commands and Implementing a fallback exception handler) while the third is our new decorator.

When run, you can see that if the service fails completely (i.e. all the retries failed) then the Heartbeat does not get run. However, if the command succeeds then the heartbeat handler is run. Our APM knows the command succeeded and can display that.

Remember

Remember to wire up the handler, as with all handlers, to your dependency injection framework, so that it can be correctly instantiated:

serviceCollection.AddScoped(typeof(HeartbeatHandler<>));

Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK