Posts
47
Comments
136
Trackbacks
0
Extending CLAP For Larger Applications

If you’ve been reading my recent posts you’ve probably noticed that I’ve been working a lot with the CLAP project lately. For anyone who is not familiar with that project, its name is an acronym that stands for Command Line Auto Parser. It lets you quickly and easily take an array of command-line arguments passed into a .NET application and map them to one or more target classes that expose methods. CLAP takes the strings representing the arguments and automatically parses them and passes them into the appropriate method. I like this library a lot because it has a very low-barrier to entry making it very easy to get up and running for simple applications. Unfortunately, things get more cumbersome if you want to work with a large or complex application. Let’s look at some examples:

Simplest Example – Static Methods Class

The simplest way to use CLAP is to create a class with static public methods that represent the commands you want to expose at the command line. Consider this example class:

public class Arithmetic
{
    [Verb]
    public static int Add(int value1, int value2)
    {
        return value1 + value2;
    }

    [Verb]
    public static int Substract(int value1, int value2)
    {
        return value1 - value2;
    }
}

Each public method we want CLAP to be able to use is decorated with a ‘Verb’ attribute. In order to invoke these methods from a command line application, all you need to do is create a a Console app and setup your Main method like this:

class Program
{
    static void Main(string[] args)
    {
        Parser.Run<Arithmetic>(args);
    }
}

In order to invoke the ‘Add’ command, you would run:

MyApp.exe add /value1:67 /value2:45

This approach is great for quick spikes or very simple command line utilities, but what if you want to split your commands into different classes or want to use dependency injection to inject external dependencies for use in your [Verb] decorated methods?

Using Multiple Classes

CLAP has support for splitting commands into multiple classes, and you can continue to use the same general approach. To build on the example from above, let’s now define another class to handle trigonometric functions:

public class Trigonometry
{
    [Verb]
    public static double Sine(double angle)
    {
        return Math.Sin(angle);
    }

    [Verb]
    public static double Cosine(double angle)
    {
        return Math.Cos(angle);
    }

    [Verb]
    public static double Tangent(double angle)
    {
        return Math.Tan(angle);
    }
}

In order to support both our basic arithmetic and more advanced trigonometry, all we need to do is tweak our Main method a bit:

class Program
{
    static void Main(string[] args)
    {
        Parser.Run<Arithmetic, Trigonometry>(args);
    }
}

The usage now gets a bit more complicated because there are multiple classes that might be able to handle any given command. We need to give CLAP a hint regarding what class to use:

MyApp.exe arithmetic.add /value1:67 /value2:45

The code here is still very simple, and the usage from the command line is still pretty intuitive. We can add more classes into the mix here as needed, but at some point this basic approach will begin to break down. Adding new generic type parameters for every new class we want to support will start to make for some pretty funny looking code eventually. Also, what happens if a static method won’t cut it anymore? What if we need to use instance methods instead?

Using Instance Methods

So far the example we’ve been using represents a simple utility application that we were building “from scratch”. When I first started using CLAP I wasn’t interested in using to build a new application or utility, but rather to create a new command-line interface for an existing application. The existing application is quite mature and already has a user interface, but is lacking in the area of the administrative tooling. There are a classification of commands that application can support that don’t require a full-blown user interface either due to the low frequency with which they need to be invoked or because they represent advanced features that only administrative users would perform. In my situation, using CLAP to build a command-line interface meant creating a harness from which an existing set of classes could be exercised from the command line. There are an ever-growing number of commands that I want to able to support from the command line interface, which means that cramming them all into a handful of classes as static methods isn’t really going to scale well. What I really want to be able to do is define many small, narrowly-focused classes that each contain a handful of closely-related [Verb] methods. Additionally, I will often want to inject one or more dependencies on existing application classes into these classes at run-time to avoid having to construct them manually or re-invent functionality that already exists elsewhere in the application.

In its current form, CLAP does support the requirements I’ve outlined above, but in a sub-optimal way. Let’s say I want to build a command line interface for an existing application that will allow administrative users to both reset passwords and delete customer data for multiple users or customers at once. Resetting a user’s password and deleting a customer are two very different things, so I’d likely want to create two different classes. Let’s say that these class signatures might look like this:

public class CustomerCleanup
{
    private readonly ICustomerDao _customerDao;

    public CustomerCleanup(ICustomerdao customerDao)
    {
        _customerDao = customerDao;
    }

    [Verb]
    public void DeleteCustomers(int[] customerIds)
    {
        /* method body */
    }
}

public class BulkUserManager
{
    private readonly IPasswordService _passwordService;

    public BulkUserManager(IPasswordService passwordService)
    {
        _passwordService = passwordService;
    }

    public void ResetPassword(int[] userIds)
    {
        /* method body */
    }
}

Note how each of these classes has an external dependency. Resetting passwords and deleting customers is already a solved problem in this application; we just want to give administrative users the ability to perform these operations on more than one user or customer at a time if needed. Because these classes have external dependencies I want to be able to inject them via the constructor and keep references to them as internal fields which means that I can’t simply create static methods and use CLAP like I did in the previous examples. In order to use CLAP with these instance methods, I’ll just need to pass instances of each class in like this:

class Program
{
    static void Main(string[] args)
    {
        Parser.Run(args, Resolve<BulkUserManager>(), Resolve<CustomerCleanup>());
    }
}

This works OK, but is inefficient because it requires that we pass in an instance of each potential target type. The ‘Resolve’ method in this example is responsible for building up an instance of  the specified type (like a call into an Inversion of Control container might do). So each time we run a command, we’re required to build up a full instance of each class that might need to be used to run that command. These classes have dependencies, and those dependencies might also have dependencies. In the end, each call to ‘Run’ is only going to end up using one of the classes that we created, and the rest of them were instantiated (along with all of their dependencies) for no reason. After awhile, you could easily be building up a dozen or more classes that you’re never going to use.

Deferring Construction

What I really want to be able to do is to tell CLAP about all of the classes that might handle the command but without actually having to instantiate each one. I want to defer the instantiation of the class until I know that it’s the one that I’m going to need to run the command. To support this, I’m proposing the use of something called the Target Resolver.

The Target Resolver will allow “registration” of classes that might need to be used to run a command. CLAP is already relying heavily on reflection to do its work, so it makes sense for the Target Resolver register Type instances representing the possible “target” classes for each commands. In addition to each Type, the Target Resolver also needs to be able to create the type on-the-fly if and when CLAP determines that it is the Type that will be needed to satisfy the command that was passed in. This is easily accomplished by providing a callback to construct each type. The example from above could be re-written like this using the Target Resolver:

class Program
{
    static void Main(string[] args)
    {
        var targetResolver = new TargetResolver();
        targetResolver.RegisterTargetType<BulkUserManager>(() => Resolve<BulkUserManager>());
        targetResolver.RegisterTargetType<CustomerCleanup>(() => Resolve<CustomerCleanup>());
        Parser.Run(args, targetResolver);
    }
}

Now we’re using generic methods on the TargetResolver class to ‘register’ each class that can handle commands, and providing a Func<T> that will construct and return each one if and when it’s needed. We then just pass that target resolver into the CLAP Parser and it takes care of inspecting each registered type and building it when it’s needed.

I think it will be fairly easy to create “adapter” packages that could easily port an Inversion of Control container into a TargetResolver instance (e.g. CLAP.Ninject, CLAP.Autofac, CLAP.TinyIoC, etc.)  for easily wiring up command-handling classes in an application. This would make it easy to add new classes as you expand the functionality of the command-line interface while being able to leverage any existing investment in an IoC container.

You can see the TargetResolver code on my fork of the CLAP project on github. I’ll be submitting this as a pull request to the main CLAP project where I’m hoping it will be accepted into a new version of the library.

posted on Wednesday, August 28, 2013 9:37 PM Print
Comments
No comments posted yet.

Post Comment

Title *
Name *
Email
Comment *  
 
Meta
Tag Cloud