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).

Published Monday, October 11, 2004 10:14 AM by Bil Simser

Comments

# re: The dumbness of a Domain Object

Monday, October 11, 2004 3:52 PM by Yves Reynhout
You need to read some books on domain modelling, especially when it comes to layering, what to put where ...
http://domaindrivendesign.org/
http://www.martinfowler.com/books.html#eaa
(Given that you're an agile follower, you will find yourself right at home).

The fact that tracks and albums are related means that you can either load the entire graph or lazy load the tracks. That's a design decision.

Assemblers are for putting together DTOs based on domain data, generally found in the service layer. Mappers (or DataMappers) are for mapping relational data to domain objects, generally found in the data layer.

But really, the domain of music, and particularly of music albums, begs the questions:
- "Am I really gonna take a performance 'blow' loading an album and 10 to 20 tracks?"
I don't think so, the execution time will be negligable (compared to network latency and the like).
- "Am I really gonna take a performance 'hit'
serializing an album and 10 to 20 tracks to and from xml to send it to or receive from a service?"
Again, I don't think so.

I could go on, but you get the point. I think you should strive for the right granularity for your service layer methods (that's something only you will be able to determine).

Also, your tool will have a lousy user experience if you insist on putting ALL validation logic on the server side. Tradeoff, tradeoff, tradeoff.

On the issue of IDs. If your service api is for "internal" usage only, go ahead and use internal IDs over the wire to correlate identification in the presentation, service and domain layer. For external apis you could use URI-s or something else that works for you. You'll always need something to identify the entities (it's fundamental). Object identity is not tied to the data layer. The fact that a DTO is a slimmed version of a domain object, doesn't mean it doesn't represent the "real" object (hence it has identity). Don't get too caught up in the whole service, DTO, and domain object stuff. These are technical things; remember that they only exist because of the user story. Spike to find what works for you.

Sorry for the randomness of my thoughts... Hope I haven't offended you in any way.

# re: The dumbness of a Domain Object

Monday, October 11, 2004 4:22 PM by Bil Simser
Thanks for the feedback Yves, great stuff. I don't think my blog was completely clear on what I where I was trying to get to (and probably never get there as I could have a 10-hour discussion on this).

Both book references are one of my staples on the shelf. It's just that when you get to the concrete implementation rather than the theoretical ideas around something, it's when you start questioning things (at least it is for me).

You do raise some interesting concepts, especially around the performance hits. In the case of a music domain, sure, an album only has 10-20 tracks (with a few exceptions like double and triple albums). However as this was only an example and trying to show a parent-chilld type situation, the architecture might shift if there were hundreds of children to a parent (or mutiple-parents). The lazy load is a good idea but then you get nailed with various trips across the tiers.

As with any software design, there are so many decision points (with no right answer but many correct ones) and very few hard and fast rules.

# re: The dumbness of a Domain Object

Tuesday, October 12, 2004 3:54 AM by Yves Reynhout
If there are hundreds of children, changes are that your end user will never be able to weed through such vast amounts of data. It's generally better to offer search functionality and a more "paged" approach. If you're going to use "soap"-based web services, you can use a custom header to pass the page number and the page size. This way you avoid interface polution (e.g. having a regular method and a paged method).

# re: The dumbness of a Domain Object

Tuesday, October 12, 2004 4:26 AM by Yves Reynhout
I meant "chances" not "changes".