Tuesday 13 March 2007

Fluent Specification Part 4: Prepacked Questions

I went to my client and demo'd the Bouncer application to him and he was happy so we rolled it out immediately but he did say that the direction of his business had changed recently and we needed to make a few changes.

A week later we met up and he excitedly told me all about how his entertainment service was doing really well but he was not getting the most potential out of his business as he was only open at weekends. What he wanted to do was be open 7 nights a week with different events each night. The problem is that on different events the rules around who to let in will change and my Bouncer isn't flexible enough to handle that at the moment. He really needs me to update the app as soon as to start ramping up his business.

So what we need to do is allow an event class to supply the questions which the bouncer will ask the clubbers. So we need some code like this:
Event tonightsEvent = new Event(new QuestionToAsk(IsClubber.YoungEnough(16), "under sixteens only mate")
.AndNot(IsClubber.WearingFancyDress(), "fancy dress only tonight");
foreach(Clubber clubber in queue.Clubbers)
{
Answer answer = clubber.Ask(tonightsEvent.EntryQuestions);
if(answer.IsFalse) ...
}

To achieve this we have to introduce a new class called a QuestionToAsk. This looks almost identical to the IQuestionSyntax except it has an Ask(T obj) method which returns a IQuestionSyntax instead of an Answer property (this is because the other question is being answered where as this one needs to be given to the clubber). This meant overriding the Ask() method of the clubber to look like this:

public IQuestionSyntax Ask(QuestionToAsk questionToAsk)
{
return questionToAsk.Ask(this);
}

So I implemented the new parts to my framework and checked that all my existing tests and new tests passed and rolled out the Bouncer to the client.

I'm sorry if I rushed the last couple of posts but there should be enough info to get the pattern off the ground. What I like is that I can use the fluent style to make sentances up which are easy to read and apply to objects.

Saturday 10 March 2007

Fluent Specification Part 3: Giving feedback

There are a few things still left to be done to improve the Fluent Specification.

I’d like to make a couple of points about TDD and iterative development before I go on . Using TDD I have managed to get to a point where I’ve solved one problem at a time and using refactoring I have improved upon my original design and without deviating from my original goal. I have managed to roll out my existing functionality and give real value to my clients. Now I am in a good position to use this proven functionality elsewhere. If I had sat down and said “I’m going to create this great framework for specifications” I would probably have got myself in a complete mess and ended up taking longer to make something unwieldy and over-engineered.

Fluent Specification has been very succesfuly with my customer class so I decide to use it with other objects. I need to employ the power of generics. I don’t think I need to go into detail here so basically I now have the following interfaces:
  • IQuestionSyntax
  • ISpecification

As I was working on another domain I realised that my I realized that I really liked my Fluent Specification with my customer object and so I decided that I should harvest it http://www.martinfowler.com/bliki/HarvestedFramework.html.

The new domain was for a bouncer application which worked through a queue of clubbers and checked if they were allowed in. So I had some nice code which went something like this:

foreach(Clubber clubber in Queue)
{
if(clubber.Ask(IsClubber.OldEnough().AndNot(IsClubber.BreakingDressCode().And(IsClubber.NameDown(guestList))
{
clubber.RefuseEntry();
)
clubber.ComeIn(event);
}

So we rolled out our bouncer application and my client was very impressed. The number of dodgy clubbers getting in went down and everything was good. A few weeks later my client comes back and says he is getting a large number of complaints because my bouncer is just refusing clubbers without telling them why. I bit my tounge about this implementing standard bouncer behaviour as he likes to operate a customer friendly adult evening enterntainment service. So I went back to my code and tried to find a way of making my bouncer give some feedback.

I had to start introducing some new concepts. For a start my Anwser property became a full fledged object with a Feedback property and an IsTrue and IsFalse properties. I then created a IQuestion interface and a standard Question class which wrapped ISpecifications and Feedback (note how I've used whole value for Feedback).

public class Question : IQuestion
{
private readonly ISpecification spec;
private readonly Feedback feedback;

public Question(ISpecification spec, Feedback feedback)
{
this.spec = spec;
this.feedback = feedback;
}

public Answer Ask(T obj)
{
return new Answer(spec.IsSatisfiedBy(obj), feedback);
}
}

I then had to upgraded my Or/AndSpecification classes to become Or/AndCompositeQuestion : IQuestion classes. I then added feedback logic into the composite questions like so:

public class OrCompositeQuestion : IQuestion
{
IQuestion question1;
IQuestion question2;

public OrCompositeQuestion(IQuestion question1, IQuestion question2)
{
this.question1 = question1;
this.question2 = question2;
}

public Answer Ask(T obj)
{
Answer result1 = question1.Ask(obj);
Answer result2 = question2.Ask(obj);

Feedback feedback = string.Empty;

if(!result1.IsTrue)
{
feedback = feedback.Append("or", result1.Feedback);
}

if(!result2.IsTrue)
{
feedback = feedback.Append("or", result2.Feedback);
}

return new Answer(result1.IsTrue result2.IsTrue, feedback);
}
}
I then walked through my code refactoring in my new classes and adding the Feedback paramater to my Ask method. Now I could do this:
Answer answer = clubber.Ask(IsClubber.OldEnough(), "over eighteens only mate")
.AndNot(IsClubber.BreakingDressCode(), "trainers aren't allowed")
.And(IsClubber.NameDown(guestList), "if you're names not down you're not coming in");
if(answer.IsFalse)
{
clubber.Refuse(answer.Feedback);
}
clubber.ComeIn();

I have left alot of code out there but I think you should get the picture. There is a bit of cleaning up needed to do and maybe a little refactoring on the structure of my classes but still I've got something that works, it passes my tests, I can demo it to the client and make sure he's happy with my new enhanced Bouncer application.

Fluent Specification Part 2: Becoming more fluent

Last post we went through combining the two patterns Fluent Interface and Specification to make a Fluent Specification and ended up with something like this:
customer.Ask(IsCustomer.OldEnoughToBuyGoods(goods))
OK this all looks pretty good. Now I can ask my customer any question I like but I can only ask them one at a time so I would still have to do:
customer.Ask(IsCustomer.OldEnoughToBuyGoods(goods)) && customer.Ask(IsCustomer.AbleToAffordGoods(goods)) 
Wouldn't it be better if we could extend our fluent interface to make it more, well, fluent?
customer.Ask(IsCustomer.OldEnoughToBuyGoods(goods)).And(IsCustomer.AbleToAffordGoods(goods))
This starts looking more like predicates now (where you can join logical rules together easily).
To achieve this we need a new object called a Question:
public class CustomerQuestion
{
readonly bool answer;
readonly Customer customer;

public CustomerQuestion(bool answer, Customer customer)
{
this.answer = answer;
this.customer = customer;
}
public CustomerQuestion And(ISpecification spec)
{
return new CustomerQuestion(answer && spec.IsSatisfiedBy(customer), customer);
}
public bool Answer
{
get{return answer;}
}
}
And we need to update our Customer Ask() method to look like this:
public CustomerQuestion Ask(ISpecification spec)
{
return new CustomerQuestion(spec.IsSatisfiedBy(this), this);
}
Now we can link Questions together easily and by simply extending the Question we can add methods for Or, OrNot and AndNot allowing us to make nice little statements like:
customer.Ask(IsCustomer.OldEnoughToBuyGoods(goods)).AndNot(IsCustomer.GivingMeFakeId())
There are a few advantages to the Fluent Specification which I’d just like to go over before wrapping up this post; they are:

  • It is easy to join specifications together.
  • Clients can easily apply specifications to objects.
  • It makes the codes intent clearer.
  • Common specifications can be gathered together in a Factory making a clear API of business rules.
  • Unlike some Fluent Interfaces it doesn’t break Query and Command.

Next post I’d like to go over extending the Fluent Specification even further.

Fluent Specification Part 1: Combining patterns

Two patterns which are really quite nice are Fluent Interface and Specification. Both are fairly simplistic compared to most patterns. Fluent Interface is more of a style where you wire objects together in a walk the dot fashion to make them work like sentances. A common fluent interface is NMock2:
Expect.Once.On(object).Method("Name").With(value).Will(Return.Value(returnValue)) 
Compared to the old non-fluent interface of:
mockObject.ExpectAndReturn("Name", returnValue, value);
At first it looks like more code but as expectations become more complex and to anyone reading the code the fluent interface is a lot clearer and easier to work with.

The specification pattern comes from Eric Evans and is found in detail in his book Domain Driven Design. The idea of the specification pattern is to break your business rules into small self contained, reusable objects called specifications. The specification has an bool IsSatisfiedBy(object) method. The idea is to give some sort of predicate style to OOP languages. So rather than:
if(customer.Age >= goods.MinimumAge)
You replace it with a LegalAgeSpecification which looks like the following:
if(new LegalAgeSpecification(goods).IsSatisfiedBy(customer))
Which again, although may look like more stuff going on once you've got a few of these scattered about your class soon becomes clearer and easier to use. The added bonus is specifications can be reused all over the place: for example for validation, searching collections or within other specifications and they keep all of your business rules in the domain where they belong. What you'll soon discover is you will build up a rich set of business rules in your domain you can call upon at will.

So how about we take this a step further, create some synergy between some patterns (which is undoubtably where patterns get their greatest power) and create a Fluent Specification? The idea behind a Fluent Specification is to make your specifications look more fluent. The above code is rather ugly so why can't we do something like this instead:
if(customer.Ask(IsCustomer.OldEnoughToBuyGoods(goods)))
Isn't that a lot clearer already? The goods object now flows with the sentance of the interface rather than trying to hide its role by sitting in a constructor. Already we have made our intent clearer.

So how would we write something like this? Well there are three main components to this example:

  • A Question (the Ask() method)
  • A specification factory (the IsCustomer singleton)
  • A specification (the OldEnoughToBuyGoods object returned from the factory)

Normally I would do this TDD and set up a test which looks like the following:

Customer customer = new Customer();
customer.Age = 14;
Goods goods = new Alchohol();
Assert.IsFalse(customer.Ask(IsCustomer.OldEnoughToBuyGoods(goods)))

And of course test IsTrue as well but I don't want to make this post 100 pages long so you'll just have to take my word for the TDD. So the first thing to do (TDD style) would be to write a test for the specification itself and test it. So my specification looks like:

public class LegalAgeSpecification
{
readonly Goods goods;
public LegalAgeSpecification(Goods goods)
{
this.goods= goods;
}
public bool IsSatisfiedBy(Customer customer)
{
return customer.Age >= goods.MinimumAge;
}
}

I want to just draw attention to a few things about this specification: I have been very specific about passing the goods object and through rather than making a more generic OlderThanSpecification. The reason is that I may wish to expand this specification in the future to include more business rules as they come up: for example the business rule may say that for certain goods we must check for ID, so the rule may expand to this:

public bool IsSatisfiedBy(Customer customer)
{
if(goods.RequireId)
{
return customer.Id.DateOfBirth.CurrentAge >= goods.MinimumAge
}
return customer.Age >= goods.MinimumAge;
}

Of course you may wish to factor out that rule into a seperate ValidIdForMinimumAgeSpecifcation and call it within this one.
Does this go against YAGNI (You Ain't Gonna Need It)? No: because you are making the object more specific and more flexible by making a very simple design desicion. To have created a more generic OlderThanSpecification is more YAGNI as there is no requirement for that just yet. You may find that as time goes on you will refactor out the rule into a seperate spec but on first pass keep your objects specific to the business rule and inline with the ubiquitous language.
So now we have a specification and its up and running so how do we make this fluent? Well I'm going to rush forward and dump the Factory and the Question out in one go (though I was a little more structured using TDD).

public class Customer
{
...
public bool Ask(ISpecification spec)
{
return spec.IsSatisfiedBy(this);
}
}
public static class IsCustomer
{
public static ISpecification OldEnoughToBuyGoods(Goods goods)
{
return new LegalAgeSpecification(goods);
}
}

During my TDD to achieve my goal of a fluid and flexible interface I needed some refactoring and created an interface for the specification. Now my old specifaction implements the ISpecifcation interface making the Ask() method polymorphic.

Next post I’ll go through making this even more fluent.

About Me

My photo
West Malling, Kent, United Kingdom
I am a ThoughtWorker and general Memeologist living in the UK. I have worked in IT since 2000 on many projects from public facing websites in media and e-commerce to rich-client banking applications and corporate intranets. I am passionate and committed to making IT a better world.