Wednesday, 8 April 2015

In Defence of Anemia

The 'Anemic Data Model' is regarded by some influential people as an anti-pattern. Martin Fowler describes it as 'contrary to the basic idea of object-oriented design; which is to combine data and process together'. So, with some trepidation, I disagree. But I'm not sure Fowler would call what I do 'anemic'.

For those who don't know what an Anemic Data Model is imagine you have a collection of Java objects (but any OO language will do). These are things like Customer, Order, OrderLine and so on. We have fields in them like customerNumber and (in Java anyway) we normally have setter and getter methods to set and query the field values. So far so normal. Now if we leave it like that it is an Anemic Data Model. If we add logic to the Customer object to, say, validate the customerNumber then we have a non-Anemic Data Model. We might add other code, for example we might have code in the Order to create a new OrderLine etc.

The opposite view, ie the one that prefers the Anemic Data Model, says you must put all this code into a Service Layer of objects that are there specifically to hold the business logic and manage the housekeeping.

There's actually quite a good summary of the pros and cons of ADM on stackoverflow, and I particularly like the #92 comment (the second one down, there aren't so very many to read) where Eric P says "Possibly it is because with Rich Domain [ie non-Anemic] it is more difficult to know on which classes to put the logic. When to create new classes? Which patterns to use? etc."

I'm working with a system at the moment where I suspect no one thought to much about these issues way back when it was written, so there are lots of service layer objects and data model objects, and they all have business logic scattered through them in a pattern I have yet to detect. Actually they have some of their business logic in the UI layer too and this is not uncommon.

Why is this even important? It is not really that things are hard to find. The IDEs we use these days have really good search facilities. It might take a couple more steps to find something but it is not a huge problem. For example finding the validation for customerNumber just needs me to invoke a search for all references to that field and maybe to the setter for that field. Job done. No, the problems come about when you need to change things around, especially in bulk.

The difference between data model objects and all the others is that they end up being serialized, which usually means they get written to a database. The database structure is not defined by our Java domain objects, at least not directly, but they need to be in sync. So various tools are around to generate SQL from annotated Java, or generate Java from a database. And the moment you decide to re-generate your domain objects from a database what happens to that customerNumber validation you coded into the Customer object? Gone.

People don't change their database so very often but there is an annoying trickle of triple maintenance requests ever after: add a new field to the Customer object, fix the SQL scripts to include the field, add it to existing databases.

So most people who decide to implement an Anemic Data Model structure things like this:
The Service Layer tends to be a bit vague on diagrams like this but I suppose you'd say it was in the arrows. It sure isn't in the Domain.

But even if you say all the logic has to be outside the Domain putting it in the Service Layer is only an implication. It can still end up in the UI layer (and it does!). This UI thing is a real problem, actually. People put validation up there and maybe some computation. When there is a requirement to shift the UI to a new technology you suddenly have to re-implement a ton of business logic (and you probably did not realise it was there so this wasn't in the budget, cue death march music....).

So we're better to say very, very definitely where all the business logic goes. Service layer! Well of course, but let's get a bit more formal. And let's take the opportunity to simplify things. Remember simplifying things means your new-hire programmer will be able to understand your code faster. Easier coding, fewer screw ups, all good stuff. Sure, but how? Well first we encapsulate.

Encapsulation is a well proven approach for simplifying things. You encapsulate the components in such a way that when someone is looking at one part of the system they don't have to know the details of all the other parts. If you code you already know this.

I mentioned earlier that there are tools which generate Java objects from database structures and vice versa. The limitation with these tools is that is all they do and this approach will need a little more. So we can turn to JAXB which generates the Java objects from an XSD file. XSD is a standard descriptor file for XML structures. XML? Well, yes. XML is another way of describing an Anemic Data model. You can't add code to XML. So our first step is to write an XSD file that describes our objects.

JAXB is a smart beast in that it accepts plugins that assist it generating the Java representation of the objects. One of those plugins adds JPA annotations, it's called HyperJAXB3. So the resulting objects are all set up to map to a database. You can generate SQL scripts or databases from there using the usual tools. You can also serialize to XML if you want.

JAXB can add all kinds of stuff to the generated code. For example maybe you want to add Ehcache annotations to the classes, that's a tiny tweak to the JAXB configuration. Then you regenerate and you haven't broken anything. You haven't lost any code because you never edit the generated objects. Actually this is a scenario worth exploring. You might want to add Hibernate caching to all your database objects. JAXB can do that no trouble. Then you might decide you'd prefer to use JPA caching. Again, no trouble, just change the settings and regenerate.

But we're not done yet. We can add JSR-303 validation to the objects. You specify the validations you want in the XSD file and the annotations will end up on the generated Java fields. It doesn't mean they will be validated, but it means you can provide generic code that will validate. This generic code doesn't count as business logic. The business logic is the annotation, it is an important difference.

Maybe we can do more? Yes, of course we can. We can use Madura Objects which implements a setter triggered delegation to a validation engine (yes, still validation so far). The important thing about this is that normally JSR-303 lets you set a value in a field and then check it. Madura Objects checks it beforehand and throws an exception if it is wrong. It means you don't need the logic to switch the value back if it fails. The validation function of Madura Objects still counts as generic code because it is driven by the validations (ie business logic) originally specified in the XSD.

This is still an Anemic Data Model but it is a delegating Anemic Data Model. And we want to put as much of the business logic into that delegated layer as we can. Want to add up the order lines into an order total? Do it in the delegation, triggered by the setting of a value on any order line. Want to calculate the tax on an order? Do it in the delegation, triggered by setting the total on the order.

The Service Layer now only needs to care about housekeeping stuff like creating objects, attaching them to other objects (adding an order line to an order), but never the real business logic. To extend that business logic we can add a rules engine, a plug-in to Madura Objects called Madura Rules. That implements declarative rules in a constraint engine. It also implements truth maintenance which means if you change a field that it derived some value from (say the value in an order line that fed into the order total) then the order total is automatically recalculated in much the same way as a spreadsheet.

What should that diagram look like now?
I've drawn the arrows between the Domain and the Business Logic tiny to show that this is not Service Layer stuff. The Service Layer calls setters, but it has no idea that the Business Logic is there. Neither does the UI or, of course, the database.

Remember that XSD file? I said we could add other stuff to it. Well now is the time to tell you that we also added permission flags, readonly flags and so on. We can use these to make the UI smarter. We don't hard code that the orderTotal is readOnly in the UI. We declare it readOnly in the XSD file and have the UI figure it out from the resulting annotations. If the field is to only display to users with SUPERVISOR permission again we declare it in the XSD and have the UI suppress it if this user doesn't have that permission. This removes the need for lots of business logic decisions in the UI and reduces it to generic code.

We do cheat just a little in two places. First the Service Layer needs to handle exceptions generated by the Business Logic. It usually just passes the exception message to the UI because such exceptions are normally user input errors. That makes it not totally transparent, but still generic. The second cheat is that we add a little metadata API to the Domain Objects. Most of the time the field metadata, eg whether the field is readOnly or not is static or permission dependent, and we can hold that in annotations. But sometimes it is dependent on what else this user just entered. Where that happens the rules can manipulate the metadata which, in turn, can influence the UI. The UI still avoids actual business logic this way.

For example, the Customer has a flag that tells us he is a credit risk. That means we want the user to enter some more information than usual to complete the order, so some fields that were not visible before become visible. The UI doesn't have code referring to credit risks, but the rules do and they set the status of the field.

Of course the UI layer has to be smart enough to do this, but if it is smart enough to do business logic it ought to be smart enough to have this added.

There is an implementation of this available using Vaadin for the UI, including a demo.

Post a Comment