Home > Domain Driven Design, Domain Events > Preventing anemic domain model – where is my model behaviour?

Preventing anemic domain model – where is my model behaviour?

This post is part of a series of DDD post and the purpose is to share my experiences in Domain Driven design and how well it has performed in the last two projects. I will try to stay objective and using this blog for documentation of knowledge but also a retrospective for me and hopefully some feedback. Going back and analyzing design decisions and writing it down for an unknown crowd forces you to really think and explain what we did. Unbelievable powerful.

Background

First post will be about how I enriched my model and prevented it from being a dead model – no business behavior. A.k.a anemic model.

Like many other that dive into DDD I started up with focus on the model. No database design and strictly POCO classes (C#).

After a few workshops and requirements gathering and some old requirement documents I had gather knowledge to start thinking about model entities and value objects. The design decisions we setup were:

  • We should use an application layer as a anti-corruption layer and also for having a layer that could coordinate transactions (unit of work) that spans over several aggregate roots and repositories. But also for making it easy to interact with external systems and infrastructure related things like word document creation, emails etc.
  • A clean Domain layer with no references other projects or components
  • Infrastructure layer that handles database, emails, excel imports/exports etc. (Fluent Nhibernate)
  • Presentation layer with Model-View-Presenter pattern (ASP.Net)

Okay… so far everybody happy and good weather with clear sky.

…dark clouds

After a while when we have introduced about 20 entities, 10 repositories, 5 services and 15 web form dialogs. Now things started to get dark and cloudy. I got this feeling that this is not going to be okay. Logic slippering out from model out to application service layer and some times all the way into Presenter classes.

Why?

Well, We could find a good places to inject lookup- or search methods in some entities. We had some calculation executed by application service layer. For example we have a questionnaire with questions and answer alternatives. This questionnaire combined with input data resulted in a evaluation. The evaluation could also have weights on each question in questionnaire for tuning the evaluation score. The final evaluation result is a score. What happened was that the knowledge of  how to calculate the score and knowledge of what parts that needs to participate in calculation slipped out to the evaluation service. The service had all necessary repositories already injected so it “was so easy” to just load everything we need and do the calculation in service method.

That was ONE example but we had several examples where we just did some coding and swoosh!, we put some very important DDD logic in service layer without notice it. Well we did notice it and discussed it during Daily Scrum meetings, but no action was taken since we were short on time.  Most important was that I had no good refactoring solution yet.

So I did a Timeout… You know, calling the judges, do the T sign with your both hands. Maybe mumbling the F-word as well…

What we had at the time for the timeout were these dark clouds

- A LOT of bi-directional relationship between entities. Mostly because Nhibernate wants you specify relationships on both ends (there are other ways to do POCO class relationships, yes I know).

- I thought my model contained to few Value Objects.

- Logic in application layer that should be in model layer.

- When you have almost no value objects.

- Too many aggregate roots where all of them do not contains that many classes.

- Too many repositories which is a consequence of the above statement.

- Each entity has very little logic and behavior. It most contains child/relationship collection management and auto properties.

- Sometime complex entity relationships traversals. I felt that I had to go along way for, up and down in object graphs for getting the entities I needed.

So as you can see above it was dark indeed and raining (read. tears :) )  is near.

Here is an entity code example (C#):

public class WorkEvaluation : DomainBase, IAggregateRoot
{
private IList _questionWeights = new List();

public WorkEvaluation(SalaryReview salaryReview, WorkEvaluationTemplate template)
{
Check.Require(salaryReview != null, "SalaryReview cannot be null");
Check.Require(template != null, "WorkEvaluationTemplate cannot be null");

WorkEvaluationTemplate = template;
template.AddWorkEvaluation(this);

SalaryReview = salaryReview;
salaryReview.BeginWorkEvaluation(this);
}

public IList QuestionWeights
{
get { return new List(_questionWeights).AsReadOnly(); }
protected set { _questionWeights = value; }
}

public long HitInterval { get; set; }
}

The class above is stripped of non important code and left is the problem with just auto properties and exposed collections. Actually the HitInterval and QuestionWeights collection are both needed for being able to a result score calculation. But as you can see the entity can be in a invalid state if we have weights but no hit interval. Or Hit interval and no weights. Not good.
The usage of the model with this design:
1. Our evaluataion service in application layer loads WorkEvaluation entity
2. Input from UI dialog through evaluation service method is a list of question weights and hit interval. We set them to entity and persist entity.
3. Same service method then do calculation which is a loop over questions and their answers and also loop over question weights. With that info calculation is done through a algorithm.
Its the evaluation service who has the knowledge of doing result score calculation.

Umbrella?

I had to redraw the model on large A3 paper where each entity/Value object on a post-it. It gives you a good flexibility in changing entities and relationships back and forth. After doing some extensive research throw stackoverflow and other communities I began to see some blue sky at the horizon.

I listed some actions I needed to introduce for making this model more “alive” and not just another information transportation layer.

First.
Try to bombard the model with actual use-cases. Se how the model fits to your clients needs. If you need to traverse around entity relationships a lot, maybe you haven’t model the reality domain. Can you make the model easier to use without losing its capability to reflect the business domain? The goal is to let your use cases develop the model on the fly.

Second.
Try to find as many Value Objects as possible. Make it a competition. The one that finds most VO’s wins. Why? Well, I noticed that Value Objects has some very good Pros. They make your Entity public interface more easy to understand. Correctly used, they can make your entities go less into invalid state. They can give you an extremely good spot to place business logic with even more common sense context (I’ll show you below). They also have a tendency to create better and more natural aggregate roots which reduces object graphs traversal

Third.
Introducing Domain Events to prevent domain behavior/logic slippering out to application layer.

Clear Sky and happy smiling faces?

I introduced these aspects and I think I came away from the dark clouds but still more can be done.
Below is an example of the class above, but not re-factored a little bit.


public class WorkEvaluation : DomainBase, IAggregateRoot
{
private IList _questionWeights = new List();

public QuestionaireScoreTuning QuestionaireScoreTuning { get; protected set; }

///

/// returns a Questionaire object that provide access to Questions, answeralternatives, QuestionCategories etc.
///

public Questionaire Questionaire
{
get { return new Questionaire(_template); }
}

public void ApplyTuning(QuestionaireScoreTuning tuning)
{
QuestionaireScoreTuning = tuning;

var @event = new QuestionScoreTuningAppliedEvent(this);
// Raise event for recalculate all group scores
DomainEvents.Raise(@event);
}
}

As you can see, Hit interval and Get property for QuestionWeights are gone. They are merged into a QuestionaireScoreTuning Value Object (see below for code)
that must be applied on this evaluation for make score tuning. Once this object is applied it also fires a domain event that calculate result score for each user and how they have answered the questions. The initiative is now in the model and not in service layer. The EventHandler is located in Service layer and will use repositories, this evaluation and tuning parameters for calculating each user score. BUT the algorithm is located in QuestionaireScoreTuning Value object and not in service layer. When we have calculated the user score we’ll persist that score through eventhandler and user repository.


public class QuestionaireScoreTuning : ValueObject
{
private readonly IList _questionWeights;

public QuestionaireScoreTuning(IList listOfWeights, long hitInterval)
{
_questionWeights = listOfWeights;
HitInterval = hitInterval;
}

public long HitInterval { get; protected set; }

public void CalculateUserScore(User @user)
{
Check.Require(@user != null, "user cannot be null");

var userAnswers = @user.Answers();
double totalScore = 0;

foreach (var userAnswer in userAnswers)
{
// Get tuning answer for this user
var questionWeightForThisAnswer = FindQuestionWeightByQuestionId(userAnswer.BelongsToQuestion.ID);

totalScore += userAnswer.AnswerAlternative.Score * questionWeightForThisAnswer.Weight.Value;
}
user.TotalScore = UserScore.CreateFrom(totalScore);
}

public IList QuestionWeights
{
get { return new List(_questionWeights); }
}

public static List GetDefaultHitIntervals()
{
return new List { 2, 3, 4, 5, 6, 7, 8, 9, 10 };
}

public QuestionWeight FindQuestionWeightByQuestionId(long questionId)
{
return _questionWeights.FirstOrDefault(q => q.Question.ID.Equals(questionId));
}
}

We also could squeeze in some search Find methods (FindQuestionWeightByQuestionId) that now feels better in right context. The same for GetDefaultHitIntervals (hard coded.. hmmm?). As you notice the criteria for a value object is that it should be immutable and once its created it cannot change state. Thats suits us like a glove. When we have weights for our questions (all questions must have a weight) and hit interval we have a “tuning” (QuestionaireScoreTuning). If we want to modify the tuning we create a newQuestionaireScoreTuning VO and apply that to evaluation entity and all user get their scores recalculated.

Summary
The evaluation entity interface is less cluttered and more straight forward and intuitive. The entity has behavior.
More example may be posted later on. I will try to post a example project that includes all this.
/Cheers

  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.