DDD entity patterns
This page explains how you can create Domain-Driven Design (DDD) styled EF Core entity classes. It also tells you the signature of that GenericServices looks for methods that it can use to create or update the entity and its aggregates.
If you are not clear about what DDD styled entity classes are, or you want a longer explanation then please read the article on my blog called Creating Domain-Driven Design entity classes with Entity Framework Core.
The basics of a DDD-styled entity class
The idea is that any creation or update of an entity (and its aggregates) has to be done by methods
with a function-revealing name, e.g.
AddReview. At the same time you must stop the ability to create/update
the data in any other way. This requires you to "lock-down" the entity class by:
- All properties should have a private setter.
- All collections should be of type IEnumerable<T>, so that nothing can be added/removed from the collection. The actual collection is handled by a backing field.
- The parameterless constructor should should be private (EF Core uses this for creating instance of read) Then creates should be done by either/both:
- A public constructors with all the parameters needed to create the entity correctly.
- A public static method with all the parameters needed to create the entity correctly. *The benefit of static methods is that they can return a status, so errors found during the construction can be fed back. The updates should be done by:
- Public methods with function-revealing names and parameters.
Example DDD-styled entity classes
- The Order entity class.
- The Book entity class. Note: I have made one property.
PublishedOn, have a public setter so that I can compare the performance of an AutoMapper update and a DDD-Styled update.
How GenericServices matches a DTO to a DDD method
The process that GenericServices goes through to match a DTO to a DDD constructor, static factory or update method is listed below. Note that the
CreateAndSave method have an optional property called
ctorOrStaticMethodName, and the
UdateAndSave method has a property called
methodName that can force the selection of the ctor/factory/method to call. This is referred to in the list below as "selectorString"
- If the entity has any appropriate ctor/factory/method than can be called (and the update
methodNameisn't "AutoMapper") then it will first look there for matches.
- If the "selectorString" is set, then I will use this to pick the ctor/factory/method. The possible matches are:
- "AutoMapper" - use AutoMapper to update the properties
- "ctor" - use a ctor
- "ctor(2)" - use a ctor with 2 parameters
- "methodName" - use
methodName(static method if create, method in entity class if update).
- "methodName(2)" - same as last item, but with given number of parameters
- If still not defined that it takes the DTO class name, removes "Dto", "VM", or "ViewModel" (case insensitive) and tries to find a entity method (update) or entity static factory (create). If it does it assumes this the the match.
- If none of these match then it looks at all the ctors/static factor (Create) or methods (update) and matches them by parameters.
- If there are no matches, or there are more than one match it will give you an error message.
NOTE: You will also get an error message if steps 2 or 3 selects a ctor/method and then the parameters don't match.
What does GenericServices expect in a DDD-styled entity?
When GenericServices calls a method to DDD-styled entity class it will have only loaded that entity class.
This means if your method needs some other part of the relationship then it must load itself.
You are allowed to have the
DbContext as one of the parameters in your method call (either your actual
DbContext or the generic
You can standard constructors and update methods that return void, but if you want to return a
IStatusGeneric, then you can. This allows you to return errors, or a success Message. The rules are:
- For update methods they can return
IStatusGeneric. This status result is combined into the
- For static methods used to create an entity instance it must return the type
IStatusGeneric<TEntity>. Again, the status result is combined into the
CrudServices' status, and the primary key(s) are copied back into the DTOs primary key(s) properties (in case you need them).
Note that some of the entities are Read-Only, i.e. they can only be created/updated via the root entity class.