About Me

Training

Nothin But .Net Developer Bootcamp

Navigation

Search

Categories

On this page

Validation In The Domain Layer - Take Two

Archive

Blogroll

 Agile Developer Venkat's Blog
 Ayende @ Blog
 B#
 Barry Gervin's Software Architecture Perspectives
 Boy Meets World
 Brad Abrams
 Canadian Developers
 Christopher Steen
 Claritude Software News
 Clemens Vasters: Enterprise Development and Alien Abductions
 Coding Horror
 Coding in an Igloo
 Dare Obasanjo aka Carnage4Life
 Darrell Norton's Blog [MVP]
 David Hayden [MVP C#]
 Don Box's Spoutlet
 Eric Gunnerson's C# Compendium
 EZWeb guy: Jeffrey Palermo [C# MVP]
 Fear and Loathing
 Generalities & Details: Adventures in the High-tech Underbelly
 Greg Young [MVP]
 Greg's Cool [Insert Clever Name] of the Day
 IanG on Tap
 Ingo Rammer's Weblog
 ISerializable - Roy Osherove's Blog
 James Kovacs' Weblog
 Jason Haley
 Jean-Luc David
 Jeremy D. Miller -- The Shade Tree Developer
 JetBrains .NET Tools Blog
 Jimmy Nilsson's weblog
 John Bristowe's Weblog
 John Papa [MVP C#]
 Jon Skeet's Coding Blog
 JonGalloway.ToString()
 Jump the Fence or Walk Around
 Lambda the Ultimate - Programming Languages Weblog
 Larkware News
 Lutz Roeder
 Marquee de Sells: Chris's insight outlet
 Martin Fowler's Bliki
 Mike Nichols - SonOfNun Technology
 MSDN Magazine - .NET Matters
 MSDN Magazine - All Articles
 OdeToCode Blogs
 Onion Blog
 Planet TW
 Raymond Lewallen [MVP]
 Rockford Lhotka
 RodMan's Corner
 Roger Johansson's blog
 Sahil Malik - blah.winsmarts.com
 Sam Gentile's Blog
 Scott Bellware [MVP]
 Scott Hanselman's Computer Zen
 ScottGu's Blog
 secretGeek
 Service Station, by Aaron Skonnard
 Signum sine tinnitu--by Guy Kawasaki
 Stephen Toub
 Steve Eichert's Blog
 Steven Rockarts
 The Blog Ride
 The Coding Hillbilly
 The Daily WTF
 TheServerSide.net: News
 Tim Gifford
 Vance Morrison's Weblog
 you've been HAACKED

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

RSS 2.0 | Atom 1.0 | CDF

Send mail to the author(s) E-mail

 Friday, June 16, 2006
Thursday, June 15, 2006 11:42:11 PM (Mountain Standard Time, UTC-07:00) ( .Net 2.0 | C Sharp )

It has been quite a while since I posted the first part of this scenario. I left off in a pretty good place, but now I need to revisit and solve the validation problems that I posed in the first entry:

  • Must be between the age of 18 – 75 to vote (yes there is an upper limit!!)
  • Must live in the country the candidate is running for

As well as the rules for voting, upon submitting a vote the person voting has to supply all of the required voter information:

  • FirstName
  • LastName
  • Age
  • Address
  • Gender
  • CountryOfResidence

We left off with having a Person domain object be able to perform validation. We now need to expand the scope to the vote class. A vote consists of both a Candidate and a Person. With the framework that is already in place, it becomes trivial to add the necessary validation for a Vote Class.

public sealed class Rules { private Rules() { } public static IBusinessRule<Vote> Age { get { return new BusinessRule<Vote>("Age", "Must be between 18 - 75 to vote", delegate(Vote vote) { return vote.voter.Age >= 18 && vote.voter.Age <= 75; }); } } public static IBusinessRule<Vote> Country { get { return new BusinessRule<Vote>("Country", "Must live in same country as Candidate", delegate(Vote vote) { return vote.voter.CountryOfResidence.Equals(vote.candidate.CountryOfResidence); }); } } public static IBusinessRuleSet Default { get { return new BusinessRuleSet<Vote>(Age,Country); } } }

This Rules class lives as a nested class inside of the Vote class. You will notice that the validation is fairly trivial. One thing that I have done is decrease the strong typing of the IBusinessRuleSet interface. Why? I want to have a layer supertype for all of my domain object that contains an IsValid property, and that will also invoke the appropriate BrokenBy method on the rule set. I accomplish this with minimum change required to the actual BusinessRuleSet class by changing the interface of IBusinessRuleSet<T> to the following:

 

public interface IBusinessRuleSet { IBusinessRuleSet BrokenBy(IDomainObject item); bool Contains(IRule rule); int Count { get; } IList<string> Messages { get; } bool IsEmpty { get;} }

With that change in place it means I now require a layer supertype for all of my domain objects:

 

public class DomainObject : IDomainObject { private IBusinessRuleSet rules; public DomainObject(IBusinessRuleSet rules) { this.rules = rules; } public IBusinessRuleSet Validate() { return rules.BrokenBy(this); } public bool IsValid { get { return Validate().IsEmpty; } } }

Notice how all DomainObjects will be constructed with a set of rules against which validation will be executed (great for testing, as well as loosening validation depending on the context). The layer supertype also takes care of performing the validation against itself. Even though it looks like I have lost some strong typing, this is not the case, as the main implementer of the IBusinessRuleSet interface is the BusinessRuleSet<T> class. It is still a generic class, and look at how it now implements the BrokenBy method:

 

public IBusinessRuleSet BrokenBy(IDomainObject item) { IList<IBusinessRule<T>> brokenRules = new List<IBusinessRule<T>>(); foreach (IBusinessRule<T> rule in rules) { if (! rule.IsSatisfiedBy((T) item)) { brokenRules.Add(rule); } } return new BusinessRuleSet<T>(brokenRules); }

Notice, that the BrokenBy method still ensures that the type of “item” is the type that it expects to be able to work with. It does this by performing a cast using the type T that it was constructed to hold rules for. Tests that exercise this method for specific types of objects will fail if the object passed into the BrokenBy call is not of type T.

So by making a small change to the interface we have now allowed for any new domain object  to now inherit from a layer supertype and have and its disposal a Validate and IsValid methods. Here is the first test that I wrote when it came to validating the rules for a vote:

  

[Test] public void ShouldVerifyVoterLivesInSameCountryAsCandidate() { IPerson person = new Person("JP", "Boodhoo", "Test", 20, Country.CANADA, Gender.MALE); Candidate candidate = new Candidate("JP", "Boodhoo", "Test", 20, Country.CANADA, Gender.MALE, Party.CONSERVATIVE); IPerson notInSameCountryAsCandidate = new Person("JP", "Boodhoo", "Test", 20, Country.USA, Gender.MALE); Vote vote = new Vote(person, candidate); Assert.IsTrue(vote.IsValid); vote = new Vote(notInSameCountryAsCandidate, candidate); Assert.IsFalse(vote.IsValid); }

As you can see, small focused tests can help drive out lots of functionality. Last but not least is the introduction of a PollingStation class, that makes use of all of the code we have put into place:

 

 

public class PollingStation { private IList<Vote> invalidVotes; private IList<Vote> validVotes; private IList<IPerson> incompleteVoters; private int voteNumber; public PollingStation() { invalidVotes = new List<Vote>(); validVotes = new List<Vote>(); incompleteVoters = new List<IPerson>(); } public void RegisterVoteFor(IPerson person,ICandidate candidate) { if (person.IsValid) { Vote vote = new Vote(++voteNumber,person, candidate); if (vote.IsValid) { validVotes.Add(vote); } else { invalidVotes.Add(vote); } } else { incompleteVoters.Add(person); } } public IList<Vote> InvalidVotes { get { return invalidVotes; } } public IList<Vote> ValidVotes { get { return validVotes; } } public IList<IPerson> IncompleteVoters { get { return incompleteVoters; } } }

Here is the test that I wrote to drive out the usage of the PollingStation class:

 

[Test] public void ShouldRegisterPolls() { ICandidate liberalCandidate = new Candidate("Lib", "Lib", "LibAddress", 30, Country.CANADA, Gender.MALE, Party.LIBERAL); ICandidate conservativeCandidate = new Candidate("Lib", "Lib", "LibAddress", 30, Country.CANADA, Gender.MALE, Party.CONSERVATIVE); ICandidate ndpCandidate = new Candidate("Lib", "Lib", "LibAddress", 30, Country.CANADA, Gender.MALE, Party.NDP); PollingStation station = new PollingStation(); IPerson tooYoungToVote = new Person("DF", "DF", "dfd", 17, Country.CANADA, Gender.MALE); IPerson tooOldToVote = new Person("DF", "DF", "dfd", 78, Country.CANADA, Gender.MALE); IPerson livesInDifferentCountry = new Person("DF", "DF", "dfd", 18, Country.USA, Gender.MALE); IPerson incompleteInfo = new Person("", "DF", "dfd", 35, Country.CANADA, Gender.MALE); IPerson liberalVoter = new Person("fd", "DF", "dfd", 35, Country.CANADA, Gender.MALE); IPerson conservativeVoter = new Person("fd", "DF", "dfd", 35, Country.CANADA, Gender.MALE); IPerson ndpVoter = new Person("fd", "DF", "dfd", 35, Country.CANADA, Gender.MALE); station.RegisterVoteFor(tooYoungToVote,liberalCandidate); station.RegisterVoteFor(tooOldToVote,liberalCandidate); station.RegisterVoteFor(livesInDifferentCountry,liberalCandidate); station.RegisterVoteFor(incompleteInfo,liberalCandidate); station.RegisterVoteFor(liberalVoter,liberalCandidate); station.RegisterVoteFor(conservativeVoter,conservativeCandidate); station.RegisterVoteFor(ndpVoter,ndpCandidate); Assert.AreEqual(1,station.IncompleteVoters.Count); Assert.AreEqual(3,station.InvalidVotes.Count); Assert.AreEqual(3,station.ValidVotes.Count); OutputStats<IPerson>("Incomplete Voters", station.IncompleteVoters); OutputStats<Vote>("Invalid Votes", station.InvalidVotes); }

Are you wondering what is going on in the OutputStats<T> method. This was just something I put in so you could visualise the validation errors. When this test is run the following will be output to the console window:

 

Incomplete Voters
	First name is required

Invalid Votes
	Must be between 18 - 75 to vote

	Must be between 18 - 75 to vote

	Must live in same country as Candidate

As is common lately, the source code for this completed project is here. And once again, comments are always encouraged and appreciated.

JP

kick it on dotnetkicks.com