I'm tryng to refactor a monstrous WCF service into something more manageable. At the time of writing, the service takes about 9 dependencies via constructor, which makes unit testing it very difficult.
The service is handling local state via state machine, does validation on the parameters, throws fault exceptions, performs the actual operation and fires publication events via a pub/sub channel. This code is very similar accross all other service calls.
I realize that I can do several of those things (argument validation, pub/sub notifications) differently, perhaps via Aspect-Oriented Programming or WCF behaviors, but my gut tells me that the general approach is wrong -- this feels too "procedural".
My goal is to separate the execution of the actual operation from things like pub/sub notifications, and perhaps even error handling.
I wonder if acronyms like DDD or CQRS or other techniques can help out here? I am, unfortunately, not very familiar with those concepts beyond the definition.
Here's a (simplified) example of one such WCF operation:
public void DoSomething(DoSomethingData data)
{
if (!_stateMachine.CanFire(MyEvents.StartProcessing))
{
throw new FaultException(...);
}
if (!ValidateArgument(data))
{
throw new FaultException(...);
}
var transitionResult =
_stateMachine.Fire(MyEvents.StartProcessing);
if (!transitionResult.Accepted)
{
throw new FaultException(...);
}
try
{
// does the actual something
DoSomethingInternal(data);
_publicationChannel.StatusUpdate(new Info
{
Status = transitionResult.NewState
});
}
catch (FaultException<MyError> faultException)
{
if (faultException.Detail.ErrorType ==
MyErrorTypes.EngineIsOffline)
{
TryFireEvent(MyServiceEvent.Error,
faultException.Detail);
}
throw;
}
}
What you've got there is a great example of a command in disguise. Nice about what you're doing here is that your service method already takes in a single argument DoSomethingData
. This is your command message.
What you're missing here is a general abstraction over command handlers:
public interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
With a little bit of refactoring, your service method would look like this:
// Vanilla dependency.
ICommandHandler<DoSomethingData> doSomethingHandler;
public void DoSomething(DoSomethingData data)
{
this.doSomethingHandler.Handle(data);
}
And of course you need an implementation for ICommandHandler<DoSomethingData>
. In your case it will look like this:
public class DoSomethingHandler : ICommandHandler<DoSomethingData>
{
public void Handle(DoSomethingData command)
{
// does the actual something
DoSomethingInternal(command);
}
}
Now you might be wondering, what about those cross-cutting concerns you implemented like argument validation, the can fire, publication channel status update and error handling. Well yeah, they're all cross-cutting concerns, both your WCF service class AND your business logic (the DoSomethingHandler
) should not be concerned about that.
There are several ways to apply Aspect-Oriented Programming. Some like to use code weaving tools like PostSharp. Downside of these tools is that they make unit testing a lot harder, since you weave all your cross-cutting concerns in.
The second way is by using interception. Using dynamic proxy generation and some reflection. There's however a variation of this that I like more, and that is by applying decorators. The nice thing about this is that this is in my experience the cleanest way of applying cross cutting concerns.
Let's take a look at a decorator for your validation:
public class WcfValidationCommandHandlerDecorator<T> : ICommandHandler<T>
{
private IValidator<T> validator;
private ICommandHandler<T> wrapped;
public ValidationCommandHandlerDecorator(IValidator<T> validator,
ICommandHandler<T> wrapped)
{
this.validator = validator;
this.wrapped = wrapped;
}
public void Handle(T command)
{
if (!this.validator.ValidateArgument(command))
{
throw new FaultException(...);
}
// Command is valid. Let's call the real handler.
this.wrapped.Handle(command);
}
}
Since this WcfValidationCommandHandlerDecorator<T>
is a generic type, we can wrap it around every command handler. For instance:
var handler = new WcfValidationCommandHandlerDecorator<DoSomethingData>(
new DoSomethingHandler(),
new DoSomethingValidator());
And you can as easily create a decorator that handles any thrown exceptions:
public class WcfExceptionHandlerCommandHandlerDecorator<T> : ICommandHandler<T>
{
private ICommandHandler<T> wrapped;
public ValidationCommandHandlerDecorator(ICommandHandler<T> wrapped)
{
this.wrapped = wrapped;
}
public void Handle(T command)
{
try
{
// does the actual something
this.wrapped.Handle(command);
_publicationChannel.StatusUpdate(new Info
{
Status = transitionResult.NewState
});
}
catch (FaultException<MyError> faultException)
{
if (faultException.Detail.ErrorType == MyErrorTypes.EngineIsOffline)
{
TryFireEvent(MyServiceEvent.Error, faultException.Detail);
}
throw;
}
}
}
Did you see how I just wrapped your code in this decorator? We can again use this decorator to wrap the original:
var handler =
new WcfValidationCommandHandlerDecorator<DoSomethingData>(
new WcfExceptionHandlerCommandHandlerDecorator<DoSomethingData>(
new DoSomethingHandler()),
new DoSomethingValidator());
Of course this all seems like an awful lot of code and if all you have is one single WCF service method than yes, this is probably overkill. But it starts to get really interesting if you have a dozen or so. If you have hundreds? Well.. I don't want to be the developer maintaining that code base if you're not using a technique like this.
So after a few minutes of refactoring you end up with WCF service classes that just depend on ICommandHandler<TCommand>
interfaces. All cross-cutting concerns will be placed in decorators and of course everything is wired together by your DI library. I think you know a few ;-)
When you done this, there is probably one thing you could improve, because all your WCF service classes will start to look boringly the same:
// Vanilla dependency.
ICommandHandler<FooData> handler;
public void Foo(FooData data)
{
this.handler.Handle(data);
}
It will get to start boring to write new commands and new handlers. You will still have your WCF service to maintain.
What you can do instead, is create a WCF service with a single class with a single method, like this:
[ServiceKnownType("GetKnownTypes")]
public class CommandService
{
[OperationContract]
public void Execute(object command)
{
Type commandHandlerType = typeof(ICommandHandler<>)
.MakeGenericType(command.GetType());
dynamic commandHandler = Bootstrapper.GetInstance(commandHandlerType);
commandHandler.Handle((dynamic)command);
}
public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
{
// create and return a list of all command types
// dynamically using reflection that this service
// must accept.
}
}
Now all you have is a WCF service with a single method that will never change. The ServiceKnownTypeAttribute
points to the GetKnownTypes
. WCF will call this method on startup to see what types it must accept. When you return the list based on the application metadata, it allows you to add and remove commands to the system, without having to change a single line in your WCF service.
You probably will add new WCF specific decorators once in a while and those should typically be placed in the WCF service. Other decorators would probably be more general and might be placed in the business layer itself. They might be reused by your MVC application for instance.
Your question was a bit about CQRS but my answer has nothing to do with it. Well... nothing is an overstatement. CQRS uses this pattern extensively, but CQRS goes a step further. CQRS is about collaborative domains which force you to queue commands and process them asynchronously. My answer on the other hand is just about applying the SOLID design principles. SOLID is good everywhere. Not only in collaborative domains.
If you want to read more about this, please read my article about applying command handlers. After that, go on and read my article about applying this principle to WCF services. My answer is a summary of those articles.
Good luck.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With