The dumbness of a Domain Object
I've been mulling over some ideas for the past couple of days as I work on a project and came to a bit of a head with regards to the dumbness of a Domain Object. There are some base principles that I try to follow with application architecture:
- The data access layer only creates or accepts domain objects
- The service layer is responsible for creating or converting data transfer objects to real domain objects
- Only data transfer objects are sent between the service layer and user interface
So first off, maybe some of these principles are not correct. Hey, principles can always be changed (as long as you reapply them everywhere if they do change and adjust accordingly). I'll leave that to the reader to discuss.
Given these norms, there's a problem (or maybe a question). If the DAL only creates Domain Objects, then it needs to know how to assemble them. After all, it's retrieving data from a source and populating *something*. You could hide the creation in an Assembler or maybe a Factory but still, at some point some data needs to be set as an attribute in a Domain Object meaning that the DAL knows something about the contruction of a Domain Object. The other option is that everything gets assembled in the service layer. If this is the case then why do we even need collections in a Domain Object? They can be dumb and just hold properties that relate to themselves and not worry about things like collections of children.
So here's an example to try to make sense of what I'm talking about. Imagine you have a Media Center tool where users can view album information, list tracks on an album and update the track information. This gives us some simple objects like so (C# pseudo-code):
class Album
{
int AlbumId;
string Title;
string Artist;
}
class Track
{
int TrackId;
string Title;
int Length;
}
In order to know what Track belongs to what Album, I could have Domain Objects that look like this:
class Album
{
int AlbumId;
string Title;
string Artist;
ArrayList Tracks;
}
class Track
{
int TrackId;
string Title;
int Length;
}
Or maybe this:
class Album
{
int AlbumId;
string Title;
string Artist;
}
class Track
{
int TrackId;
int AlbumId;
string Title;
int Length;
}
In the case where I have an ArrayList of Tracks in my Album, I don't need the AlbumId to know which Track belongs where. However, if I have to create an Album object out of my DAL, I now know that I have to create Tracks as well. Am I not putting logic of structure of the application in my data layer now? In the last example, I can have some Assembler make calls to the data layer (1 for the album and 1 for the tracks) and put together an object myself. And if the only reason I'm doing this is so that I can show the user at the UI layer an album and it's related tracks, why does my Album Domain Object even need to know about Tracks?
This also brings up the question around using IDs everywhere. Remember we have a principle (right or wrong) that the UI only deals with DTOs and knows nothing about the domain or (gasp) the data access layer. End users don't need to know that "Like a Virgin" has an ID of 4532 in the database. However, if they update the title to a track on the album and want the tool to save that information, it means the UI needs to keep track of all this junk in order to send it back to the system for updating (there's no guarantee that two tracks won't have the same name, even on the same album). So going back to our example where I only want to update one single Track.Title property with a new name the user typed in, do I send back an entire AlbumDTO (with an Array of TrackDTOs). Probably not. I only want to update one track so I only send back one TrackDTO which leads me to the idea that:
- A TrackDTO must be able to exist on its own (for updating the backend from the UI)
- It needs to keep track of the Album it belongs to (otherwise how do we know what Album it's for when we update it)
Given this, why should a Domain Object (Album in this case) even bother to have a collection of children (Tracks). So all assembling should be done somewhere other than in the domain objects or data access layer? While this might not be the rule for all Domain Objects, I thought it was something that didn't seem to have a very clear answer (or maybe there is but I'm just missing it).