Friday, February 15th, 2008.

I got a question about how to stay away from CRUD based service interfaces when the logic itself is like that, and I’ve found that this shift in thinking really needs more examples, so I’ve decided to put this out there:



For instance, in an HR system, the process of interviewing candidates – wouldn’t you just insert, update, and delete these Appointment objects?

If I were to put on my domain-driven hat, I would describe those requirements differently – interview appointments have a lifecycle: proposed, accepted, cancelled, etc. It seems that only a user of the role HR Interviewer should be able to make appointments for themselves, so the service layer code would probably look something like this:





using (ISession session = SessionFactory.OpenSession()) using (ITransaction tx = session.BeginTransaction()) { ICandidateInterviewer interviewer = session.Get<ICandidateInterviewer>(message.InterviewerId); ICandidate candidate = session.Get<ICandidate>(message.CandidateId); interviewer.ScheduleInterviewWith(candidate).At(message.RequestedTime); tx.Commit(); }

The “ScheduleInterviewWith” method accepts an ICandidate and returns an IAppointment. IAppointment has a method “At” which accepts a DateTime parameter and returns void – just changes the data of the appointment. The state of the appointment at creation time would probably be proposed. The appointment object would probably be added to the list of appointments for that interviewer – that’s what will cause it to be persisted automatically.

Later, when the candidate accepts the meeting, we could have the following method on ICandidate – void Accept(IAppointment); that would obviously check that the candidate is the right person for that interview, the appointment’s current state (not cancelled), etc – finally updating its state. What part of this looks like create, update, delete? If that’s what your service layer to domain interaction looks like, do you now know what your messages will be looking like?CRUD seems to be what most of us are familiar with. Moving to domain-driven thinking takes time and practice, but is well worth it. Contrast this with a more traditional O/R mapping solution:

using (ISession session = SessionFactory.OpenSession()) using (ITransaction tx = session.BeginTransaction()) { ICandidateInterviewer interviewer = session.Get<ICandidateInterviewer>(message.InterviewerId); ICandidate candidate = session.Get<ICandidate>(message.CandidateId); Appointment a = new Appointment(); a.Interviewer = interviewer; interviewer.Appointments.Add(a); a.Candidate = candidate; candidate.Appointments.Add(a); a.Time = message.RequestedTime; session.Save(a); tx.Commit(); }

As you can see, we’ve got simpler, more expressive, and more testable code when employing the domain model pattern, than using “just” O/R mapping. I’m not saying that the domain model pattern doesn’t need O/R mapping in the background for it to work. But that’s just it – the persistence gunk needs to be in the background and the business logic needs to be encapsulated.

So, while I’ll agree with Dave that the Domain Model is more lifestyle than pattern, I would argue against these conclusions:

If this post had a point, it’s only to share the idea that Domain Model is a big, big thing. It’s probably overkill in a lot of cases where you have simple applications that have very simple purposes.

As you just saw in the example above, there is no “overkill” to be seen. The domain model in the example wasn’t “a big, big thing”.

The domain model. Use it.

Why not have a better lifestyle?