Requirements for a Basic Rules API Related to Persistence






Requirements for a Basic Rules API Related to Persistence

Now that we have decided on a specific context for now, that of the transition to the state of persisted, let's start the discussion about an API regarding that. We will get back to other (and from the model's point of view, more interesting) state transition problems, both general and specific, afterward.

Note

As usual, the discussion here is really more about some generic ideas than the API itself. I'm not trying to tell you how you should create your inter-faces/APIs, I'm just trying to give my thoughts on the subject and hope that you may be prompted to find your own solutions.


I'd like to take a new code example where we apply the idea about being proactive regarding not persisting exceptional state errors and/or problems that will throw exceptions from the persistence engine.

I'm going for the latter: showing a situation when the persistence engine would throw an exception. (Again, the former problem should be hard to get to. If everything works as expected, that state shouldn't be possible to reach.)

Assume that Order has Note, and Note is defined as VARCHAR(30) in the database. Depending on your solution for persistence, you might get an exception when trying to save a long string, or you will lose information silently. The important thing here is that I will proactively catch the problem. It could look like this:

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


    Assert.IsFalse(o.IsValidRegardingPersistence);
}

So instead of asking if the order is IsValid, I changed the name of the property to IsValidRegardingPersistence to make it very clear that the order isn't valid in the context of being persisted.

Let's set up a simple interface for the purpose of letting the consumer ask an Aggregate root if it's in a valid state or not for being persisted. It could look like this:

public interface IValidatableRegardingPersistence
{
    bool IsValidRegardingPersistence {get;}
}

So you let your classes that have persistence-related rules implement that interface. Normally the consumer only has to ask the Aggregate [Evans DDD] root if it's valid or not to be persisted and the Aggregate root will check its children.

Externalize Rules á la Persistence?

As you saw in the code snippet, I chose to let the Domain Model classes themselves be responsible for checking rules; considering how we like to deal with persistence (from the "outside"), that might feel strange.

This might be something to think about, but for now I'll stick to my first idea here, partly because I think the rules are at the heart of the Domain Model. The rules are not "just" an infrastructural aspect, as persistence is.


Good, we are back on track.

Back to the Found API Problems

Several pages back I found out a couple of problems with the API I started to sketch. I delayed the discussion because I wanted to first deal with the problem of lack of context. With that out of the way, let's get back to those problems to see if they still apply to the previous code snippet (CantExceedStringLengthWhenPersisting()). The problems were

  • What was the problem? We only know that there was one, not what it was.

  • We allowed an incorrect transition.

  • What if we forgot to check?

Let's discuss those three problems in the context of the persistence-related rules.

What Was the Problem?

The first problem still applies. If the instance isn't valid, it might well be interesting to find out which rules have been broken. The Aggregate root will aggregate the broken rules from all its children as well as from itself and return the broken rules as an IList.

Let's add to the interface for dealing with this. It could look like this:

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

Note

As I see it, the list of broken rules is an implementation of the Notification pattern [Fowler PoEAA2].

One important thing mentioned with that pattern is that it's not only errors that we need to be informed about, but warnings/information as well. Another thought that springs to mind is that the broken rules list I have talked about is pretty focused on Aggregates, but we can, of course, aggregate (pun not intended) several such lists into a single one in an Application layer [Evans DDD], such as from several Aggregate root instances.


We Allowed an Incorrect Transition

Sure, the Note became too long, and that could be considered to be a transition to an invalid state. But I can't say I think of that thing as very interesting most often. I would probably let the order be around with a too-long Note for some time to make it possible for the user to delete a few letters of the Note.

Unfortunately, in this case, the too-long Note won't be possible to save. That transition between not persisted and persisted, on the other hand, is interesting here. But it won't be allowed. (As I said, I will get an exception or the string will be truncated silently.)

The transition to persisted is a bit "special." We will get back to the problem of allowing incorrect transitions in the context of domain-related rules shortly.

What If We Forgot to Check?

If the consumer doesn't follow the protocol of first checking the possibility of a transition before trying to make the transition, we have an exceptional situation, and exceptional situations throw exceptions. The same applies here, too. So if we tried to transition to persisted, we would like to throw an exception that is subclassing ApplicationException and will hold an IList of broken rules.

Again, the consumer isn't following the protocol, and that's an exceptional situation.

We got caught up in a specific transition problem: transition to persisted. It's time to change focus from infrastructure-related transitions to domain-related for a while, but we will try to move some of the ideas with us, such as that it's an exception if the consumer does not follow the protocol.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows