Refining the Implementation


So far we have been focusing on the rules API from a consumer viewpoint. I think it would be interesting to write about some ideas on how to implement the rules as well. So let's get under the hood of the Order class and try out some ideas.

Before we actually start "refining" we need to come up with a very simple solution to the problem so that we have something to start with.

A Naïve Implementation

Let's see...first of all, we need a test. We can reuse something from before and change it a bit. The CantExceedStringLengthWhenPersisting() test will do. It looked like this:

[Test] public void CantExceedStringLengthWhenPersisting() {     Order o = new Order(new Customer());     o.Note = "0123456789012345678901234567890";     Assert.IsFalse(o.IsValidRegardingPersistence); }


We need to write an IsValidRegardingPersistence property on the Order class for the test even to compile. Something like this should work:

//Order public bool IsValidRegardingPersistence {     get     {        if (Note.Length > 30)            return false;        return true;     } }


Note

Of course, I haven't forgotten the TDD mantra of red, green, refactor, red, green, refactor. I just didn't want to slow down your reading rhythm here.


OK, that works. Let's take a copy of the test (clipboard inheritance), change the name, and expand it a bit:

[Test] public void TryingIdeasWithTheRulesAPI() {     Order o = new Order(new Customer());     o.Note = "012345678901234567890123456789";     Assert.IsTrue(o.IsValidRegardingPersistence);     o.OrderDate = DateTime.Today.AddDays(1);     Assert.IsFalse(o.IsValidRegardingPersistence);     o.OrderDate = DateTime.Today;     Assert.IsTrue(o.IsValidRegardingPersistence);     o.Note += "012345";     Assert.IsFalse(o.IsValidRegardingPersistence); }


As you saw, I added one more rule stating that the OrderDate can't be a future date, and for some reason (not too much of a stretch) that rule was mandatory regarding persistence.

Note

To make the OrderDate rule obviously and mandatory connected to infrastructure, it shouldn't be allowed to be smaller/larger than what your database can store in a DATETIME (if there is such a difference for your database).


In order to get a green test, we need to change IsValidRegardingPersistence a little bit, as follows:

//Order public bool IsValidRegardingPersistence {     get     {         if (Note.Length > 30)             return false;         if (OrderDate > DateTime.Today)             return false;         return true;     } }


I think that will do for now as far as rules complexity goes. Not too much, eh?

The next step is to report back to the user, so we need to write a BrokenRulesRegardingPersistence property. Again, let's do something extremely basic, but first we need to make a few changes to the test:

[Test] public void TryingIdeasWithTheRulesAPI() {     Order o = new Order(new Customer());     o.Note = "012345678901234567890123456789";     Assert.IsTrue(o.IsValidRegardingPersistence);     Assert.AreEqual(0, o.BrokenRulesRegardingPersistence.Count);     o.OrderDate = DateTime.Today.AddDays(1);     Assert.IsFalse(o.IsValidRegardingPersistence);     Assert.AreEqual(1, o.BrokenRulesRegardingPersistence.Count);     o.OrderDate = DateTime.Today;     Assert.IsTrue(o.IsValidRegardingPersistence);     Assert.AreEqual(0, o.BrokenRulesRegardingPersistence.Count);     o.Note += "012345";     Assert.IsFalse(o.IsValidRegardingPersistence);     Assert.AreEqual(1, o.BrokenRulesRegardingPersistence.Count); }


And then a first implementation:

//Order public IList BrokenRulesRegardingPersistence {     get     {         IList brokenRules = new ArrayList();         if (Note.Length > 30)            brokenRules.Add("Note is too long.");         if (OrderDate > DateTime.Today)            brokenRules.Add("OrderDate is in the future.");         return brokenRules;     } }


This is often not detailed enough information about broken rules, but at least we have started. What feels worse right now is that I don't want to express the same rules in both the IsValidRegardingPersistence and the BrokenRulesRegardingPersistence properties, so for now let's change IsValidRegardingPersistence to an execution inefficient solution instead, which cuts down on duplication.

Note

This inefficiency regarding execution is often not much to worry about if you put it in relationship to some database calls, for example.


//Order public bool IsValidRegardingPersistence {     get {return BrokenRulesRegardingPersistence.Count == 0;} }


OK, we have dealt with the possibility of asking whether the instance is in a valid state for being persisted at any time and what the current problems are ("dealt with" is an overstatement, but still).

There is another protocol, though, and that relates to transitions. Let's deal with a transition and make it possible to get information about the problems. We will also deal with the situations reactively (that is, we'll react if the transition is attempted when there are known problems).

Let's say that a rule for being able to Accept() an order is that the Customer for the order must be in Accepted state. It could go like this:

[Test] public void TryingTheAcceptTransitionWithTheRulesAPI() {     Order o = new Order(new Customer());     Assert.IsFalse(o.IsOKToAccept);     o.Customer.Accept();     Assert.IsTrue(o.IsOKToAccept); }


You should note especially that for a new Order (NewOrder state), it's okay to have a Customer that isn't accepted.

Then if we make the Customer Accepted, it's okay to Accept() the Order as well.

One more thing I'd like to show, though, is that the general rules for the Order will be applied at the transitions as well, so let's add that to the test:

[Test] public void TryingTheAcceptTransitionWithTheRulesAPI() {     Order o = new Order(new Customer());     Assert.IsFalse(o.IsOKToAccept);     o.Customer.Accept();     Assert.IsTrue(o.IsOKToAccept);     o.OrderDate = DateTime.Today.AddDays(1);     Assert.IsFalse(o.IsOKToAccept);     try     {         o.Accept();         Assert.Fail();     }     catch (ApplicationException ex) {} }


Note

What also happens is that the new Order as default for OrderDate is set to DateTime.Today in the constructor. That's why it is IsOKToAccept directly.


And while I was at it, I also showed what will happen if the transition is tried even though it shouldn't work.

We need to do something in the Order for this. First, let's do something in IsOKToAccept.

//Order public bool IsOKToAccept {     get     {         if (Customer.Status != CustomerStatus.Accepted)             return false;         return IsValidRegardingPersistence;     } }


As you saw, I first checked the specific rules (or rule in this case) and then the persistence related rules, because I don't want to move on with something that won't be savable; therefore, I want to point out problems (persistence related ones as well) as early as possible.

Again, I need to change this a bit if I also want a method for telling what broken rules there will be if I call the Accept(), but that's simple and not important to show right now.

The same goes for the Accept() method. It's pretty simple and just needs to check IsOKToAccept before doing the transition.

What might be a bit "tricky," or at least not obvious, is that now the rules for the IsValidRegardingPersistence just got a bit more complex, because after we enter the Accepted status of the Order, we will have to check the same rules even in the IsValidRegardingPersistence property. We moved the real processing to the BrokenRulesRegardingPersistence property as you saw before, so that is where you will see the change:

//Order public IList BrokenRulesRegardingPersistence {     get     {         IList brokenRules = new ArrayList();         if (Note.Length > 30)             brokenRules.Add("Note is too long.");         if (_orderDate > DateTime.Today)             brokenRules.Add("OrderDate is in the future.");         if (_OrderIsInThisStateOrBeyond(OrderStatus.Accepted))             _CollectBrokenRulesRegardingAccepted(brokenRules);         return brokenRules;     } }


So in _CollectBrokenRulesRegardingAccepted(), there will be a specific rule evaluation for entering (or rather, in this case, being in) the Accepted state.

Note

You may have noticed that I used the Collecting Parameter pattern [Beck SBPP] for _CollectBrokenRulesRegardingAccepted(). I sent in the list of broken rules and asked the method to add more broken rules to the list if appropriate.


To summarize all this, I think you recognize plenty of smells and lack of functionality. For example:

  • Code explosion in BrokenRulesRegardingPersistence because rule evaluation is manually coded

  • Returned information about broken rules is just strings, which isn't enough

  • We haven't addressed customization

  • We haven't addressed providing information about the rules themselves to be used by the UI

It's time to refine this naïve implementation a little bit, without showing the iterations for getting there. Let's reuse the tests and generalize the implementation a bit.

Creating Rule ClassesLeaving the Most Naïve Stage

I think creating rule classes for the basic needs so that they can be used declaratively will cut down on code explosion, manual rule evaluation, and deal with customization and providing information about the rules to the outside.

The complex rules always have to be coded by hand for each situation and are hard to customize by adding information in a config file. Furthermore, it's not important to provide information about the complex rules to generic forms. Those rules aren't my target here. They aren't uninteresting; on the contrary, I want to code them by hand. If they fit into the big picture, that's fine, but what I'm trying to say is it's not important that they are declared.

My target for what should be declared is the simple rules: those that are uninteresting to write by hand over and over again. Let's give it a try.

Note

As always, watch out so you don't fall into the trap of building frameworks up front. It's generally better to let them show themselves in your code and then you go from there. [Fowler Harvested Framework]


My rule classes should implement an interface; let's call it IRule:

public interface IRule {     bool IsValid {get;}     int RuleId {get;}     string[] ParticipatingLogicalFields {get;} }


IsValid is used to check if the rule is valid or not. The RuleId is used for making it possible to translate rules information and such. It requires some extra information, typically in the form of long constant lists, but I'll leave that to the reader.

The idea regarding ParticipatingLogicalFields is that the consumer should be able to mark out the fields that are affecting a certain broken rule.

And a rule class could look like the following example. This one is for checking that a date is within a certain range:

public class DateIsInRangeRule : RuleBase {     public readonly DateTime MinDate;     public readonly DateTime MaxDate;     //Note: Only the "largest" constructor is shown here.     public DateIsInRangeRule(DateTime minDate, DateTime maxDate,         int ruleId, string[] participatingLogicalFields,         string fieldName, object holder)         : base(ruleId, participatingLogicalFields, fieldname         , holder)     {         MinDate = minDate;         MaxDate = maxDate;     }     public override bool IsValid     {         get         {             DateTime value = (DateTime)base.GetValue();             return (value >= MinDate && value <= MaxDate);         }     } }


I also needed a base class (RuleBase) for generic functionality, such as reading values by reflection, and for taking away the infrastructure part of the game from the subclasses. All that is left for the subclasses is the "interesting" code (and some constructorsI only showed one in the previous snippet).

Note

By the way, fieldName can be either a field or a property.

If you wonder what holder is, it's the Domain Model instance, such as an order instance, that is using the rule object.


I think a figure would help here to explain the different pieces. See Figure 7-2.

Figure 7-2. Overview of IRule, RuleBase, DateIsInRangeRule, and a Domain Model class


Then we "only" have to write classes, such as DateIsInRangeRule, for the different needs that crop up. It's a piece of highly reusable code that is being written, but it will take some effort.

You might be wondering why I wanted to use reflection. "Want" isn't exactly correct, but I want the positive effect of reflection. I mean, I like to be able to define the rule once, and then let it execute without providing the value to check against explicitly. Instead, I'd provide the field/property that has the value. Let's look at how it could be done.

Note

A possible optimization is to only use reflection when really needed. For example, when you use reference types, you don't have to go via reflection to get the same effect. On the other hand, that does increase the complexity a little bit, so watch out carefully.

Gregory Young added the following: "Another answer here might be to use code generation to amortize the reflections overhead."


Setting Up a List of Rules

We have one rule class so far. Let's assume a similar one for MaxStringLengthRule as well. It's time to put them to use. When using them in an Order class, it could look like this:

//Order private IList _persistenceRelatedRules = new ArrayList(); private DateTime _orderDate; public string Note = string.Empty; private void _SetUpPersistenceRelatedRules() {    _persistenceRelatedRules.Add(new DateIsInRangeRule        (MyDateTime.MinValue, DateTime.Today, "OrderDate", this));    _persistenceRelatedRules.Add(new MaxStringLengthRule        (30, "Note", this)); }


Then in the constructors of the Order, I just call the _SetUpPersistenceRelatedRules().

Note

I'm not putting any effort into giving the rules instances good identifiers. Therefore, I used a constructor here where I'm not providing the values at all.


Using the List of Rules

I have the persistence related rules in place. Let's see how this could affect the BrokenRulesRegardingPersistence implementation.

//Order public IList BrokenRulesRegardingPersistence {     get     {         IList brokenRules = new ArrayList();         RuleBase.CollectBrokenRules(brokenRules             , _persistenceRelatedRules);         if (_OrderIsInThisStateOrBeyond(OrderStatus.Accepted)             _CollectBrokenRulesRegardingAccepted(brokenRules);         return brokenRules;     } }


Note

Do you remember I said that the list of broken rules is an implementation of the Notification pattern [Fowler PoEAA2]? I still think it is, but I have moved away slightly.

Gregory Young described the difference like this: "The key difference is that the actual rule (IRule) is exposed as opposed to a surrogate object which simply says it has been broken as is the case with the Notification pattern."


A good bonus, thanks to the solution, is that we can easily write a RuleBase.IsValid() that we provide with the list of _persistenceRelatedRules as a parameter. That way we can increase the execution efficiency for the IsValid property without increasing the amount of code duplication. I mean, that implementation should just see if it finds any rule that is invalid. There is no need to collect a list of broken rules, no need to go through all the rules even though the first one is broken, and so on.

Back to the basic flow. Let's have a look at how CollectBrokenRules() in RuleBase could look:

//RuleBase public static void CollectBrokenRules     (IList brokenRules, IList rulesToCheck) {     foreach (IRule r in rulesToCheck)         if (! r.IsValid)             brokenRules.Add(r); }


That was about the persistence related rules; very straightforward. But what about the rules per transition?

Dealing with Sublists

One solution to this problem could be to set up transition-specific lists as well, such as _acceptedSpecificRules. When that is in place, the BrokenRulesRegardingPersistence method can be written like this:

//Order public IList BrokenRulesRegardingPersistence {     get     {         IList brokenRules = new ArrayList();         RuleBase.CollectBrokenRules(brokenRules             , _persistenceRelatedRules);         if (_OrderIsInThisStateOrBeyond(OrderStatus.Accepted)             RuleBase.CollectBrokenRules(brokenRules,                 _acceptedSpecificRules);         return brokenRules;     } }


That way, most of the work has moved to defining the list of rules (again, we can only go so far with this approach, but it is actually quite a long way).

I now put all the rules in one bucket (or actually several if there are transition-based lists as well): an instance-based one. With some work, both of the rules examples shown so far (DateIsInRangeRule and MaxStringLengthRule) could be changed to a list of static rules instead. I need to change the date rule so that it will evaluate what DateTime.Today is on its own instead of getting it in the constructor. I could also provide relative dates to the constructor as -1 and 1 to mean one day before and one day after today. I could also create some functionality that will only let the static list live until it's a new day. Of course, you should strive to move the rules to static lists instead, if possible. That will potentially reduce the overhead a lot. With that said, it's not affecting the principle much, if at all.

Let's move on instead of looking more into those details regarding scope.

An API Improvement

My friend Ingemar Lundberg pointed out that it felt a bit strange to use a property for BrokenRulesRegardingPersistence, and Christian Crowhurst suggested that it would be better to let a method use a collecting parameter and return a bool. Something like this:

public bool CheckX(IList brokenRules)


Do you remember IsOKToAccept and BrokenRulesIfAccept? This idea will reduce the size of the API and not split the check and the detailed error information into two separate pieces. Those two pieces could very well be rewritten into one method like this:

public bool CheckOKToAccept(IList brokenRules)


The consumer code will be slightly affected. Instead of this:

//Some consumer... if (! o.IsOKToAccept)     _Show(o.BrokenRulesIfAccept);


You would typically write something like this:

//Some consumer... IList brokenRules = new ArrayList(); if (! o.CheckOKToAccept(brokenRules))     _Show(brokenRules);


Regarding the collecting parameter brokenRules, if it's not initialized (null) when CheckOKToAccept() starts, CheckOKToAccept() can ignore it and only return the bool. That's at least one approach, but an overload to CheckOKToAccept() without a parameter feels more intention revealing. (It's not really a split into two methods, but just a chaining from one of the overloads to the other. It's also the case that even if the bool is TRue, the returned list can have warnings for example.)

An additional consequence is that it steers a consumer away from executing rule checking code twice, once for the IsOKToAccept and again for the BrokenRulesIfAccept. For example, it is unlikely that the client will write the following code:

//Some consumer... if (! o.CheckOKToAccept()) {     IList brokenRules = new ArrayList();     o.CheckOKToAccept(brokenRules);     _Show(brokenRules); }


I like this.

Customization

There are loads of ways to achieve customization of a Domain Model; for example, you could let different customers who are using your application add some behavior of their own. One appealing solution is to use the Decorator pattern [GoF Design Patterns], while another is to provide inheritance hooks and configurable creation code.

Yet another, and pretty simplistic, technique is to make it possible to define the rules, or at least add specific rule instances, by describing them in the form of configuration files.

With the current solution draft, we have most of it in place. What we need is a way of adding rules, so let's sketch a new interface called ICustomRules:

interface ICustomRules {     void AddCustomRule(IRule r); }


In this first draft, I just assume that we can add persistence related rules and that they go to the instance-based list of rules. That can (and should) be refined if you need to.

Note

Talking about needing to add rule instances to static lists reminds me of an old wish of mine. I'd like to be able to define static interfaces as well. Why not?


Now you can add more rules during instantiation of Domain Model classes, butas I saidinstantiation gets a bit messy, and you should probably use a Factory of some sort.

The good news is that the customization we just talked about works seam-lessly with the possibility of dragging out information about the rules, which comes in very handy for creating the forms.

Providing the Consumer with Metadata

The persistence related rules should be treated as something that can be dragged out of the Domain Model instance proactively. This is because they could be easily used by the consumer. I add another method to the IValidatableRegardingPersistence interface:

interface IValidatableRegardingPersistence {     bool IsValidRegardingPersistence {get;}     IList BrokenRulesRegardingPersistence {get;}     IList PersistenceRelatedRules {get;} }


Then it's up to the consumer to do something interesting with the rules she finds. There will probably be some rules that are easily used by the UI when new forms are shown, such as fields that are required, certain value ranges, lengths of strings and so on.

Conceptually, I like the solution. Something like this should be useful for a great number of projects. But I can't help feeling that it is still a bit smelly, especially regarding the transition-specific rules, at least if the scale of the problem increases.

A Problem Suitable for a Pattern?

What do you think about using the State pattern [GoF Design Patterns] here? We discussed it quite a lot in Chapter 2, "A Head Start on Patterns." It's quite appealing if you ask me, and it means I can delegate the responsibility of the transition-specific lists out into the certain state classes. It's actually a problem that the State pattern is pretty much made for.

You can also work with several levels in the inheritance hierarchy regarding the different states; for example, if an instance-based rule definition is the same for several of the states.

What About the Complex Rules?

As I said, I don't mind writing the complex rules by hand. I want to spend my time there.

I think applying the Specification pattern [Evans DDD] is often a good idea for complex rules.

What we try to achieve when using Specifications is to express important domain concepts in code. The common theme with DDD is to be concept-focused. (The focus is on domain concepts instead of just technical concepts. For example, numeric intervals and string lengths are more of technical concepts most often.)

For example, there might be a Specification class called ReadyToInvoiceSpecification with a method like this:

public bool Test(Order o);


Let's take a quick and simple example. Assume that you'd like to create a single invoice for a customer, even if the customer currently has several orders that can be invoiced. Perhaps you then would like to ask the customer if he has something to invoice. A method on the Customer class could look like this (assuming that an IOrderRepository has already been injected):

//Customer public IList OrdersToInvoice(ReadyToInvoiceSpecification     specification) {     IList orders = _orderRepository.GetOrders(this);     IList result = new ArrayList();     foreach (Order o in orders)     {         if (specification.Test(o))             result.Add(o);     }     return result; }


As always, it's context-dependent whether a certain design is recommended or is even possible, but my focus here was just to provide you with a simple example that shows how the concept could be applied and that the code is very clear and yet highly flexible. (Remember, the Specification parameter had been configured before being sent in.)

Again, the rules for what makes an order invoicable are encapsulated in the Specification, not spread out in several methods.

Note

A very nice feature of a Specification implementation is if you can let it be evaluated in the database as well, so that it can be used for concept-focused querying.


The problem with the Specification approach as I see it is how to make it easy for the UI to automatically do smart things beforehand instead of letting the rule break. On the other hand, I can often live with that problem, especially if Specifications are used for somewhat more advanced problems that are hard to be proactive about anyway. On the other hand, if a Specification is using a concept similar to IRule, the parts of the Specification could be used as usual regarding proactivity for automatically setting up the UI.

I also must point out how nice complex rules that are encapsulated as Specifications are when it comes to unit testing. It's very easy to test the Specifications separately.




Applying Domain-Driven Design and Patterns(c) With Examples in C# and  .NET
Applying Domain-Driven Design and Patterns: With Examples in C# and .NET
ISBN: 0321268202
EAN: 2147483647
Year: 2006
Pages: 179
Authors: Jimmy Nilsson

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net