Someone asked me the other day if putting Load(), Save(), and Delete() methods in entity objects is a bad thing. While I tried my best to explain why I don’t put these methods on entities, I thought it was worthy of a blog post.
I am a .NET developer, so I’m going to answer the question with how I would do it in .NET. If you’re working in a dynamic language like Ruby, things are certainly different and you don’t have all of the limitations that you have in .NET. So what I’m about to say really only applies to the static-typed languages (.NET, Java, C++, etc.).
First of all, I am writing unit tests, which means that I’m using concepts like dependency injection. Someone who doesn’t write unit tests or use dependency injection might not see value in structuring their code the way I structure it. But the arguments here only partially have to do with testability.
Let’s say that I have these entities in my application — Product, State, Country, Customer, Order. In this list I have some things that change often (Customer, Order) and some things that won’t change often (Product, State, Country). So let’s say that I decide that I’m going to cache the entities that don’t change often.
Let’s think of this in terms of loading, saving, and deleting. When I load them, I’m going to check the cache first. When I save them, I’m going to save the object to the cache as well as the database. When I delete them, I’m going to remove them from the cache. I’m going to change how these types of entities are loaded, saved, and deleted.
Now let’s imagine that my entities had methods like Load(), Save(), and Delete(). I would have to implement the special cached loading, saving, and deleting in three places – Product, State, and Country (and whatever other cacheable entities that I have). Any time you have to copy and paste code you should start to wonder if something is wrong.
In this case, I might deduce that the way to solve the duplication would be to split out the code that loads, saves, and deletes cacheable entities into some other class that is outside of the entity class. These classes would be powerful because they aren’t just acting an individual entity type, they are acting on certain kinds of entities. Maybe you specify that an entity is cacheable by having it implement and interface or maybe you decorate it with an attribute.
Now when you have another cacheable entity, all you have to do is implement the right interface or put an attribute on the entity class and it will automatically work with the cache!
This is a good example of the difference between object-oriented code and procedural code. We want to do more object-oriented programming and less procedural programming because object-oriented code is more testable, flexible, and reusable.
There are also testability reasons for not putting Load(), Save(), and Delete() on entity objects. If you don’t understand dependency injection, you should go read about it first so that this makes more sense.
When using dependency injection, you take in dependent classes through the constructor, but you don’t do this with entity objects because entity objects do not have dependencies. Entity objects define what the model is, and other classes (“domain services”) define how the entities work together.
Earlier in the post I talked about how I would create a class that knows how to save cacheable entities. If I had a Save() method on my entity class, I would need to call this other class (a dependency) from my entity’s Save() method. But remember, entities don’t take in dependencies, and I don’t want to new up the class that does the saving because now I can’t mock that class in a unit test.
You might be reading this and it might not make a whole lot of sense, but that’s OK. This is much different from how some people have written code for a long time, and they don’t teach you this type of stuff in school. But it’s always a good idea to question why things are done certain ways and maybe you’ll learn something new that can really help you out.
Jon,
What about if you were using dependency injection to pass in an interface that defines the saving and loading methods (e.g. ICustomerData) and your domain object’s Save()/Load()/Delete() methods are defined in an abstract class (from which the entity object inherits – sae BaseDomainObject) that uses a delegate (with a signature like SaveMethod(IDomainObject) from the interface passed in to the constructor of the entity object to perform the operation within the appropriate method.
This way the entity object does technically have Save()/Load()/Delete() methods, as they are defined in the abstract class it inherits from, but the inheriting entity object does not contain any of the details of the saving and loading of object and the implementation of the data operations can be changed by passing in another version of ICustomerData, thereby allowing the use of mocking.
I think the approach I have described is similar, but reading your post made me think again about what I am doing which is never a bad thing!