Last time I talked a bit about the new release of Entity Framework Core 3 and introduced the small demo I wrote to test the new features. Now let’s see some code!
I took the liberty to update the repository on GitHub and split it into two projects. Example 1 is just a simple console app, while Example 2 shows more or less the same code but in a Web API envelope.
Let’s start with Example 1 then. The Models folder contains the juice of the project. This is the first place to start putting your classes when you want to do some DDD. Of course you need a somewhat clear understanding of the Domain, otherwise you’ll surely end up with a big-ball-of-mud.
Using TDD in this case might help: if your knowledge of the Domain is lacking or you don’t have yet a complete idea of the final design, writing tests first will give you the proper hints and point you in the right direction.
Or at least will make you ask the right questions.
Going back to the code, here we have 5 classes, modeling a simple eCommerce scenario:
- Product: nothing fancy, no methods. More or less an “anemic model“. It is an Entity and Aggregate root.
- Quote: here things get interesting. Holds a list of QuoteItems and an AddProduct() method. This one will check for null and add it to the bucket. Then we have a Total property that computes the…well, total price. Quote is an Entity and Aggregate root.
QuoteItems is modeled as Value Object instead, although in a more complex scenario should be considered as an Entity on its own.
- Order: more or less as Quote, it’s an Entity and Aggregate root. It also holds a collection of products, OrderLines in this case. However, there’s no way to update the list as it gets populated at order creation. If you think about it, makes kinda sense: once you’ve placed an order, you cannot add more items. Again, here as well we have the Total property.
OrderLine is a Value Object like QuoteItem.
Almost all the classes have a private parameterless constructor. That’s the only concession we have to do to Entity Framework Core: it is needed in order to serialize back and forth the instance as it might not be possible to call the other one.
The Product class is the only one that doesn’t have a parameterless cTor. That’s because its cTor arguments are mapping to all the properties exposed from the class (names included).
As you can see, there’s no persistence-related code here: these classes are “just” modeling our domain and expressing our business needs, nothing more.
Persistence instead is handled by our CommerceDbContext class in the Infrastructure folder. All the mapping is expressed using model-specific implementations of the IEntityTypeConfiguration interface.
The QuoteEntityTypeConfiguration and OrderEntityTypeConfiguration classes are very similar and quite interesting. As you can see we’re ignoring the Total property as it’s computed dynamically. Also, the QuoteItems and OrderLines are handled as Owned Entities . They have a table of their own and both reference the Products table.
As you may have noticed, I’ve defined a shadow property “Id” on both in order to be able to persist them. This won’t affect our models, even though in a more advanced scenario we would upgrade them to Entities in order to unlock more functionalities.
For example, imagine you’ve ordered multiple items on Amazon and your order is ready to be delivered. Maybe each product is sold and shipped by a different vendor. This means that you might not receive all of them together. Modeling the OrderLine as Entity allows you to properly track and handle this kind of situation.
Now, as you might have noticed the QuoteItem class is very small, just two properties. I might have used a struct instead. However, this would allow our consumers to call the default parameterless constructor directly, avoiding the other one. This is a really big problem as we are checking the invariants in that cTor and ensuring the instance is always in a good state.
That’s enough for now. Next time we’re going to take a look at Example 2. See you!