Tame Your Domain Using THIS Powerful Tool!

  Рет қаралды 4,928

Codewrinkles

Codewrinkles

Күн бұрын

In this video, we will explore the power of domain services in Domain-Driven Design (DDD) and how they can help you tame the complexity of your domain. Domain services are a powerful tool that can be used to encapsulate complex business logic and keep your domain models clean and maintainable. We'll go over real-world examples of how domain services can be used to solve common problems in DDD and show you how to implement them in your own projects. Whether you're new to DDD or an experienced developer looking to improve your approach, this video will provide valuable insights and best practices for using domain services to improve the maintainability and scalability of your domain. We'll even go deeper into concepts like the role of the aggregate root, DDD value objects and much more!
Join this channel to get source code access and other perks:
/ @codewrinkles
Contents:
1. Intro: 00:00
2. App overview: 00:55
3. Challenge: calculating rating: 03:08
4. What about business logic that spans across aggregates? 03:52
5. First idea: squeezing it in the aggregates? 03:59
6. Bad idea: 07:44
7. Second idea: putting it in the handler? 08:25
8. No way! 09:48
9. Drum roll! Domain services FTW: 10:59
10. Implementing a domain service: 13:01
11. Should we add domain services to DI? 14:10
12. Don't use interfaces for domain services! 16:15
13. Transactional consistency: 17:45

Пікірлер: 34
@vincentcifello4435
@vincentcifello4435 Жыл бұрын
Thank you for the DDD videos. This clearly takes a lot of effort. I have spent months trying to understand this topic, so any content like this is truly welcome and appreciated. Please do not take the following to be anything other than a search for understanding. 1)Domain Services should be used to implement Domain Logic that does not properly fit in an Aggregate. This is not a good example of a Domain Service. The RatingCalculation is a mathematical function, not really Domain Logic, per se. The main issue is that it could be easily implemented as a private method in a well-designed Aggregate. I understand that Domain Service was chosen here to coordinate a business rule (both players' ratings updated simultaneously) across the 2 Player Aggregates. However, this implementation fails to achieve that goal (ie the 2 player objects are saved in separate transactions which can result in data inconsistency). This is because the boundaries are not correct (see below). 2)There was a missed opportunity from the Project example in the prior videos. As you stated, a perfect use of a Domain Service is to enforce invariants ACROSS Aggregate Instances. In the Project example, a Business Rule could be that any Member may only be ProjectManager on 2 different uncompleted projects simultaneously. There is no way to enforce this invariant within a single Project Aggregate instance. Therefore, a Domain Service could be used to implement that business logic within the Domain layer across Project Aggregate instances. 3)This chess example was a missed opportunity to demonstrate Bounded Context and Aggregate design. The example's ending Handle method turned into a long Transaction Script. This should be viewed as a code smell in an Application using DDD. The purpose of DDD is to push the logic from the Application Handlers down into the Domain. I believe the Transaction Script occurred because the boundaries are wrong. There are 3 different repository save calls (whitePlayer blackPlayer, and game) that are individual transactions. If one fails, the system will be in an inconsistent state. For example, only white's rating was updated and it was based on a game that was not completed. I couldn't quite tell, but it almost sounded like there was a suggestion to wrap the 3 transactions in another transaction!? Yes! Inside an Aggregate! Updating the players ratings only makes sense simultaneously at the completion of a game. Those 3 things should occur in 1 atomic transaction. That is the perfect use for an Aggregate. Of course, nesting aggregates is just plain wrong, but that doesn't need to be done here if the boundaries are correct. Fowler's example has Customer and Product concepts in each bounded context (this originated with Evans, of course). That thought process can be used here as well. www.martinfowler.com/bliki/BoundedContext.html One solution: Player Bounded Context with a Player Aggregate containing demographic-type data (playerId, name, favorite color, etc). That would be used to sign up players, update their info, etc. The Player Bounded Context would be the owner of playerId. Gaming Bounded Context with a Game Aggregate Root containing Black and White GamePlayer Entities (playerId, rating, total games, etc). CalculateRatings() is a private method used in the Game AggregateRoot, as follows. public void Complete(result, reason); //would call... private void CalculateRatings(GamePlayer winningPlayer, GamePlayer losingPlayer); The GamePlayer Entities themselves could still retain control over actually increasing/decreasing their own ratings using whatever business rules are appropriate. The Handler is massively simplified and all 3 updates occur within one transactional boundary thereby maintaining proper data consistency: var currentGame = _gameRepository.GetByID(request.gameId); //would retrieve the Game Aggregate (game, whitePlayer, blackPlayer) currentGame.Complete(blackWon, justPlainBetter); //would update the GameResult/Reason, calculate the ratings, and update the contained player entities. _gameRepository.Save(currentGame); //would save the Game Aggregate (Game, BlackPlayer, WhitePlayer) in 1 transaction. Sorry for the long comment, but there is a lot to unpack and the topic is fascinating. Please correct me where mistaken.
@Codewrinkles
@Codewrinkles Жыл бұрын
First of all, thank you very much for this very insightful and well reasoned comment. I totally appreciate it. 1. I'm not sure that rating calculation is just a mathematical function. In most chess apps it depends on a lot of domain specific variables, like rating deviation, idle time without played games and some other. 2, 3 I kind of agree with your suggestion. Still I think this was the right choice for the video. You see, most of the people who are watching this video are new to DDD. And DDD concepts are not easy to grasp. That's why it's a pedagogical procedure to extract and isolate principles as much as possible. So, even if I agree with you on the bounded contexts design, talking about bounded contexts in this video would just distract the attention from the main point. Also, for people that are for the very first time dealing with DDD concepts, I think this chess example is much easier to grasp then a follow-up to the project from the previous video. I totally resonate with your description of a correct bounded context design for this video.
@francisdar127
@francisdar127 Жыл бұрын
Thanks for this! This definitely answers my query about your previous video. Looking forward to the next one about persistence.
@Codewrinkles
@Codewrinkles Жыл бұрын
I guess there will be just one more before persistence. Plan is the first persistence video next Monday!
@onedev7316
@onedev7316 Жыл бұрын
Again a great video. Precise and to the point.
@Codewrinkles
@Codewrinkles Жыл бұрын
Glad you liked it!
@vladmartian
@vladmartian Жыл бұрын
Why not use a static class for the domain service? Is it is stateless and doesn't depend on anything. Or could be made as extension method
@Codewrinkles
@Codewrinkles Жыл бұрын
To be honest I never thought about this and never saw an implementation using static classes. But now that you say it, it seems to make sense! Thanks for the input.
@donnyroufs551
@donnyroufs551 9 ай бұрын
I suspect that the next videos in your series will clarify some of my questions, anyhow: 1. You mentioned that a domain service does not contain dependecies nor does it have state. You also dont mock it, wouldnt it be better to make it a static class and use it directly in the handler instead of injecting? 2. Why did you not opt for a domain event to calculate the new ratings and persist those aggregates? It seems odd that the handler has to know about it and deal with a possible failure on calculating the rating?
@sotsch9280
@sotsch9280 Жыл бұрын
DomainServices are the glue that integrates two or more Aggregates! :)
@Codewrinkles
@Codewrinkles Жыл бұрын
Did I say this in the video? If not, then I did a terrible job explaining domain services. Your metapher is really on point.
@sotsch9280
@sotsch9280 Жыл бұрын
@@Codewrinkles No! your explanation was great, just had the feeling to add this comment! Didn't saw any Video explaining DomainServices as well as this.
@simonecambursano9266
@simonecambursano9266 Жыл бұрын
Great video as usual! I have a question though. Let's say I have aggregate A which has a nested list of aggregates of type B. I am also using the combination of values of a certain property of aggregates B in the list to enforce a certain business rule on the parent aggregate A. How would I do this without referencing the child aggregates in the list as plain objects?
@Codewrinkles
@Codewrinkles Жыл бұрын
The response to this is that the domain needs to be probably remodeled. It's the scenario that I have covered in the previous video when instead having Employees in the Project, we have create a new entity called ProjectMember and Employee has become an own aggregate. So in that case the Project Aggregate can manage its invariants and the Employee aggregate manages its own.
@sanjaydebnath
@sanjaydebnath Жыл бұрын
Great to-the-point explanation. Quick question, how do you handle the scenario when any of the 3 transactions fails to commit? Should we plan for reverting back previous ones too as part of same handler? More of a generic question while saving multiple aggregates.
@Codewrinkles
@Codewrinkles Жыл бұрын
That question doesn't have a simple answer because it really depends on the business requirements. I'm a big advocate of eventual consistency when it comes to cross-aggregate scenarios. So if an aggregate is saved correctly, then we don't revert the change as the aggregate is a boundary of consistency and it shouldn't care if other transactions failed or not. Usually there would be retry mechanisms or services that would do reconciliation. In my case, if the Game gets saved, the came shouldn't really care if the Player aggregate managed to successfully save the new elo rating. There might be scenarios, however, in which eventual consistency is not good enough: an example for that would be payments or similar stuff. So, it really depends on a case by case basis. Starting next week I will have a series on persistence in DDD, btw. So make sure to stay tuned :)
@sanjaydebnath
@sanjaydebnath Жыл бұрын
@@Codewrinkles Thanks, makes sense. In most cases, retry might just solve this easily and this is by default these days like in EF Core or cosmos client.
@JohnMcclaned
@JohnMcclaned Жыл бұрын
​ @Codewrinkles @Sanjay, Would it make sense to enforce immutability within a domain service? So any changes are applied to a new instance of that aggregates created within. the Domain Service? Therefore if an exception is thrown, no partial changes would have been applied to the aggregates passed into the service. Also, regarding persistence. Might it be possible to pass in a transactional commit context from the infra layers into a use case, such that each repository "save" can use the same context (if you really wanted to avoid eventual consistency).
@Codewrinkles
@Codewrinkles Жыл бұрын
@@JohnMcclaned Immutability at the service level is for sure something that needs to be kept track on. However, that is not really the problem. The problem is with persisting state. So, regarding your point on persistence, for sure we'll have access to all the DB stuff in the use case. The dilemma still remains, however. Because keeping in mind that aggregates are consistency and transactional boundaries, we should save each aggregate in a different transaction. And that's eventual consistency. If you follow DDD, then eventual consistency is what you end up. As said, that's not a big problem necessarily. There are huge event sourced apps out there, and in event sourcing you don't really get any other type of consistency. However, as you proposed, if from a business need you would want to have all your aggregates saved at the same time (or not at all if there is an exception somewhere) then you could for sure place everything in a single transaction.
@pilotboba
@pilotboba Жыл бұрын
In this example, doesn't the application need to know about domain rules in order to know to call the calculate rating in the domain? Is this something that could be handled by domain events? And domain event handlers?
@Codewrinkles
@Codewrinkles Жыл бұрын
Theoretically it could be handled through domain events as well, though in that case the application also needs to know about how to handle this one way or another. The application layer is an orchestrator of the entire application and therefore it's not a problem that it needs to know that a rating calculator exists and it should call it. The only thing that matters is that the calculation itself is not performed in the application layer. Domain events are very useful, but bring another level of complexity. In my opinion using domain events should be done when we need to handle disconnected logic. Like I update something on an aggregate and that update needs to be processed also by a totally unrelated aggregate. Services, on the other side, are useful when we have one common rule that involves two or more aggregates at the same time. It is also a very simple approach.
@pilotboba
@pilotboba Жыл бұрын
@@Codewrinkles nice. Thanks for your response.
@marcinrakowski3496
@marcinrakowski3496 Жыл бұрын
If you don't have interfaces for your domain services, why don't you use that services as static classes? They don't have any state inside. What is the purpose of registering them on the DI container?
@Codewrinkles
@Codewrinkles Жыл бұрын
As I replied in another comment, I don't see any problem in using a static classes for this purpose. I just try to stay away from static classes (call me an OOP purist) but this would be a suitable use case.
@marcinrakowski3496
@marcinrakowski3496 Жыл бұрын
Thanks for your answer, your video is great. Just for me not every code should be fully object oriented, even in the DDD space. Your example is just pure function :)
@Yarkendar
@Yarkendar Жыл бұрын
When video about persistance ?
@Codewrinkles
@Codewrinkles Жыл бұрын
Hopefully next Monday, the video will be about persistence :)
@jackyzhang3883
@jackyzhang3883 Жыл бұрын
Why not put the calculate logic into the Player which accept the Game Result, and call this method in the Handler for 2 players, the calculate logic seems should belong to the Player not in the a separate service.
@Codewrinkles
@Codewrinkles Жыл бұрын
When you play chess, as a player, do you know exactly how your ELO is calculated?
@jackyzhang3883
@jackyzhang3883 Жыл бұрын
@@Codewrinkles From your video, it seems the rating only related to the result for the player itself, that's why I think it make more sense to put such logic into the player. But with the ELO system, I agree player shouldn't know how to calculate the rating(It actually relate to other player's result, not just player itself). It that case, maybe a ELO class will be more suitable for a Calculator, the ELO class maybe could include all the involved players and can calculate the rating for each player which more like another domain object. Thanks for your video, just some thoughts.
@Codewrinkles
@Codewrinkles Жыл бұрын
@@jackyzhang3883 These discussions are important. Because DDD in general is not straightforward. And this type of challenges and uncertainties will come up very often. What I found out working like this is that we are biased to structure and understand classes in a very data-driven, technical way. However, in DDD that is not the case. We need to always start from the business and the ubiquitous language. That's why I asked the question about you as a player, do you know how the ELO is calculated? Because I ask myself this question all the time. Instead of focusing on the technical part, I focus on the business: so, does a chess player how the ELO is calculated? Obviously not. What we try to model in our apps using DDD are real word concepts with their state, behavior and capabilities.
@jackyzhang3883
@jackyzhang3883 Жыл бұрын
@@Codewrinkles Thank you for your reply. I do agree that player shouldn't know how to calculate the ELO, since it will touch our player's result. What I feel uncomfortable about is the class RateCalculator, it is a pure stateless class which will accept the 2 players and the results. But the real ELO system require all the players to calculate the result for each player and do we want to pass al the players every time when calculate the result? That's why I think maybe a ELO class which capture all the players will better to deal with a real ELO system. What do you think?
@Codewrinkles
@Codewrinkles Жыл бұрын
@@jackyzhang3883 Ok, I agree that the naming of the class is probably not the best. A class representing the Elo would be a good idea. But according to DDD we couldn't really make it an aggregate because it would need to nest other aggregates (players) and that is not ok. Services in DDD (as stated in the video) should be stateless classes. However, I don't really say we need to always follow 100% DDD. In my experience, for most applications the majority of developers work on, full DDD is overengineering. But since this is a series on DDD, i want it to keep it 100% adherent to DDD principles.
How To Structure Your DDD Project In A Smart Way?
13:17
Codewrinkles
Рет қаралды 6 М.
Alat Seru Penolong untuk Mimpi Indah Bayi!
00:31
Let's GLOW! Indonesian
Рет қаралды 10 МЛН
버블티로 체감되는 요즘 물가
00:16
진영민yeongmin
Рет қаралды 89 МЛН
Settling the Biggest Await Async Debate in .NET
14:47
Nick Chapsas
Рет қаралды 140 М.
How to await ANYTHING in C#
8:47
Nick Chapsas
Рет қаралды 45 М.
Bob Nystrom - Is There More to Game Architecture than ECS?
23:06
Roguelike Celebration
Рет қаралды 193 М.
Mastering DDD Aggregate Modeling With THESE 3 Steps
17:26
Codewrinkles
Рет қаралды 10 М.
The Identity Paradox | DDD, EF Core & Strongly Typed IDs
16:15
Amichai Mantinband
Рет қаралды 27 М.
ИГРОВОВЫЙ НОУТ ASUS ЗА 57 тысяч
25:33
Ремонтяш
Рет қаралды 278 М.
Cadiz smart lock official account unlocks the aesthetics of returning home
0:30