-
Notifications
You must be signed in to change notification settings - Fork 95
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 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.
- 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.
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
methodName
isn'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.
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 DbContext
class).
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:
- You need to add the NuGet library GenericServices.StatusGeneric in the assembly(s) that your entity classes are in.
- For update methods they can return
IStatusGeneric
. This status result is combined into theCrudServices
' status. - For static methods used to create an entity instance it must return the type
IStatusGeneric<TEntity>
. Again, the status result is combined into theCrudServices
' status, and the primary key(s) are copied back into the DTOs primary key(s) properties (in case you need them).
You can find more about the pattern I use by looked in at the Summarising the DDD-style in my article and studying the entity classes in the DataLayer/EfClasses directory.
Note that some of the entities are Read-Only, i.e. they can only be created/updated via the root entity class.