This article is about how to apply a Domain-Driven Design (DDD) approach to the classes that the Entity Framework Core (EF Core) library maps to a database. This article is about why DDD is useful with a database, and how you can implement a DDD approach to data persistence classes using EF Core.
This article fits into the other articles on building business logic with EF Core. Here are some links to the other articles on this topic.
-
- Architecture of Business Layer working with Entity Framework (Core and v6) – revisited
- A library to run your business logic when using Entity Framework Core
- Creating Domain-Driven Design entity classes with Entity Framework Core (this article)
- GenericServices: A library to provide CRUD front-end services from a EF Core database
- Three approaches to Domain-Driven Design with Entity Framework Core – looks at different ways of implementing DDD in EF Core.
TL;DR – summary
EF Core has a few new features that allows a DDD approach to building classes that EF Core maps to a database (referred to as entity classes from now on).
There are a number of benefits to using a DDD-styled entity classes, but the main one is that the DDD design moves the create/update code inside the entity class, which stops a developer from misinterpreting how to create or update that class.
The aims of this article
This article shows you how to build a DDD-styled entity class and then compares/contrasts the DDD version with the standard version. The parts are:
- Setting the scene – what DDD says about object design and persistence
- A look at what a DDD-styled entity class looks like
- Comparing creating a new instance of a DDD-styled entity class
- Compare altering the properties of a DDD-styled entity class
- Summarising the DDD-style I have developed in this article
- Looking at the pros and cons of this DDD-styled EF Core entity classes
- Other aspects of DDD not covered in this article
- Conclusion
This article is aimed at developers who use Microsoft’s Entity Framework library, so I assume you are familiar with C# code and either Entity Framework 6 (EF6.x) or Entity Framework Core (EF Core) library. This article only applies to EF Core, as EF6.x doesn’t have all the features needed to “lock down” the entity class.
Setting the scene – what DDD says about object design and persistence
I read Eric Evans’s book Domain-Driven Design some years ago – it had a profound effect on me then, and it’s still a guiding light to many of my designs today. In this article I am going to focus on the part of DDD that effects the way you write and use the classes that EF Core maps to a database.
Eric Evans talks about persisting objects (classes in C#) to a database, and here are a few of the points he made about what his DDD approach should do:
- Present the client with a simple model for obtaining persistent objects (classes) and managing their life cycle.
- Your entity class design should communicate design decisions about object access.
- DDD has the concept of an aggregate, which is an entity that is connected to a root DDD says the aggregates should only by updated via the root entity.
NOTE: Eric talks about a DDD repository – I don’t recommend a repository pattern over EF because EF Core itself is already implements repository/UnitOfWork pattern (see my article “Is the repository pattern useful with Entity Framework Core?” for a fuller explanation on this point).
I was already applying some of these rules in my EF6.x applications, but I couldn’t fully implement what Eric was talking about, especially around the aggregates. But now with EF Core I can, and after some experimenting I have developed a pattern for building entity classes. The rest of this article describe my solution to using a DDD approach to building entity classes, and why DDD is better than the standard approach.
The design of a DDD-style entity class
I’m going to start by showing you DDD-styled entity class and then compare how they work compared to the standard way of building entity classes with EF Core. To help me I am going to use an example database I used with a web application that sells books (think super-simple Amazon). The figure below shows the database structure.
The top four tables are about a book, its authors and any reviews that book has, which is the part that this article looks at. I included the two tables at the bottom, as they are used in the business logic example, covered here.
NOTE: All the code in this article can be found in the GenericBizRunner GitHub repo. This repo contains the source of the GenericBizRunner NuGet library, plus an example ASP.NET Core application that uses the GenericBizRunner to run some business logic. You can find out more about the GenericBizRunner via the article “A library to run your business logic when using Entity Framework Core”
I now create a Book entity class, which has relational links to the Review table and the Authors table, via the many-to-many BookAuthor linking table. The following code shows you the main structure of the DDD-styled Book entity class (you will see the actual code for the constructor an methods later in this article).
public class Book { public const int PromotionalTextLength = 200; public int BookId { get; private set; } //… all other properties have a private set //These are the DDD aggregate propties: Reviews and AuthorLinks public IEnumerable<Review> Reviews => _reviews?.ToList(); public IEnumerable<BookAuthor> AuthorsLink => _authorsLink?.ToList(); //private, parameterless constructor used by EF Core private Book() { } //public constructor available to developer to create a new book public Book(string title, string description, DateTime publishedOn, string publisher, decimal price, string imageUrl, ICollection<Author> authors) { //code left out } //now the methods to update the book’s properties public void UpdatePublishedOn(DateTime newDate)… public IGenericErrorHandler AddPromotion(decimal newPrice, string promotionalText)… public void RemovePromotion()… //now the methods to update the book’s aggregates public void AddReview(int numStars, string comment, string voterName, DbContext context)… public void RemoveReview(Review review)… }
Things to note from this code are:
- Line 3: All the properties have private setters. That means the only way a developer can set the values is either via the public constructor, or via the methods shown later in the Book class.
- Line 9 and 10: Both of the one-to-many relationships (these are the aggregates that DDD talks about) are available only as a IEnumerable<T> property. This means you cannot add/remove items from the collection – you have use the access methods provided by the Book class.
- Line 13: EF Core needs a parameterless constructor, but it can have a private access modifier. That means no other code cannot create an instance via this parameterless constructor.
- Lines 16 to 20: The only way you can create an instance of the Book class is via its public constructor. This requires you to provide all the information needed to create a valid book.
- Lines 23 to 25: These three lines are the methods that allow a developer to alter the existing book’s value properties.
- Lines 28 to 29: These methods allow a developer to alter the existing book’s relational properties – known as aggregate by DDD.
The methods listed in lines 23 to 29 are referred to as access methods in this article. These access methods are the only way to change the properties and relationships inside the entity.
The net effect of all this is the class Book is “locked down”, so the only way to create or alter the class is via specific constructor(s) and appropriately named access methods. This contrasts with the standard way of creating/updating an entity class in EF Core, that is, create an default entity with its public parameterless constructor, and set values/relationships via its public setter on all the class’s properties. The next question is – why is this better?
Comparing creating a new instance of a DDD-styled entity class
Let’s start by considering the code to create a new instance of Book entity using first the standard-style and then with the DDD-styled class. The code will take some data input from json file containing information on various books and builds a set of Book entities based on that data (this is my code to seed the database with some realistic data). I start with the original code that used the standard-styled entity class (i.e. not DDD-style).
a. Standard entity class with public constructor and properties
var price = (decimal) (bookInfoJson.saleInfoListPriceAmount ?? DefaultBookPrice) var book = new Book { Title = bookInfoJson.title, Description = bookInfoJson.description, PublishedOn = DecodePubishDate(bookInfoJson.publishedDate), Publisher = bookInfoJson.publisher, OrgPrice = price, ActualPrice = price, ImageUrl = bookInfoJson.imageLinksThumbnail }; byte i = 0; book.AuthorsLink = new List<BookAuthor>(); foreach (var author in bookInfoJson.authors) { book.AuthorsLink.Add(new BookAuthor { Book = book, Author = authorDict[author], Order = i++ }); }
b. DDD-styled entity class, with specific public constructor with parameters
Now, here is the same code, but using the DDD-styled entity class with its public constructor.
var authors = bookInfoJson.authors.Select(x => authorDict[x]).ToList(); var book = new Book(bookInfoJson.title, bookInfoJson.description, DecodePubishDate(bookInfoJson.publishedDate), bookInfoJson.publisher, ((decimal?)bookInfoJson.saleInfoListPriceAmount) ?? DefaultBookPrice, bookInfoJson.imageLinksThumbnail, authors);
The constructor inside the Book class is shown below.
public Book(string title, string description, DateTime publishedOn, string publisher, decimal price, string imageUrl, ICollection<Author> authors) { if (string.IsNullOrWhiteSpace(title)) throw new ArgumentNullException(nameof(title)); Title = title; Description = description; PublishedOn = publishedOn; Publisher = publisher; ActualPrice = price; OrgPrice = price; ImageUrl = imageUrl; _reviews = new HashSet<Review>(); if (authors == null || !authors.Any()) throw new ArgumentException( "You must have at least one Author for a book", nameof(authors)); byte order = 0; _authorsLink = new HashSet<BookAuthor>( authors.Select(a => new BookAuthor(this, a, order++))); }
Things to note from the DDD-styles constructor code are:
- Lines 1 to 2: The constructor requires you to provide all the data needed to create a property initialised instance of the Book class.
- Lines 5,6 and 17 to 19: The code has some business rule checks built in. In this case they are considered coding errors, so they throw an exception. If they were user-fixable errors I might use a static factory that returns Status<T>, where the Status type contains a list of errors.
- Lines 21 to 23: The creating of the BookAuthor linking table is done inside the constructor. The BookAuthor entity class can have a constructor with an internal access modifier, which stops the code outside the DataLayer from creating a BookAuthor instance.
c. Comparing the two ways of creating an instance of the Book entity class
As you have seen, the amount of code to create an instance is similar in length for both cases. So, why is the DDD-style better. Here are my comments:
- The DDD-style controls access. There is no possibility of changing a property by accident. Each change is done by a named method or constructor with defined parameters – it is very obvious what you are doing.
- The DDD-style is DRY (don’t repeat yourself). You might need to create a Book instance in a few places. By putting all the code in the Book’s constructor then you don’t have to repeat it in different places.
- The DDD-style hides complex parts. The Book has two properties, ActualPrice and OrgPrice, and both must be set to the same value when it is created. In the standard-style code it required the developer to know this fact, while the writer of DDD-style class knows how it should work and can put that code inside the constructor.
- The DDD-style hides the setup of the aggregate, AuthorsLink. In the stardard-style class the code had to create the BookAuthor entity class, including the ordering. With the DDD-style version that complication is hidden from the caller.
- The DDD-style means the property setters can be private. One reason for using a DDD-style is to “lock down” the entity, i.e. you cannot alter any of the properties or relationships directly.
This last point moves us onto the looking at the DDD-style for altering an entity’s properties.
Compare altering the properties of a DDD-styled entity class
One of the advantages the Eric Evans says for a DDD-style entity is “They communicate design decisions about object access”. I understand this to mean that the design of your entity class should a) make it obvious how to change data inside an entity, and b) make it obvious when you shouldn’t change specific data in an entity. So let’s compare two different updates that my business rules say are allowed – one is simple and the other is bit more complicated.
1. Changing the publication date of a book
Say we want to preview a book at will be published later. You might have a draft publish date, but it’s likely to change. We therefore need a way to change the PublishedOn property in an instance of a book.
a. Standard entity class with public properties
The standard-style entity class woks by setting a property via is public setter, as shown in the following code.
var book = context.Find<Book>(dto.BookId); book.PublishedOn = dto.PublishedOn; context.SaveChanges();
b. The DDD-styled entity class with access methods
In the DDD-styled class the properties have private setters, so I have to set things via the access method.
var book = context.Find<Book>(dto.BookId); book.UpdatePublishedOn( dto.PublishedOn); context.SaveChanges();
There is nothing obviously different about these – in fact the DDD-style is a bit longer, as the UpdatePublishedOn method must be written (OK, it’s only two lines of code, but its more work). But there is a difference – in the DDD-style entity class you know you can change the publication date because there is a method with an obvious name – UpdatePublishedOn . You also know you’re not allowed to change the Publisher property, or any other property that doesn’t have an access method. That is helpful to any developer that needs to interact with an entity class.
2. Adding/removing a promotion to a book
The other business requirement is, we want to be able to add a promotion to a book. A promotion consists of a new (lower) price, and an explanation of why there is a promotion, such as “50% off all this week!”. Now, the implementation of this feature is efficient, but not obvious. Here are the rules:
- The OrgPrice property holds its normal price – when there is no promotion the book should sell for the OrgPrice.
- The ActualPrice property holds the price that the book is currently selling for – if there is a promotion then the ActualPrice holds the new price. If there is not promotion it should be set to the OrgPrice.
- The PromotionText property must hold text to show they customer when there is a promotion, otherwise it should be null when there is no promotion.
The rules are fairly obvious to the person who implemented this business feature, but it may not be so obvious to another developer when he comes implement the front-end code to add a promotion. By creating the DDD-style AddPromotion and RemovePromotion methods in the Book entity class the implementer of the feature can hide the complexities of this implementation. The user of the Book entity has obviously names method to use.
Let’s look at the AddPromotion and RemovePromotion access methods, as there is some further learning to take from these.
public IGenericErrorHandler AddPromotion(decimal newPrice, string promotionalText) { var status = new GenericErrorHandler(); if (string.IsNullOrWhiteSpace(promotionalText)) { status.AddError( "You must provide some text to go with the promotion.", nameof(PromotionalText)); return status; } ActualPrice = newPrice; PromotionalText = promotionalText; return status; }
Things to note from this code are:
- Line 4 to 10: It’s deemed that the PromotionalText property must always has something in it, so the method checks that. Because it’s an error that the user can fix, it returns an error to show the user.
- Lines 12, 13: This sets up the properties in the way the initial developer decided to implement the feature. The caller of the AddPromotion method doesn’t need to know how the feature is implemented.
To add a new promotion you would write:
var book = context.Find<Book>(dto.BookId); var status = book.AddPromotion(newPrice, promotionText); if (!status.HasErrors) context.SaveChanges(); return status;
The RemovePromotion is much simpler, and there is not possibility of a user-fixable error, so the method’s return type is void
public void RemovePromotion() { ActualPrice = OrgPrice; PromotionalText = null; }
These two examples are quite different. The first example, setting the PublishOn property was so simple that the standard-styled class was fine. But the second example contained implementation details on a promotion is added/removed which wouldn’t be obvious to anyone who hadn’t worked on the Book entity. In that case the DDD-style access method hides that implementation detail, which makes it easier for the developer.
Also, the second version shows that there are some value updates can have a piece of business logic in them. For small pieces of business logic like this we can still use an access method by having the method a status result, which contains any errors.
3. Handling aggregates – the Reviews collection property
The DDD approach says that aggregate entities should only be changed via the root entity. In our example the Reviews collection navigational property is an aggregate, which gives us a problem – even if we provide a private setter a developer could still add, remove or clear a property of type ICollection<T>. This is where EF Core backing fields feature comes in.
The backing field feature allows a developer to hide the actual collection in a private field and expose the collection as a IEnumerable<T>, which doesn’t allow the adding, removing or clearing of the collection. The code below shows the part of the Book entity class that defines the Reviews collection as a backing field.
public class Book { private HashSet<Review> _reviews; public IEnumerable<Review> Reviews => _reviews?.ToList(); //… rest of code not shown }
The other thing you need to do is to tell EF Core that on a read in of the Reviews relationship it should write to backing field, not the property. The configuration code to do that is shown below.
protected override void OnModelCreating (ModelBuilder modelBuilder) { modelBuilder.Entity<Book>() .FindNavigation(nameof(Book.Reviews)) .SetPropertyAccessMode(PropertyAccessMode.Field); //… other non-review configurations left out }
To access the Reviews collection I added two methods, AddReview and RemoveReview, to the Book class. The AddReview methods is the most interesting one, and is shown below
public void AddReview(int numStars, string comment, string voterName, DbContext context = null) { if (_reviews != null) { _reviews.Add(new Review(numStars, comment, voterName)); } else if (context == null) { throw new ArgumentNullException(nameof(context), "You must provide a context if the Reviews collection isn't valid."); } else if (context.Entry(this).IsKeySet) { context.Add(new Review(numStars, comment, voterName, BookId)); } else { throw new InvalidOperationException("Could not add a new review."); } }
Things to note from this code are:
- Lines 4 to 7: I purposely don’t initialise the _reviews field in the private parameterless constructor that EF Core uses when loading the entity. This allows my code to detect if the _reviews collection was loading, say via the .Include(p => p.Reviews) method. (For the create case I initialise the _reviews collection in the public constructor, so that Reviews can be added on creation).
- Lines 8 to 12: If the Reviews collection has not been loaded then the code needs to use the DbContext, so this just checks its set.
- Lines 13 to 16: If the Book has already been written to the database, then it will have a valid key. In that case I use a quicker technique to add a Review, by setting the foreign key in the new Review instance and write it out to the database (see section 3.4.5 in my book for this technique).
- Line 19: If none of these work then there is a code problem, so I throw an exception.
Note: I have designed all of my access methods to handle the case where only the root entity is loaded. It is up to the access method on how it achieves the update of the aggregates – this could include loading additional relationships.
Summarising the DDD-style I have developed in this article
Let me summarise the design of the DDD-styled entity classes that works with EF Core.
- You provide public constructor(s) for developers to create a properly initialised instance of the class. If the code needed to create an instance includes possible user-fixable errors then it will need static factory method instead, which will return Status<T>, where T is the class it is creating.
- All properties should have a private setter, i.e. they are read-only from outside the class
- For navigational properties that are collections you need to use backing fields and a IEnumerable<T> for the public property. That stops developers from changing the collection.
- You need to provide methods for all the different ways you want to change the data in the entity class, both property updates and aggregate updates. These access methods either return void if the process has no user-fixable errors in it, or a status value containing success or a list of errors if the code can detect user-fixable errors.
- The ‘scope’ of what the code in the entity class should take on is important. I have found limiting the methods to only changing the root entity and its aggregates is a good idea. I also limit my validation checks to making sure the entities I am updating are correctly formed, i.e. I don’t check outside business rules like stock availability etc. – that is the role of proper business logic code.
- The access methods must assume only the root entity has been loaded. If it needs relationship that isn’t loaded, then it must load the extra relational data itself. This simplifies the calling pattern to all access methods.
This last item means there is a standard way of calling access methods – here are the two forms, starting with the access method in which no user-fixable errors could occur:
var book = context.Find<Book>(bookId); book.UpdateSomething( someData); context.SaveChanges();
If there could be an error then the code would be
var book = context.Find<Book>(bookId); var status = book.UpdateThatCouldHaveErrors( someData); if (!status.HasErrors) context.SaveChanges(); return status;
The pros and cons of this DDD-styled EF Core entity classes
I always like to critically at any design pattern or architecture, and here are my thoughts on using DDD-style entity classes with EF Core.
The good parts of using a DDD approach with EF Core
- Using access methods to change value property is clearer. The act of making all the properties have a private setter and then having to use meaningfully-named methods to change the properties is positive. The named access methods make it obvious what you can change, and going via a method means it can return an error status if it detects a problem.
- Altering the aggregates via business-oriented methods works well too. Hiding the relationships, such as the Book’s one-to-many relationship to the Review class means that the details of how that update is done is kept inside the root entity class.
- Using specific constructors to create entity instances ensures an entity is created properly. Moving the code for building a valid entity class into the class’s constructor reduces the likelihood of a developer incorrectly interpreting how a class should be initialized.
The bad parts of using a DDD approach with EF Core
- My design includes some EF Core code inside the access methods, and this is considered an anti-pattern by some people. The problem is that your domain entities are now linked to the database access code, which in DDD terms isn’t a good thing. I found if I didn’t do this then I was relying on the caller to know what needed to be loaded, which breaks the separation of concerns rule.
- The DDD CRUD code requires more code to be written. For simple updates, like the change to the publication date of a book, the DDD-styled access methods seem a little bit of an overkill. Is it worth it?
As you can tell, I do like the DDD-style, but it did take a while to get the format right. Now I have a pattern that I can apply to any applications I work on. I have tried this style of access methods on small applications and it works well, but I won’t fully know its pros/cons until I have used it in a sizeable project.
My decision to allow EF Core code inside the access methods was a tough call. I tried to keep EF Core commands out of the access methods, but this led to the caller of the method to have to load the various navigational properties need by the method, and if the caller didn’t do that correctly the update could fail silently (especially on one-to-one relationships). That to me was a not acceptable, so I allowed EF Core code inside some methods (by not constructors).
The other bad point was that DDD CRUD access method requires more code, and I’m still thinking about that one. Should I bite the bullet and make all updates go through a DDD access method, or is there a case for allowing some properties to have a public setter and allow direct change of the value? The purest in me would say always use access methods, but I know that there is a ton of boring updates to be done in a typical application, and it’s easier to do directly. Only a real project will tell me what works best.
Other aspects of DDD not covered in this article
This article is already very long so I’m going to finish here, but that means I will leave some big areas out. Some I have already written about and some I will be writing about in the future. Here are the things I have left out.
- Business logic and DDD. I have used DDD concepts with business logic for some years, and with EF Core’s new features I expect to move some of the business logic into the DDD-styles entity classes. Read this article “Architecture of Business Layer working with Entity Framework (Core and v6) – revisited”.
- A DDD repository pattern. Eric Evans talks about building a repository pattern to hide your database access library. I have concluded that using a repository pattern with EF Core is a bad idea – you can read why in this article “Architecture of Business Layer working with Entity Framework (Core and v6) – revisited”.
- Multiple DBContexts/Bounded Contexts: I have been thinking a lot about spitting a database across multiple DbContext’s, for instance having a BookContext that only knows about the Book entity and its aggregates, and a separate OrderContext that handles orders. I think the bounded context idea is very important, especially if you are building an application that needs to scale up as it grows. I haven’t quite got my pattern right yet, but I expect to write an article about this in the future.
Conclusion
EF Core, with its backing field feature, allows the building a DDD-styled entity classes, which is “locked down”, i.e. the public constructor(s) define how to create an instance and properties can only be updated via access methods. The benefits are that makes it more obvious how the entity class should be used, and knowledge about the inner workings of the entity class are held inside the entity class itself.
It has taken me some time to get a DDD style that provided a consistent interface to the entity class access methods. Getting the access method pattern right is important because it makes the calls quicker and simpler to code. In any application that uses a database you will get a lot of CRUD (Create, Read, Update and Delete) database accesses and you need produce these quickly and easily – so a simple and consistent pattern will make you quicker at writing CRUD code.
NOTE: You can find the code in this article in GenericBizRunner GitHub repo. That repo also contains an example ASP.NET Core application where you can try changing a book using these access methods. Please do clone it and run it locally – it uses an in-memory Sqlite database so it should run anywhere.
Happy coding.