I'm doing some R&D work, and as such am exploring design patterns. I have recently been reading up on the Specification pattern and was referred to this great article.
I was intrigued by the simplicity and cleanliness of the code, but i started to draw some comparisons to implementing the same cleanliness using other techniques.
Consider the following interface contract for a service layer:
public interface IFooDataService
{
ICollection<Foo> GetFoosBySpecification(Specification<Foo> specification);
ICollection<Foo> GetFooByPredicate(Func<Foo,bool> predicate);
ICollection<Foo> GetFooBySearchArgs(FooSearchArgs searchArgs);
}
So, some initial points:
- All three return a collection of Foo objects
- All three take one single argument
- Specification method restricts access to specific requirements
- Predicate method has basically no restriction
- Search args method restricts access to specific requirements
Now, onto the implementation:
public ICollection<Foo> GetFoosBySpecification(Specification<Foo> specification)
{
return fooDataRepository
.Find()
.Where(f => specification.IsSatisfiedBy(f))
.ToList();
}
public ICollection<Foo> GetFooByPredicate(Func<Foo, bool> predicate)
{
return fooDataRepository
.Find()
.Where(predicate)
.ToList();
}
public ICollection<Foo> GetFooBySearchArgs(FooSearchArgs searchArgs)
{
return fooDataRepository
.Find()
.WhereMeetsSearchCriteria(searchArgs)
.ToList();
}
Points on the implementation:
- All three are extremely simple in implementation (one line of chained code)
- Specification and Search Args filtered implemented externally.
- Search args method simply uses IEnumerable extension method to inspect args
So, that being said, under what conditions would you use one of above 3 techniques?
My thoughts on Specification Pattern:
- Nice in that it isolates business/domain requirements into reusable components
- Extremely easy to read, makes code speak english
- Fair bit of code involved (interfaces, abstract classes). If i were to use this, i would put the abstractions in a common assembly (so i dont have a bunch of static files in my solution).
- Easy to change requirements by only changing specification, and not service layer.
- Supreme testability of domain logic (specifications)
My thoughts on Extension Methods (Pipes & Filters):
- 'Weighty' in logic, but still result in the same simplicity.
- Isolate query logic from service layer to static methods
- Still requires "reflection" of sort (inspecting supplied search args and building up query)
- Allows you to code architecture (repository, service layer) first, without thinking about specific business requirements (which is handy in certain scenarios)
My thoughts on Predicate Method:
- Could be used where you need coarse grained control over the queries.
- Good for small projects, where specifications may be overdoing it
My final thought logic is that if you are working on a complex business application where business requirements are known up front but may change over time, then i would use Specification pattern.
But for a application that is a "startup", ie requirements will evolve over time and has a multitude of ways to retrieve data without complex validation, i would use the Pipes and Filters methods.
What are your thoughts? Have any of you run into problems with any of the above methods? Any recommendations?
About to start a new project so these types of considerations are critical.
Thanks for the help.
EDIT for Clarification on Specification pattern
Here is same usage of the Specification pattern.
Specification<Foo> someSpec; // Specification is an abstract class, implementing ISpecification<TEntity> members (And, Or, Not, IsSatisfiedBy).
someSpec = new AllFoosMustHaveABarSpecification(); // Simple class which inherits from Specification<Foo> class, overriding abstract method "IsSatisfiedBy" - which provides the actual business logic.
ICollection<Foo> foos = fooDataService.GetFoosBySpecification(someSpec);
Specification<Foo>
is different fromFunc<Foo, bool>
. – Wince.GetFoosByPredicate(f => f.Bar = "FooBar")
or.GetFoosBySpecification(new Specification<Foo>(f => f.Bar = "FooBar"))
? I don't see what that buys you. Does one way enable any more expressiveness than the other? Is one easier to use? Faster? Provide better abstraction? – Wince