I've been a developer for a long time, and I've been reluctant to use DDD for a long time because until now I hadn't found anyone who could explain it in such a didactic way as you have been doing. And I confess that I'm enjoying it and maybe I'll make a change for myself. I'll take a look at your course and maybe I'll become your student.
Nice. I would think though that as a meeting requires an organiser that we should provide organiser as a parameter to Create() rather than adding the organiser after creating the meeting.
Hi Milan, Thank you for the video. The entity still looks more anemic than a rich domain. You have moved the behaviors into the entity, but the properties are still available to change within the business logic. Also, an entity would have domain events, which are missing in your example.
I like rich domain models, but I sometimes feel like I have to wrestle with EF core to get it to recognize the changes added to an aggregate. I feel like sometimes I have to do aggregate.Add(users) and users.add(user) in order to get it to recognize the changes. If not I get a "Database operation expected to affect 1 row(s) but actually affected 0 row(s)." Have you run into this issue? Also, how do you feel about an anemic approach where the domain operations are encapsulated in a static extension class? I think EF also struggles with this sometimes.
I've seen it a couple of times, but this usually happens with Owned types. Right? | Also, how do you feel about an anemic approach where the domain operations are encapsulated in a static extension class? This is only anemic in "name". We still behavior around the domain entity. Although I prefer just having the method in the entity for better cohesion.
Good work Milan! I miss specification pattern OR policy pattern for a meetings (instead of meetingPolicyService). Another suggestion would be to use extension methods instead of helper methods. Helpers are sometimes considered a bad practice :)
@@MilanJovanovicTech not bad practice per se, just in relation to extension methods in DDD. Hard to give a short answer. Extension methods are often better than helper methods in DDD because they keep behavior close to domain objects, improve readability, and make code more natural to use. They also promote encapsulation and object-oriented principles, unlike helper methods, which can lead to procedural code and cluttered utility classes
Great video! A question. In 9:58 you refactored the domain model to include policy logic (Passing the policy object to the Create method), why wouldn't you use an Extension method for that? Something like: meeting.Validate(meetingPolicy) ?
Milan why did you create a static Create function in the domain? Why not have a MeetingFactory ? That will make testing a lot easier. Also why not use AutoMocker for your tests ?
| why did you create a static Create function in the domain? I always preferred a static factory method over a new class for simple creation logic. This is just calling a constructor and a few business rules. A factory would introduce the same code plus an additional dependency. As for AutoMocker, I generally use NSubstitute.
why would you need a factory object to create that meeting? Because one day you might need an external dependency? ok so you do this for all your entities just in case?
You should always go for rich if you want to abide to DDD, because anemic models are an anti pattern. When a model (entity, aggregate, value objects) has only data then it is considered anemic. If it hides its data and exposes behaviour (methods) then it is considered rich. You probably use anemic models because you put the business logic into the services and gave the power to manage your models' state. However this leads to: 1. Unneccessary, or even circular, dependencies between services, 2. Constructor hell, 3. Allowing the whole system to create invalid states with the un-encapsulated models, 4. Fat test setups which makes them fragile. Service logic is only meant to be used when the logic is too complex for one model to contain within, or it has external deps like saving to a database. You can mix them together, with the service only containing support logic such as saving to DB, logging, sending events. While the model can contain the business rules which keeps itself valid. I also do anemic models and fat services. I pay the cost of it with all of the points I mentioned above. I do whatever I find easier to follow and maintain :)
@@ciekawki6574 They aren't an anti-pattern, they're much more commonplace than DDD, which is usually chosen in response to a problem. What you and your team is familiar with will be far more important than any objective strengths/weaknesses of each. Rather than trying to predict what's "right" I'd recommend learning how to identify the signs that you've gone down the wrong path. For example if you go with DDD as a default you might grow resentful of going through the effort if your domain is so simle that you are getting no benefits, that emotion/smell is a sign you've gone the wrong way. Going from the opposite direction if you get problems with new releases causing problems because they aren't following business rules that's a sign your simple design needs adapting, DDD could be one of many solutions.
Great video! Coming from a performance perspective though, the creation of agenda items now went from only needing one pass to get the meeting duration to traversing the list every single time you add an agenda item Would this be of any concern to you?
No concern. We're talking micro-seconds here since the list should fit in memory. What meeting has 1M agenda items? I'd rather quit that company right away 😅
If the logic for checking to see if there is conflict with other meetings is put into a domain service. Do we then somehow inject that domain service into the domain model to ensure it's being used when populating the Meeting aggregate?
Injecting a domain service or general service isn't possible, as a domain isn't something that can be injected. I'd move the check method (which returns a boolean) to the Meeting or UserRepository (persistence layer). Then, in the MeetingService, have a void method like EnsureUserHasNoTimeConflicts that throws an exception if needed (application layer). Call this in the command handler. An even better approach: Forget the MeetingService. Just use the repository method (returning a boolean) in the command handler. Call the result variable isUserConflictFree (or similar) and pass it to AddParticipant. Perform the check there, throwing an exception if needed.
Domain models should not have dependency injections for services. They should exist in a world where they only know about their own rules and nothing else. Their mission is to protect themselves and always be valid. I would see if there is an additional sub domain that really handles this logic and is not a lonely service that is being used by multiple models for code reuse. In all cases that relate to cross cutting concerns, services come to the rescue. Here is the flow I imagine: User -> MeetingService -> Checks MeetingChecker -> MeetingService creates Meeting -> Meeting performs its own validations (end date being earlier than start date and so on) -> MeetingService calls the DB or whatever.
I think it's possible to perform the logic in domain service then send the result as interface instance as argument in the constructor in the application layer. So the agg root can then check this logic and perform some validation
It's absolutely possible to inject a domain service and call its method. This is often called double dispatch. Check these two videos for implementation and tradeoffs: - Double dispatch: ruclips.net/video/wi_wsw5Gp6Q/видео.html - Tradeoffs: ruclips.net/video/eC7GMGIR4Gw/видео.html
Hmm, but now we splitted checks and logic that was in a single class on two: the use case and meeting aggregate. May be we should try to move all logic in domain services?
Going more into the "why" is something that's always missing in DDD for me. Having an objective real example of before/after is what I really need to see what value DDD brings, which I appreciate is hard to do in a video. I still struggle to see DDD as an alternative to other approaches rather than being able to understand the domains and problems where it is objectively better.
I don't like throwing exceptions from constructors, for one. Second thing is this leaves the possibility to use domain events later - again a preference of mine: not having side effects (domain events) in constructors.
ok but now how do you handle the database insertions if you are changing the mapped properties. for example if i include Participants in a query now ef does not know what to do
@@DaminGamerMC Mappings. I'm not sure if the default mappings supports the => because I think that is a method and not a property. But, if he used a property I think it would do it correctly. Or you can specify the field in the mappings.
I will just get lost at differentiating domainEntities that i created vs ones that are already tracked and i am just updating; Particularly because I try to call save changes just once in the controller but may have a long dependency of method that pass the entities around
The huge problem with your example is that you do not describe the exact invariants which have to be consistent. An aggregate is about enforcing true business invariants by keeping the aggregate consistent.
I kind of agree. I think the first part of the video could be showing all the invariants (business rules) the problem has. And them defining the aggregates. But when he validates the agenda to not exceed the duration of the meeting it is kind of an invariant. So it kind of makes sense be a aggregation of a Meeting.
The idea was to "discover" them as we go, rather than stating them all up front. I wanted to approach it as someone reading the code for the first time and figuring stuff out.
A continuation of this video implementing Domain Services and Domain Events would be nice
I hear you!
Waiting too! Thanks
I've been a developer for a long time, and I've been reluctant to use DDD for a long time because until now I hadn't found anyone who could explain it in such a didactic way as you have been doing. And I confess that I'm enjoying it and maybe I'll make a change for myself. I'll take a look at your course and maybe I'll become your student.
Hey, glad to hear that. 😁 If you do end up considering the course, there will be a nice discount next week for Black Friday
I don't know why, but these DDD videos are so soothing and interesting to watch. Another great video Milan!
Hey, thanks a lot
Nice. I would think though that as a meeting requires an organiser that we should provide organiser as a parameter to Create() rather than adding the organiser after creating the meeting.
Actually, that's a great idea! Including it in P2 of the video with other feedback
More videos on DDD / aggregates / domain / events / messaging please.
Okay, I'll consider how to extend this video with these topics
Hi Milan,
Thank you for the video. The entity still looks more anemic than a rich domain. You have moved the behaviors into the entity, but the properties are still available to change within the business logic.
Also, an entity would have domain events, which are missing in your example.
I commented about the properties in the video. We'll see where I take this in Part 2
I like rich domain models, but I sometimes feel like I have to wrestle with EF core to get it to recognize the changes added to an aggregate. I feel like sometimes I have to do aggregate.Add(users) and users.add(user) in order to get it to recognize the changes. If not I get a "Database operation expected to affect 1 row(s) but actually affected 0 row(s)."
Have you run into this issue?
Also, how do you feel about an anemic approach where the domain operations are encapsulated in a static extension class? I think EF also struggles with this sometimes.
I've seen it a couple of times, but this usually happens with Owned types. Right?
| Also, how do you feel about an anemic approach where the domain operations are encapsulated in a static extension class?
This is only anemic in "name". We still behavior around the domain entity. Although I prefer just having the method in the entity for better cohesion.
Love DDD and any related video you make about it. A aquired your course and changed the way I think when building models (now domains).
I'm very glad this was helpful! :)
Good work Milan!
I miss specification pattern OR policy pattern for a meetings (instead of meetingPolicyService).
Another suggestion would be to use extension methods instead of helper methods. Helpers are sometimes considered a bad practice :)
I don't consider helper methods a bad practice. Why would they be?
@@MilanJovanovicTech not bad practice per se, just in relation to extension methods in DDD. Hard to give a short answer. Extension methods are often better than helper methods in DDD because they keep behavior close to domain objects, improve readability, and make code more natural to use. They also promote encapsulation and object-oriented principles, unlike helper methods, which can lead to procedural code and cluttered utility classes
Great video!
A question. In 9:58 you refactored the domain model to include policy logic (Passing the policy object to the Create method), why wouldn't you use an Extension method for that? Something like: meeting.Validate(meetingPolicy) ?
I prefer having it all in one method on the Meeting
Awesome thanks Milan
Sure thing!
Great video! How are those participants and agendaItems are being stored in the database?
Separate tables in the database for each, with a FK pointing to the meeting
Milan why did you create a static Create function in the domain?
Why not have a MeetingFactory ? That will make testing a lot easier.
Also why not use AutoMocker for your tests ?
| why did you create a static Create function in the domain?
I always preferred a static factory method over a new class for simple creation logic. This is just calling a constructor and a few business rules. A factory would introduce the same code plus an additional dependency.
As for AutoMocker, I generally use NSubstitute.
why would you need a factory object to create that meeting? Because one day you might need an external dependency? ok so you do this for all your entities just in case?
Not a big thing but what’s the advantage of a static create method instead of exposing just one public constructor with the required inputs?
A video we were waiting for
Finding a decent example takes time
It was worth it @@MilanJovanovicTech
Are there any general guideliness regarding whether I should use rich or anemic model pattern? So far i've been always using the latter one.
You should always go for rich if you want to abide to DDD, because anemic models are an anti pattern.
When a model (entity, aggregate, value objects) has only data then it is considered anemic. If it hides its data and exposes behaviour (methods) then it is considered rich.
You probably use anemic models because you put the business logic into the services and gave the power to manage your models' state. However this leads to:
1. Unneccessary, or even circular, dependencies between services,
2. Constructor hell,
3. Allowing the whole system to create invalid states with the un-encapsulated models,
4. Fat test setups which makes them fragile.
Service logic is only meant to be used when the logic is too complex for one model to contain within, or it has external deps like saving to a database. You can mix them together, with the service only containing support logic such as saving to DB, logging, sending events. While the model can contain the business rules which keeps itself valid.
I also do anemic models and fat services. I pay the cost of it with all of the points I mentioned above. I do whatever I find easier to follow and maintain :)
@@user-dc9zo7ek5j Thank you for your detailed answer!
No guidelines, just preferences. You get something with one approach that you lose with the other (and vice versa).
@@MilanJovanovicTech Understood. Altough calling anemic model na "anti pattern" got me thinking, that there are only bad things attached to it
@@ciekawki6574 They aren't an anti-pattern, they're much more commonplace than DDD, which is usually chosen in response to a problem. What you and your team is familiar with will be far more important than any objective strengths/weaknesses of each.
Rather than trying to predict what's "right" I'd recommend learning how to identify the signs that you've gone down the wrong path. For example if you go with DDD as a default you might grow resentful of going through the effort if your domain is so simle that you are getting no benefits, that emotion/smell is a sign you've gone the wrong way. Going from the opposite direction if you get problems with new releases causing problems because they aren't following business rules that's a sign your simple design needs adapting, DDD could be one of many solutions.
Great video! Coming from a performance perspective though, the creation of agenda items now went from only needing one pass to get the meeting duration to traversing the list every single time you add an agenda item
Would this be of any concern to you?
No concern. We're talking micro-seconds here since the list should fit in memory.
What meeting has 1M agenda items? I'd rather quit that company right away 😅
@@MilanJovanovicTech Very true 😄
If the logic for checking to see if there is conflict with other meetings is put into a domain service. Do we then somehow inject that domain service into the domain model to ensure it's being used when populating the Meeting aggregate?
Injecting a domain service or general service isn't possible, as a domain isn't something that can be injected.
I'd move the check method (which returns a boolean) to the Meeting or UserRepository (persistence layer). Then, in the MeetingService, have a void method like EnsureUserHasNoTimeConflicts that throws an exception if needed (application layer). Call this in the command handler.
An even better approach:
Forget the MeetingService. Just use the repository method (returning a boolean) in the command handler. Call the result variable isUserConflictFree (or similar) and pass it to AddParticipant. Perform the check there, throwing an exception if needed.
Domain models should not have dependency injections for services. They should exist in a world where they only know about their own rules and nothing else. Their mission is to protect themselves and always be valid. I would see if there is an additional sub domain that really handles this logic and is not a lonely service that is being used by multiple models for code reuse. In all cases that relate to cross cutting concerns, services come to the rescue.
Here is the flow I imagine: User -> MeetingService -> Checks MeetingChecker -> MeetingService creates Meeting -> Meeting performs its own validations (end date being earlier than start date and so on) -> MeetingService calls the DB or whatever.
I think it's possible to perform the logic in domain service then send the result as interface instance as argument in the constructor in the application layer. So the agg root can then check this logic and perform some validation
It's absolutely possible to inject a domain service and call its method. This is often called double dispatch.
Check these two videos for implementation and tradeoffs:
- Double dispatch: ruclips.net/video/wi_wsw5Gp6Q/видео.html
- Tradeoffs: ruclips.net/video/eC7GMGIR4Gw/видео.html
@MilanJovanovicTech Thanks this is exactly what I needed.
Hmm, but now we splitted checks and logic that was in a single class on two: the use case and meeting aggregate. May be we should try to move all logic in domain services?
We'd end up with what we had in the use case (more or less)
Going more into the "why" is something that's always missing in DDD for me. Having an objective real example of before/after is what I really need to see what value DDD brings, which I appreciate is hard to do in a video. I still struggle to see DDD as an alternative to other approaches rather than being able to understand the domains and problems where it is objectively better.
The real value of DDD is before writing any code. But that's much harder to express in a practical video.
Why did you move the code to the Meeting.Create method instead of a constructor for the Meeting class? :)
I don't like throwing exceptions from constructors, for one. Second thing is this leaves the possibility to use domain events later - again a preference of mine: not having side effects (domain events) in constructors.
Awesome
Thanks!
ok but now how do you handle the database insertions if you are changing the mapped properties. for example if i include Participants in a query now ef does not know what to do
EF Is super smart and would use the backing fields.
@@pilotbobahow does it know which backing field it should use
I would not recommend using domain entity with EF. Map you domain entity to data objects and use them with EF core
@@DaminGamerMC Mappings. I'm not sure if the default mappings supports the => because I think that is a method and not a property. But, if he used a property I think it would do it correctly. Or you can specify the field in the mappings.
EF 6/7/8 can handle this just fine, with no additional configuration. Default mapping is Property => _property (backing field)
and also automation test books
Book first, got it
I will just get lost at differentiating domainEntities that i created vs ones that are already tracked and i am just updating; Particularly because I try to call save changes just once in the controller but may have a long dependency of method that pass the entities around
EF is usually pretty good at managing this
The huge problem with your example is that you do not describe the exact invariants which have to be consistent. An aggregate is about enforcing true business invariants by keeping the aggregate consistent.
Ok, whats ur Github Bro.
I kind of agree. I think the first part of the video could be showing all the invariants (business rules) the problem has. And them defining the aggregates.
But when he validates the agenda to not exceed the duration of the meeting it is kind of an invariant. So it kind of makes sense be a aggregation of a Meeting.
The idea was to "discover" them as we go, rather than stating them all up front. I wanted to approach it as someone reading the code for the first time and figuring stuff out.
Very nice video! Can you share source code?
In the description (if not then Patreon - can't recall exactly)
First comment from kashmir
Blazing speed
I would suggest to read DDD books first!
Ok