Sunday, 14 September 2014

Rich Domain Models and Entity Framework

Previous posts in this series

Working with a Rich Domain Model and Entity Framework

As described in previous posts in this series, I'm documenting a few changes to the way I've recently been building out ASP.Net MVC applications, following various influencers from blogs and books. One area I was keen to look at was creating a rich domain model - inspired by the Eric Evans classic book and some posts by Julie Lerman and Jimmy Bogard.

Previously I would have worked more with what is known as an anaemic domain model - which isn't quite as bad as it sounds... but means that when creating entities for use in Entity Framework Code First, they would in the most part be made of a simple set of properties with public getters and setters. With a rich domain model, we look to push more behaviour into these model classes and provide tighter means of control over their use by calling code from the rest of the application.

There are a number of means to do this - in this blog post I describe a number I've used.

Control via constructors

One first method is to remove the parameterless constructor that you might otherwise have, thus preventing calling code from simply newing up an instance of the class, without necessarily setting some required properties. In fact you can't completely remove it - EF requires it - but you can make it private.

Once that's in place you can create one or more constructors that will enforce the calling code to provide whatever properties are deemed required and thus not create what might by the terms of the application be deemed an invalid object. Here's an example from a survey application that defines an entity for a question:

    private Question()
        Options = new List<QuestionOption>();

    public Question(string code, string text, int orderInSection,
        QuestionSection section, QuestionType type)
        : this()
        Code = code;
        Text = text;
        OrderInSection = orderInSection;
        Section = section;
        Type = type;

Control via properties

A second step to control more fully how your class is used, is to make the property setters private. This will prevent calling code from simply setting values for them, instead you provide methods where appropriate groups of properties can be set together, and validation can be applied.

This example from the same application controls the setting of an allowable range for a numeric question's answer:

    public int? NumericalMin { get; private set; }

    public int? NumericalMax { get; private set; }

    public void SetNumericalRange(int min, int max)
        if (min > max)
            throw new ArgumentException(“Max parameter must be greater than the min parameter.");

        NumericalMin = min;
        NumericalMax = max;

Logic in the model

The last method to discuss involves pushing more of the application's logic into the rich domain model, rather than having this in various services or other components of the application. There's some major advantages to this, particularly when compared to making additional database requests to handle particular behaviours. The code is arguably easier to write and maintain, and certainly easier to test as you can simply create instances of the classes and test the behaviour methods.

You do need to take care though - in order to fulfil certain behaviours it might be necessary to instantiate your object from the database with a more fully populated object graph that you otherwise normally would. So it's important to bear in mind the database requests that your method will require.

This final example illustrates that balance and why it's certainly worth looking to move behaviour into model methods when you can. With the survey application, we have multiple choice questions that you can obtain a score from based on which options you select in your answer. I required a means of calculating the maximum score available on a question.

For a multiple select question with check-boxes, the maximum score would be the total associated with each of the options. For a single select though, where you can only select one via radio buttons, the maximum score would be the option that had the highest score associated with it.

The following code examples illustrate how this logic can be set up as a method of the Question class, which will be valid so long as the related Options for the question are also loaded. If that is the case though, the code is very easy to write, maintain and test.

    public int GetMaximumAvailableScore()
        if (Type.Id == (int)QuestionTypeId.MultipleSelect)
            return Options.Sum(x => x.Score);
        else if (Type.Id == (int)QuestionTypeId.SingleSelect)
            return Options.Max(x => x.Score);
            return 0;

    public void Question_GetMaximumAvailableScoreForMultiSelect_ReturnsCorrectValue()
        // Arrange
        var question = CreateMultiSelectQuestion();

        // Act
        var maxScore = question.GetMaximumAvailableScore();

        // Assert
        Assert.AreEqual(8, maxScore);

No comments:

Post a Comment