Lately, I can’t seem to shake the pipeline pattern. It keeps popping up in all my applications. It’s like when I think about buying a new car and then I start seeing them everywhere on road and think – have there always been that many out there?
So, here it is in action. Below is a controller from an ASP.NET MVC application. The method accepts an order in the form of xml for provisioning products in our system.
public ActionResult EnqueueOrders(string orderXml) { var context = new OrderContext(orderXml); var pipeline = new FilterPipeline<OrderContext>(); pipeline.Add(new RewriteLegacyUsernameFeature(productFinder)); pipeline.Add(new ValidateOrderXml()); pipeline.Add(new ValidateReferenceCode(customerFinder)); pipeline.Add(new IgnoreOnException(new ValidateRemoteAccountUsernames(packageRepository))); pipeline.Add(new IgnoreOnException(new ValidateRemoteAccountUniqueEmail(packageRepository))); pipeline.Add(new EnqueueOrders(customerFacade)).When(new OrderHasNoErrors()); pipeline.Execute(context); var xml = BuildResponseFromOrderContext(context); return new XmlResult(xml); }
Most of it is just validation logic, but there are a few meatier pieces. Take a look a the first filter – RewriteLegacyUsernameFeature. If you’ve ever published an API that accepts xml, then you’ve probably wanted to change your schema 5 minutes later. The pipeline is great way to deal with transforming legacy xml on the way in.
Next is the implementation of the FilterPipeline which is essentially a chain of responsibility with a few convenience features.
public class FilterPipeline<T> : IFilter<T> { private IFilterLink<T> head; public void Execute(T input) { head.Execute(input); } public IFilterConfiguration<T> Add(IFilterLink<T> link) { var specificationLink = new SpecificationFilterLink<T>(link); AppendToChain(specificationLink); return specificationLink; } public IFilterConfiguration<T> Add(IFilter<T> filter) { return Add(new FilterLinkAdapter<T>(filter)); } public void AppendToChain(IFilterLink<T> link) { if (head == null) { head = link; return; } var successor = head; while (successor.Successor != null) { successor = successor.Successor; } successor.Successor = link; } }
You might be scratching your head and wondering what this business is with both IFilter<T> and IFilterLink<T>. IFilter<T> is just a simpler version of IFilterLink<T> that doesn’t require the implementer to deal with calling the next link in the chain. The subject will always pass through, no short-circuiting, hence the pipeline.
My favorite part is the SpecificationFilterLink<T> which is a decorator that uses a Specification to decide if the filter should be invoked. So you can do little readable snippets like:
pipeline.Add(new EnqueueOrders(customerFacade)).When(new OrderHasNoErrors());
Maybe this post will get it out of my system and I can move on to other solutions. It’s just so handy…
No comments:
Post a Comment