We're already getting a lot of use out of the framework, but I'm constantly seeking out ways to make it easier to use and extend.
There is one particular aspect of the framework code is leaving a bad taste in my mouth. After trying a few approaches I've settled on one that I feel is the best option. Not everyone agrees, and I'd appreciate some alternative approaches.
It has to do with sharing state in a batch of commands. Consider the following powershell script:
new-deposit -Name Stan -Amount 500;
which, after some magical binding and command-building logic, breaks down into a complex sequence of simple commands (composites and chains are exploded as sub-items):
- FindOrCreateUserAccount for identifier "Stan" (chain):
- LoadUserAccount for user named "Stan"
- CreateUserAccount for user named "Stan" (composite):
- AcquireAuthTicket for creating a user account
- CreateUserAccountWithTicket for user named "Stan"
- MakeDeposit in the amount of $500 to Stan's account (composite):
- AcquireAuthTicket for making a deposit
- MakeDepositWithTicket to move $500 into Stan's account
The powershell function translates into a batch of eight command objects to perform the actual work. The commands need to share some state to accomplish the overall goal - for instance, the FindOrCreateUserAccount command will need to produce a UserAccount object on which the MakeDeposit command can operate. This is a bit of a conundrum - I want each command object to know only of its own duties, so the FindOrCreateUserAccount command isn't able to directly pass the UserAccount object to the MakeDeposit command. So how do I get the UserAccount object created by the FindOrCreateUserAccount command to the MakeDeposit command?
I've tried a few approaches.
Using a Command Context
After completing my first end-to-end use of the framework, I jotted down some concerns, many of which orbit around the need to consolidate access to all of the system services I'm automating. To address this, I changed the ICommand.Execute() method signature to accept a single parameter of type ICommandContext:
public interface ICommandContext
{
IUserService UserService { get; }
IGameService GameService { get; }
// ...
}
public interface ICommand
{
bool Execute( ICommandContext context );
}
So now anyone executing a command must supply a command context. I did this for a few reasons:
- it allows each command easy access to the various services that comprise the production system without a lot of plumbing code;
- it gives me a single point of extension for all command types. E.g., if the system expands to include another service, I can modify ICommandContext without breaking any of the other commands or configuration;
- it provides an abstraction against which the command objects run. For example, I can execute commands against a "test" context to verify their behavior, or a "record" context to build up transcripts of system activity;
- it isolates configuration to a single object, so instead of having to manage a large configuration across dozens of command objects, I only need to focus on configuring one object. Bonus.
Anyway, a collegue suggested I just add a table of named objects to the command context, something like this:
public interface ICommandContext
{
IUserService UserService { get; }
IGameService GameService { get; }
Dictionary< string, object > Data { get; }
// ...
}
The idea is that one command could load an object with a specific name:
public class LoadUserAccountCommand : ICommand
{
public string UserName { get; set; }
public bool Execute( ICommandContext context )
{
UserAccount account = null;
// this call populates the user account object
context.UserService.GetUserAccount( UserName, account );
context.Data[ "UserAccount" ] = account;
return true;
}
}
and another could consume it using the same name:
public class MakeDepositWithTicketCommand : ICommand
{
public decimal Amount { get; set; }
public bool Execute( ICommandContext context )
{
UserAccount account = context.Data[ "UserAccount" ] = account;
// make the deposit into the account ..
context.UserService.Deposit( account.Id, Amount );
return true;
}
}
I tried this for a little while, it has some charm in its simplicity but I'll be blunt: I hate it. I think it's fine and simple for a hack job but will become unmaintainable very quickly:
- the hashtable hides the inputs of the command - e.g., there is no way
to look at a command object and determine that it needs in the way of
input to do its job without deciphering code;
- the sheer number of entries required during a command session could become quite long, and even assuming we use best practices and have an enum of magic Data keys it becomes difficult to use;
- along those lines, as the number of entires grows, the names start to loose their simplicity. "UserAccount" is no longer sufficient, so you have the "NewlyCreatedUserAccount" item, the "CachedUserAccount" item, etc. Or worse, the team gets lazy and we have "UserAccount", "UserAccount2", etc;
- this actually creates a high (and, ironically, hidden) level of coupling between commands - e.g., the MakeDepositWithTicketCommand can only work against the "UserAccount" data item, which will limit the scope of commands with which it can operate to those that know to fill the "UserAccount" data item.
I'm convinced that a general purpose variable stack or hashtable will make the framework too cumbersome to use. I came up with an alternative that feels better, but still has some ugly parts.
Using Shared Property References
The easiest way to explain this is by example. In this rewrite of the sample from the previous section, note how both command objects expose an Account property:
public class LoadUserAccountCommand : ICommand
{
public UserAccount Account { get; set; }
public string UserName { get; set; }
public bool Execute( ICommandContext context )
{
// this call populates the user account object
context.UserService.GetUserAccount( UserName, Account );
return true;
}
}
public class MakeDepositWithTicketCommand : ICommand
{
public UserAccount Account { get; set; }
public decimal Amount { get; set; }
public bool Execute( ICommandContext context )
{
// make the deposit into the account ..
context.UserService.Deposit( Account.Id, Amount );
return true;
}
}
If both Account properties are set to the same object reference, the commands implicitly share the Account state:
//...
UserAccount account = new UserAccount();
CompositeCommand cmd = new CompositeCommand(
new LoadUserAccountCommand { UserName = userName, Account = account },
new MakeDepositWithTicketCommand { Amount = depositAmount, Account = account }
);
bool result = cmd.Execute( context );
// ...
The LoadUserAccountCommand fills the account data into the object, and the MakeDepositWithTicketCommand uses the object to deposit money. I like this a lot better than the other solution:
- the needs of each command are expressed in its public members;
- each command operates in isolation and there is no hidden coupling - e.g., there is no assumption made by the MakeDepositWithTicketCommand that will prevent it from working with other command objects;
- it's simple;
- it feels right;
After using this for a while, I've found a few drawbacks. First, it adds some extra setup to every command batch. Second, this mechanism obviously doesn't work for value types, you have to wrap the value in a reference type which can feel a bit awkward. I've also been told that the state-sharing mechanism isn't obvious, but I don't agree.
I'd appreciate some feedback on my choices here - is there another approach I haven't considered?