So what's all this about anemia?
There's a lot of talk going on these days about "anemic domain models" and "fat beans" (and I haven't been blogging much, let alone anything meaty, for far too long), so I figured I'd put up a post that discusses the pros, cons, ideas, and pitfalls of the various approaches here.
So, let's waste no time...
"Anemia" in my Domain
So what is this anemic domain model? I'm going to give it to you from my perspective and mine alone, so Mr. Fowler doesn't have anyone reading my blog start blaming him for things. The anemic domain model is an "anti-pattern" (Wikipedia: In computer science, anti-patterns are problems that occur frequently in computer programming and that programmers following good practice tend to avoid.) that has rules like:
- Beans should just be data-bearing collections of get/set methods
- All business logic belongs in monolithic Service singletons
In short, the anemic domain model is how a whole host of ColdFusion "Object Oriented" applications are being built these days. Interestingly, according to Fowler, these are emphatically NOT OO applications and instead are collections of Transaction Scripts and have little to nothing to do with OO in any way.
Before you get defensive, keep in mind that this only has value if you view Fowler et al as doctrine. I think it's impossible argue that they're wrong in this, but whether you're writing your applications as well-organized procedural Transaction Scripts or if you're actually putting logic in your beans, it's still better than the old page-model applications (again, unless this is best for your app, which sometimes it is). What name we give a particular design pattern is far less relevant than whether or not we use a design pattern at all or how well we do at organizing things in general.
Fat Beans
At one point I argued with someone that this.validate() isn't a method you should see on a bean. Their argument was that a bean should at least know what it's own basic composition should look like and have the ability to say "Yes I am valid" to any object that expects to use it. I argued that a bean shouldn't get to the point that it's contents are invalid because it's setters should validate it's contents at runtime and that, if any further validation is required, it should be done outside the object. As it turns out, we're both right, sort of.
A "bean" is (classically speaking) is something that actually has rules associated with it's use, construction, shape and function. There's actually a specification from Sun that covers what a bean is, what it should be able to do, and how it should be able to work in order to be considered valid. Some of the things that beans must be able to do in order to be considered "valid beans" are:
- Have a zero-argument constructor (i.e. the "constructor" or init() method shouldn't accept any arguments)
- Persist (i.e. save to disk, DB, etc., in a permanent location) their contents and restore changes to their state (i.e. any changes that have occurred since instantiation)
- Use events to communicate with other beans thru a subscription process (which means that any bean can be a listener or a broadcaster
- Maintain properties thru getters and setters
What this means is that Services can be beans, however this also means that Business Objects (a fancy name for beans) should, by the spec, have some degree of intelligence. Their setters should validate and their getters should calculate, and they need to have access to permanent storage of their state. In CF, this amounts to needing to include DB or file system access, having a mechanism to broadcast and receive events, and other specialized actions that, for the most part, I don't see people (other than Peter Bell, who's taken it to a bit of an extreme in some ways and missing the boat in others (though don't we all in some way or other)) doing this at all. I'm not doing it either, because until I sat down to write this I hadn't ever looked at the JavaBean spec.
Skinny Services
According to Fowler and the JavaBean spec, taken together, a proper "Domain Model" must put a lot of the functionality we CFers typically reserve for services into our beans. This means that, in a system with a DB backend, our User beans should be able to retrieve state from the DB based on any given number of arguments, and be able to save it's state back to the DB. This means that we should be using an event model to trigger actions in one part of the application via events in another part of the application. It means that we need to make our systems at once simpler and more complicated.
The Service Layer in an application based on this paradigm is very thin, and only reflects state relevant to the completion of tasks. No business-related state is kept in the Service layer, leaving the mainstay of the work to be done by the objects themselves. It's an interesting idea. The management of singletons, for instance, belongs in the service layer. Access to off-board systems, like webservice calls, might be found in the service layer. In general, only activities that have a wide application belong in the Service while anything specific to an entity in the Universe of Dicourse (UoD, aka Problem Domain, aka Area of Discourse, aka "business rules"). It's also sensible, from time to time, to have a procedural layer on top of your domain model, but in that case the Service still depends on the functionality encased within each type to do the work that must be done.
If, for instance, you want to read from a webservice, set the data into a bean, and save it, you might have a service-layer object that has this sort of a call: application.Service.readAndSaveFromWs("ws_name","getentry"). This would have the Service object making a call to the webservice, creating an Entry object (probably via a factory) and then calling Entry.save() to persist the state of the entry to the DB. This requires that the Entry business object be capable of saving itself, validating any data passed to calls to its setters, and the Service being the only object that has an instance of the webservice itself available.
Note in that case that the Entry is the one doing the work of saving itself.
ORM, OO and Me
I've contemplated many of the implications of this kind of design... some of the problems I've considered with it are:
- Beans take up more space in memory
- Beans are more complicated
- There's a higher likelihood of redundant code across your model
- Design is more demanding and more of the business must be understood (it almost mandates BDUF)
Having your objects generated by an ORM tool means they can do many of the things mandated by the JavaBean spec, but it also means fatter beans, taking up more memory in an application, and all sorts of functionality going on behind the scenes that I may or may not want to be happening. Reactor, Transfer, Hibernate all use this sort of process and, while not bad, it's also nothing like knowing how this works and what should be happening behind the scenes yourself. Like I say about Frameworks, if you don't know how to buld a solid app without one you may want to learn that before you start using one. And so I put my mind to thinking about these things.
It occurs to me: just because your beans know how to save their own state to a persistent location doesn't mean that they have to know everything about HOW that's done. You could have a persistence-layer interface class that knows how to save many object's state, and retrieve many object's state, composed into your beans. Your bean would then say "yeah, I know how to do that, I ask him to get me some data!" This means having a Factory and a Storage class composed into the beans. This might mean having a system like Clarion in place that handles event-management. But it doesn't mean that we have to put all the code for everything right in the bean... it means using intelligent design to accomplish as much as possible as elegantly as possible.
In summation...
We've done a great deal to improve the state of software architecture and engineering in the ColdFusion community, but we're nowhere near perfection, and we've got a long, long way to go. This means many more arguments, disagreements, discussions and conferences. It means that some people have to start doing things differently and evolve further into the Enterprise Patterns world in order to see how this all plays out for CFers, how the server platform handles these advances, and what happens in the long run as we switch into maintenance mode with some of these applications. It will be very, very interesting to watch, and I look forward to being a part of it all!
And so, my New Year Resolution, is to try writing at least one application using this interesting approach and see how it goes... because, at the very least, the time is worth the education.
Laterz!!
Comments
Dude - great posting. I'm currently busy working up a bunch of different posts referencing this as I think it raises a bunch of issues that should be discussed in the CF world.
I disagree with a number of the statements, but love the thinking and am really glad you've opened this door. Hopefully it'll start an interesting debate!
Posted By Peter Bell / Posted At 12/28/06 10:08 PM
Hmm, like Peter I don't agree with a number of your statements but the thinking is reasonable. Yeah, the thin bean / fat service model is exactly what the anemic domain model anti-pattern is all about - and it's something that should be avoided (I've blogged about this in the past).
One thing I really do want to pull you up on is this: "fatter beans, taking up more memory in an application". The bean really will be the same size - it has the same data. The functions exist just once so it doesn't matter whether they're in the beans or the service objects.
Rule of thumb: functions belong where the data they act on exists. If you have a function full of getThis() and getThat() method calls, it probably belongs *inside* the object, not *outside* it.
I'm going to stick my neck out: download the Fusebox 5 / 5.1 core files and have a look at what a "fat beans" application looks like. The "service layer" is a single CFML file: fusebox5.cfm. Yes, there are getters and setters but there's a lot of business logic in the beans.
Posted By Sean Corfield / Posted At 12/29/06 12:34 AM
Fair enough, Sean... as far as the methods/memory comment are concerned. I meant to indicate that these were musings I've often had, not that any of them were true or valid. I'd be interested to see if a get/set only bean and a Reactor bean are much different in size in RAM and if that difference is enough to cause any issues over the life of the application.
What you say makes sense, but the scientist in me can't help but wish to measure it too. ;)
J
Posted By Jared Rypka-Hauer / Posted At 12/29/06 1:20 AM
Jared, This isn't meant as a criticism, but I think you are comfortable with this idea (as I am) because in the end it is procedural code in OO dress (I actually had a post recently where I discussed how I fall into this trap too often myself). Think about it, your beans are intended to model some "real world" object, and this real world object is not purely a collection of data is it? Then why use a bean at all, why not make a structure and send the struct to a validate method? Ah, but this wouldn't look OO enough but would effectively be the same wouldn't it?
I mean, to use a dumb analogy based on the usual example, your "employee" bean models real world employees right? So, man, what a sorry ass company would we have filled with employees who can tell me their name, but can't do much else..."Uh, sorry, you need to call the service department to do anything." Anyway, I need to figure a lot of this out for myself too (and I may not really follow the spec), but I do know the fat bean model is the better way to go.
Posted By Brian Rinaldi / Posted At 12/29/06 11:25 AM
I'm not saying that a dumb bean and a Reactor bean are the same size!
I'm saying that if you build an app with dumb beans and a fat service layer, the overall amount of memory taken by the data and functions is going to be *roughly* equivalent to an app with fat beans and a thin service layer - because all you're doing is moving the functions to a different place (and functions only exist once, they are not duplicated into every object). And, in fact, if you have the functions in the right place, you might actually use *less* memory because you no longer have all those getter / setter calls in the service layer - instead you have just one call to a bean to "do something".
Make sense?
The amount of raw data in your employee bean (name, department etc) is the same whether you have a fat bean or a dumb bean.
Posted By Sean Corfield / Posted At 12/29/06 1:25 PM
Nice post...
When I last read the JavaBean spec from Sun I thought that is said that "beans" have to implement the Serializable interface not actually be Serializable to a database right inside of them. In the java world Serializable mainly serves as a marker interface to save beans to disk temporarily. This really doesn't affect your point very much but I wonder if I missed something in the spec or if it was updated.
Posted By Kurt Wiersma / Posted At 12/29/06 2:20 PM
Good discussion, Jared. A few thoughts:
(1) In my mind the service layer exists to provide a simplified API to the client, which could be a controller in a MG app or even a Flex front-end. It saves the client from worrying about all the method calls equired when you have lots of domain model objects floating around. It's also a useful place to do things like transactions, security, logging, etc. Given that, the methods in the service layer usually map very well to application use cases, say perhaps savePersonelRecord(...) or scheduleAppointment(...). These will involve interactions with the domain objects and data access layer, and other "integration components" like e-mail, task scheduling, invoking other remote services, etc. The problem is that if involved business logic only lives in the service layer, the methods become very bloated. They start to do a lot, probably too much. And reuse becomes more difficult. You have to break these methods up into very small methods that do atomic tasks and make some of them public (the "use-case" methods) and probably make the rest private. Your service layer class becomes enormous. I think what makes sense is to look at all those private methods and say, "can't these be migrated into the domain objects themselves?" This keeps the behavior and state close to one-another, that is the fundamental aim of object-oriented programming. That's what the rich domain model is all about.
(2) Fat objects don't mean that code is being duplicated; quite the opposite. Take the classic validation issue. If you have methods that validate things like phone numbers and zip codes, just create a "ValidationUtils" class that contains the logic for the validation, then just call ValidationUtils.checkWhatever() in your bean. You still achieve code reuse but have less validation plumbing in your service layer.
(3) I don't think a bean should ever have a this.validate() method. I also don't think you should do validation in the setters either. :) What these both imply is that your object can only ever exist in one valid state. Either a property value is valid or it isn't; either the object is valid or it isn't. The simplest case where setter validation breaks is when Address.state is required for Address.country="USA" but not required when Address.country!="USA". You can't assume the setters are called in any order, so setter validation is risky. You can fix this with having a this.validate() method that's called after all the setters. However, what if your object has multiple valid states? This comes into play in any kind of app that has workflow. An Order object may have "shipDate" as a required field, but when is it required? Only after the Order has been shipped. Beforehand, the field can't be required because the Order hasn't shipped yet. You could work around it with composition and lots of smaller classes but then it's a lot more plumbing you have to mess around with. Fowler has a pattern called "Contextual Validation" that is brilliant; simply put, you don't just have a this.validate() method, you have this.validateForOrderPlacement() or this.validateForShipmentProcessing(). Each validation is basically tied to an action that will alter the state of your object in some way. It's a minor distinction perhaps but an important one.
Sorry for the lengthy reply.
-Cliff
Posted By Cliff Meyers / Posted At 12/29/06 10:12 PM
Cliff,
Good points... especially about the Contextual Validation pattern.
About item 1... I think that's what's meant by it not being a bad idea to have a procedural layer on top of your object model, basically tying your OO Domain Model into "bundles"... calling the model components in sequence in a procedural way.
Anyway, thanks for the input and the long comment is no big deal. :)
J
Posted By Jared Rypka-Hauer / Posted At 12/29/06 10:17 PM
I just wanted to point out that an interesting little tid-bit that people have been saying...
Thin Bean / Fat Services = Procedural App in OO clothing
I did want to mention that HTTP is a very procedural process and definitely not OO! So at least to not having a bit of an anemic domain model is OK for web applications considering the transport method. At least being anemic is better than having spaghetti code IMO and at least a step in the right direction. Whilst it's "nice" to build to the "spec" (Fowler, etc.) - my job is to build an application that works and deliver in a reasonable amount of time (and maintain in the future).
Cliff did bring up some good points using his validation method example. I have several applications where validation is different depending on the type of the user or specific process. Sometimes a general validation works for across the boards, sometimes you have to create more specialized versions.
Posted By Peter J. Farrell / Posted At 12/30/06 10:57 PM
I understand your point, but IMO the transport protocol has diddly to do with the application architecture on the server. I mean the converse of your point is that Mach-II is OO, so why would you want to build a procedural app in an OO framework that runs over a procedural protocol? Whether the client/server connection maintains state or not, from the server's perspective the application architecture is a completely separate issue. That's why J2EE can service any number of different kinds of clients, only one of which is the web browser. If you're servicing Flash/Flex clients, you certainly aren't using HTTP for most remoting calls and the same set of principles apply... so I'd be careful saying that web applications can be anemic, we can still call them OO and don't have to be mindful of the difference between a good OO model and an anemic one.
Also keep in mind that my skill, or your skill, as an OO modeler doesn't really change the nature of the discussion. When you're considering whether or not a fat service/thin bean app is OO or Anemic, you have to look at it from an objective perspective. When you have a deadline and a certain base of experience upon which you can draw to meet it, whether or not some app is OO or Anemic is also irrelevant.
I totally agree, though, that an organized app is better than a disorganized one, so whether you're using Fusebox, Mach-II, MG or your own approach, if you do a well-structured fat-service/thin-bean app it's better than no organization at all.
Posted By Jared Rypka-Hauer / Posted At 1/1/07 8:23 PM
Following on from Jared's points - SOA faces this argument. SOA is essentially about a bunch of procedural remote method calls to black box systems. Yet the systems themselves can be OO internally. Much the same applies to HTTP based systems (and Flash Remoting *is* HTTP based). The external request/response protocol is procedural in nature but there's no reason why the system handling the request has to be procedural.
Fusebox is a great example of this. Up to Fusebox 4.1, the core files have been purely procedural yet you can write OO web apps with Fusebox. Now with Fusebox 5 onwards, the core files are purely OO yet you can still write procedural apps if you want. The two are orthogonal - in the same way that OO systems are orthogonal to procedural transport protocols.
Posted By Sean Corfield / Posted At 1/1/07 10:49 PM
My bad... flash remoting uses HTTP. D'oh. What was I thinking (or maybe I wasn't thinking at all)?
That doesn't change the fact that the internals of a system are irrelevant to any other system provided that a reliable transport can be erected between them and through which they can speak clearly to one another. That point of contact and the associated mechanics and semantics are the only commonalities between them, thus negating all but questions of their performance and reliability, which are things that could cause the whole system to fail.
I've been reading a biography of Ben Franklin lately... can you tell? Heh.
Posted By Jared Rypka-Hauer / Posted At 1/1/07 11:16 PM
Nice site.
Look here:
<a href= http://buyasoma.com/spybot/map.html >spybot</a> [url=http://buyasoma.com/spybot/map.html]spybot[/url] <a href= http://xanaxtramadol.com/sildenafil/map.html >sildenafil</a> [url=http://xanaxtramadol.com/sildenafil/map.html]sildenafil[/url] <a href= http://buyasoma.com/cookie-diet/map.html >cookie diet</a> [url=http://buyasoma.com/cookie-diet/map.html]cookie diet[/url] <a href= http://xanaxtramadol.com/buy-alli/map.html >buy alli</a> [url=http://xanaxtramadol.com/buy-alli/map.html]buy alli[/url] <a href= http://buyasoma.com/body/map.html >body</a> [url=http://buyasoma.com/body/map.html]body[/url] <a href= http://buyasoma.com/digital-asset-management/map.html >digital asset management</a> [url=http://buyasoma.com/digital-asset-management/map.html]digital asset management[/url] <a href= http://buyasoma.com/oyunlar/map.html >oyunlar</a> [url=http://buyasoma.com/oyunlar/map.html]oyunlar[/url]
Posted By tpy_gnsjd / Posted At 10/2/09 5:49 PM
U3fKu6 <a href="http://czzjcbhftikg.com/">czzjcbhftikg</a>, [url=http://luomjmscusrb.com/]luomjmscusrb[/url], [link=http://lrnvgrkahdds.com/]lrnvgrkahdds[/link], http://ezmvbugmmgzu.com/
Posted By szacgtz / Posted At 10/3/09 5:01 AM
Makiā¦as always, great post here.
Posted By googlefuck.com / Posted At 10/5/09 5:17 AM
? ?, ????????, ?????? ????? ???? ????!
Posted By googlefakel / Posted At 10/8/09 5:14 AM
Nice site.
Look here:
<a href= <a href="http://buyapropecia.com/ikariam/map.html">ikariam</a>
></a> [url=<a href="http://buyapropecia.com/ikariam/map.html">ikariam</a>
][/url] <a href= <a href="http://buyapropecia.com/discount-travel/map.html">discount travel</a>
></a> [url=<a href="http://buyapropecia.com/discount-travel/map.html">discount travel</a>
][/url] <a href= <a href="http://buyapropecia.com/avg/map.html">avg</a>
></a> [url=<a href="http://buyapropecia.com/avg/map.html">avg</a>
][/url] <a href= <a href="http://buyapropecia.com/oyun/map.html">oyun</a>
></a> [url=<a href="http://buyapropecia.com/oyun/map.html">oyun</a>
][/url] <a href= <a href="http://buyapropecia.com/casino/map.html">casino</a>
></a> [url=<a href="http://buyapropecia.com/casino/map.html">casino</a>
][/url] <a href= <a href="http://buyapropecia.com/juegos/map.html">juegos</a>
></a> [url=<a href="http://buyapropecia.com/juegos/map.html">juegos</a>
][/url] <a href= <a href="http://buyapropecia.com/phim/map.html">phim</a>
></a> [url=<a href="http://buyapropecia.com/phim/map.html">phim</a>
][/url]
Posted By wwc_pxihg / Posted At 10/9/09 6:10 PM
Thank you for this valuable post. It changed my Thank you for this valuable post. It changed my policy
Posted By ofigennoe.ru / Posted At 10/11/09 11:24 AM