Intro from 2018: I wrote this article in 2009 and it’s been sitting in my drafts ever since. But I was inspired to look it up again by https://dzone.com/articles/the-secret-life-of-objects-information-hiding, and negatively inspired by https://medium.com/@cscalfani/goodbye-object-oriented-programming-a59cda4c0e53. So here it is all these years later.
(Prompted by a discussion with Ben Nadel)
There’s a bit of a debate in the CF OO community. OO is good. OK, what’s it good for? You can have an OO domain model to capture all your business logic. What business logic? All I’m doing is inserting and updating records. Etc.
Then there’s all discussion about the “anemic domain model” antipattern. I want to make my beans less anemic, but I just can’t find anything to put in them!
Maybe domain models are only useful for sophisticated, simulation-based apps? CRUD apps don’t have enough business logic. Right?
Maybe not so right. My CRUD apps have lots of business logic. If I trawl through my database schema and pull out all of the constraints, defaults, foreign keys etc, that adds up to a lot of business logic. If I went the whole hog and added triggers to enforce all the more complex invariants, I would have a complex, rich domain model implemented in my database schema. And that’s without any of the personified simulation-style objects that we think of as being the sweet spot for complex domain models.
Some of the data modelling people insist that this is the only way to implement a domain model. Use database constructs for invariants, and put all the calculation logic into stored procedures. Maybe that’s the way to go for a pure CRUD application. The database will throw an exception if I violate any constraint, so my CRUD application just needs to catch those and react. However, any SQL database is such a miserable development environment that I really don’t want to lock myself into that scenario.
Let’s go to the other extreme and implement all of these invariants in our OO application. In practice we would duplicate some of the constraints in the schema, but we’ll say that our app doesn’t rely on that. In the OO world, we have a much richer programming model, so we should be able to go better than just throwing exceptions. We should be able to design our model so that many invalid operations simply aren’t available, and others return sensible defaults, nulls or result codes.
Here’s an example. I need to be able to create and update user records. My invariant is that usernames must be unique.
In a SQL domain model, I would put a uniqueness constraint on my username column. Any attempt to INSERT or UPDATE with an existing username would throw an exception. In theory this should be enough. In practice we tend to write application code to predict whether or not we are going to get an SQL exception. Not quite sure why we do this extra work, but the end result is the same.
In an OO domain model, I can constrain the available operations to make violation of the constraint impossible. First, I create a Users object that represents the set of all users. Then I make the constructor for the User object private. I can’t actually create a new user. If I want a new user, I have to ask the Users object for it.
// me = new User("jmetcher") <--- operation does not exist!! me = Users.create("jmetcher");
This gives the Users object a chance to enforce the invariant. If there is already a user with username “jmetcher”, it can return that object, or return a null object, or return false, or even throw an exception. Probably I’d return the existing object. So that takes care of the INSERT.
What about the UPDATE? I require that the User object does not have a setter for “username”. Username is part of the logical identity of the User object, so it must be immutable. I may provide a utility method (say, on Users) to change a username, but that will be a maintenance activity – low-level, stop the world, reorganize my data kind of thing. It’s not part of the defined behaviour of a User.
me.setUsername("notjmetcher"); <--- operation does not exist!!
The domain model’s main purpose is to enforce those invariants. The lightbulb realization is that
A good domain model enforces invariants as much by its design as by its code
In this example, I’ve made my User object “richer” by hiding the constructor and taking away a setter – not by adding stuff.
There’s also a lot of discussion about validation. This cycle is taken for granted:
and then we talk a lot about where to put these responsibilities. My assertion is that we should be able to just:
Save should just be automatic. Save should be the default. You should do something extra if you don’t want to save. Like:
- manipulate the copy
But what happened to the “validate” step? I’ve got us automatically saving things that haven’t been validated! But see above – I’ve designed the domain model so that I can’t make invalid transformations. So:
A good domain model makes direct manipulation of the domain data a safe operation.
So, what’s a domain model for? A good domain model on top of a full-featured persistence layer will:
- Enforce invariants using a rich programming model
- Make manipulating your data safe – without you having to remember to validate before save, or copy before manipulate, or save before exit.
Manipulating data safely while obeying invariants sounds like bread-and-butter CRUD to me.
Footnote from 2018:
What Riccardo says in the first article I linked above is so clear, at least to me. How is it that Charles in the second article doesn’t get it? Maybe OOP is like all design thinking, like design patterns and agile methodologies. If you can’t tolerate living in a world of judgement calls, if you can’t code to a conceptual model instead of or as well as a spec, if you think a bunch of smart people making independent decisions sounds like chaos, it’s not for you. If you just want to know the rules, pick another door. These are paradigms to help you write the rules. Does anybody really think that is or can be easy?