I can't believe it, I was having an argument in my work about the exact same concern, and after work I opened UA-cam and as if you were a Deus ex machina you appear to address the issue. Thank you so much. Each and every video you make is really touching hot topics in a very enlightening way.
Then how do you handle cases where what the transaction script is doing requires services that are injected into, say, the mediator handler? How do you move that logic to the aggregate root class? Would you inject services into the aggregate root too? This seems like such a basic/fundamental problem and yet I barely see anyone talking about it.
In real world application business logic for future dates etc will make this even more important. Transaction script has its place but pick / drop off logic could grow become harder to reason about (i.e. increase complexity). Great example.
Intro: All modern backend applications works with data. Handling data with care is very important. Question: could we treat data save and load operations as part of businnes logic?
Great video. Could you please clarify one question. Case: I want to mark entity as disabled, for example. And I have two options to achive the result. First option: Load data from DB, convert dao object into DomainObject, call "Disable" method on domain object which actually just set Disabled = true, and last call repository/storage to save my domain object back to DB. And second option is simple call the specific storage method like DisableEntityById which finally call the DB like update entity set disabled = true where id = x. It's obvious the second option is the fastest. It's not require loading objects from db, dao to domain and back conversion. The first option keeps the businness logic in one place (entity object) and it looks more ddd friendly. My reason looks like this: For really simple operations I want to have an options to avoid dao to domain translation, but for real businness logic I want to use reach domain objects. And the problem is this two situation could be presented in one object. Could you please give your opinion about this P.S. If it's ok for you we could discuss this topic deeply, feel free to contact me.
Option two works for scenarios where it's purely CRUD and will not require any logic. This is because you could end up with many different places that are making that state change. If you need control that state change, you want it in a single location, which could be an object. Option 1 is that option which allows you to control that state change.
We do it differently. We built a platform where the case management business rules are captured using domain specific constructs and then we do the hard work of generating the underlying implementation. We can target various technology stacks and even evolve your application as new technology because available.
In general, you shouldn't have `http` or `sql` type imports in your "business logic" classes. The whole point of the controller is to isolate the communication domain, and the repository is to isolate the persistence domain, from your actual business domain. This allows you to test your business logic w/o having to mock half the tech stack. You can also change out those exact implementations of each domain where you're really only bound to your API. Think long and hard about your apis, the more you promise the harder it is to keep it. Non/blocking apis are viral and switching is non-trivial, consider if it's worth advertising an async api even if your current implementation isn't async.
I loved the flow of the video on how you start with a small (real world problem) and add complexity to that over time. Lately I've been trying to apply Clean Architecture and CQRD as much as possible (your videos have helped a lot btw :p). Whereabouts would you include Aggregate classes? In the same location as the actual Handlers? Thanks and keep up the GREAT work
I generally do yes, because I follow more a Vertical Slice Architecture. Some folks like to keep aggregates in the core domain completely separate in another project that has no dependencies in more of a Clean Architecture.
Transaction script is pain in big applications. But unfortunately there's a lot of domain logic that involves interaction and consistency between different collection of entities -> Eg: checking uniqueness of a customer name - and it's difficult to organize that logic in a way different than walls of service-layer based procedural code. I understand that the alternative is defining aggregates, but their design is somewhat limited by performance concerns, such as having big cardinality in root's child entities.
Uniqueness or duplicates is often brought up. In some situations, duplicates aren't really a problem. In other situations they are absolutely a problem and must be unique. One solution is to use the reservation pattern. I should probably create a video about that to address this.
@@CodeOpinion I'd propose a simpler solution that's sufficient in most cases - when using SQL database, enforce uniqueness via index, as that's probably the reason you are worrying about duplication in the first place. If that's impossible, let's say you are using document database without support for unique indices, domain service is a viable place for such check. But honestly, as you were saying and I think it needs to be emphasized, it's rather rare that you care about uniqueness, and much rarer that it's an actual business logic concern.
@@CodeOpinion Corect me if I'm wrong, but the reservation pattern is basically about putting a lock on something across services, what the OP here is referring to has nothing to do with that. See Ardalis's DDD-NoDuplicates GitHub repo, which demonstrates the problem being referred to here.
Fully agree with everything you said. I like the simplicity of fetching the aggregate, doing something with it and saving it. The only problem i foresee is that the aggregate can become very bloated with logic. Is there also a point where you want do delegate the logic in a bloated aggregate to separate state transitions or domain services?
I don't think an Aggregate can ever be considered bloated if everything you are putting there belongs there and you have done a thorough research on finding good aggregate roots for your domain.
That's a very good point, thank you. However, while using the example in the video, I can imagine more transitions will be added in the future (e.g. PartiallyDeliver, Redeliver, Lost). At some point, a state machine/saga feels more appropriate that might be refactored in multiple classes for readability, especially when the transitions are not just lineair.
It's not so much about behaviors that drive an aggregate bur rather the invariants you need to control and it being a consistency boundary. I've done a video about this exact topic. ua-cam.com/video/64ngP-aUYPc/v-deo.html
@@CodeOpinion Thanks, that video gave some nice insights! I like most parts of DDD, but prefer anemic models over aggregates with behavior. But I can apply the concepts of invariants by moving the behavior part to some kind of domain services. Or is that a big mistake in your eyes?
Absolutely great videos. Do you plan anything on reporting, calculations and composing ui for that? I work with a domain that needs to be queried and calculated with given time range selected. I find that reporting bit gets messy because all the entities from different contexts are coming into one place, creating new business values that are valid only in this particular report. Also UI in reports might be tricky when there is a need to both calculate results for web and to show the same calculations but projected to spreadsheet tables and formulas.
Yes, I'll try and cover some type of reporting in the future. It's also in line in some ways with UI composition. However, in a lot of situations, it's no different than any type of data warehousing that's done on a periodic basis if the data can be a hours or days stale.
Hi, this is really relevant to our app. Would you say the implementation for domain logic you illustrate here is mutually exclusive with Unit Of Work, or could you see a possible approach where both are employed? Thanks
An aggregate is a consistency boundary. So technically you wouldn't need a unit of work. But in the real world, you may want to change more than one aggregate at a time. For this an transaction that spans the aggregates within a logical boundary. It's not that shouldn't use a UoW, but it can be a sign that if you need one, you may want to a look at using an aggregate.
How you would put in the domain level logic some rule where you need to go in the database, eg: say you are adding an order with an item code, and you need to check if the item exists in the database? I meant, I wouldn`t put the Repository inside the entity. I could put the validation in the handler, but then I would not be validating in the domain level :S
It can go in a call/layer above or use double dispatch within your domain. A lot of this depends on requirements around consistency. ua-cam.com/video/uKURpE12Mgo/v-deo.html
As I was adding to the transaction script, I mentioned I was going to publish an event for integration purposes, like sending a push notification to the mobile app. TO maintain that functionality, I was creating the event within the aggregate and exposing that list of events, which ultimately our repository would use after it persisted our state, it would publish those events to our message broker
Maybe I do not get it: to protect entities from external change it's properties (boundaries), the "setters" has to be private. With aggregates you modify the required entity properties. But in your samples you are not held with this principle!? Because there are entity properties with public setters, which made it possible to make un controlled changes. Or!?
Check out this video where I talk about this. ua-cam.com/video/GtWVGJp061A/v-deo.html A lot of the issues related to this are specifically because of needing to conform to an ORM.
@@CodeOpinion In the video code examples are still public properties; for example: it is still possible to add items with shoppingcartitems Items list which held a public setter. Why? I thought this behaviour is not appropriate, because the root aggregate has a method to add shopping cart item entity.
@@xance The aggregate root is the public API, if you want to segregate that into a separate assembly where it's public but others are internal using access modifiers, so be it.
I've had the exact same situation in one of my projects, where I used transaction scripts. As business got more and more mature, I found myself calling multiple transactions within a single call. This especially becomes the case when you have lots of business logic validation. What about the situation where you need to deal with validation across multiple aggregates? Would you let them interact in a handler?
I'm trying to think of a situation I've been in where there is validation across multiple aggregates. An aggregate is a consistency boundary. While you may call some type of validation within a handler, it's not likely going to be consistent with the aggregate. I've talked about that in this video: ua-cam.com/video/uKURpE12Mgo/v-deo.html
@@CodeOpinion extremely helpful video as well! Basically the key takeaway is to try organizing the aggregates in such a way as to avoid the inter-boundary logic. If that's not possible, we need to ask the business if we can have a "promised" piece of data to live within the context where it's being frequently used from.
I would solve this using a predefined possible state transitions map stateXz to [stateXz2, stateXz3] and check possible actions about the current state of the shipment
Also I think jumping to DDD and effectively 'moving/hiding' that logic away into domain models is a cop out. There is usually ways to refactor and abstract your core business logic out in a way that you can still have nice clean abstractions. That logic still has to exist, it still has to live somewhere, it's just now being moved into a domain model. IMO that becomes the anti-pattern, the amount of times I've seen that domain model become a dumping ground for state, people piling in more and more logic.. Before you know it, it ends up becoming an application's worth of logic, within it self. It just simply doesn't scale and is hard to keep under control. Programming to interfaces and abstractions gives you the liberty of making an easier decision, try to fix that problem sensibly before jumping into DDD and hiding logic away.. Also if you need to liberate a monolithic solution into smaller API's or smaller services, if you don't have clean abstractions that do 'one thing' how do you pull it out? If all your logic is in the DTOs/models.. That will be far more difficult to untangle as your implementation is directly baked to a physical 'thing'. If you have anemic dto's then changes to the core business logic wouldn't directly impact data persistance, it'll continue to work as-is.
I think there's a place for anemic and a place where it falls short. I don't by the "before you know it" part though. That can happen with anything if you aren't able/willing to do continuous design and make changes when required. "before you know it" can happen and turn into a 10k line transaction script just a a god domain model. If you let it happen.
I can't believe it, I was having an argument in my work about the exact same concern, and after work I opened UA-cam and as if you were a Deus ex machina you appear to address the issue. Thank you so much. Each and every video you make is really touching hot topics in a very enlightening way.
Excellent!
Then how do you handle cases where what the transaction script is doing requires services that are injected into, say, the mediator handler? How do you move that logic to the aggregate root class? Would you inject services into the aggregate root too?
This seems like such a basic/fundamental problem and yet I barely see anyone talking about it.
Hi I don't code on c# I don't have any relationship with c# at all but your content helps me to become better software developer ❤️❤️
Thanks! Great to hear!
This is a really really really good video. I think it shows code in a way that many people could spend hours searching for.
Excellent criteria for detecting when transaction script starts to be a dead-end: When we start to chain it, that's the end of it!
Yes, that's a good way say it.
I love just watching clean code being written.
In real world application business logic for future dates etc will make this even more important. Transaction script has its place but pick / drop off logic could grow become harder to reason about (i.e. increase complexity). Great example.
Intro: All modern backend applications works with data. Handling data with care is very important.
Question: could we treat data save and load operations as part of businnes logic?
Great video. Could you please clarify one question. Case: I want to mark entity as disabled, for example. And I have two options to achive the result. First option: Load data from DB, convert dao object into DomainObject, call "Disable" method on domain object which actually just set Disabled = true, and last call repository/storage to save my domain object back to DB. And second option is simple call the specific storage method like DisableEntityById which finally call the DB like update entity set disabled = true where id = x. It's obvious the second option is the fastest. It's not require loading objects from db, dao to domain and back conversion. The first option keeps the businness logic in one place (entity object) and it looks more ddd friendly. My reason looks like this: For really simple operations I want to have an options to avoid dao to domain translation, but for real businness logic I want to use reach domain objects. And the problem is this two situation could be presented in one object. Could you please give your opinion about this P.S. If it's ok for you we could discuss this topic deeply, feel free to contact me.
Option two works for scenarios where it's purely CRUD and will not require any logic. This is because you could end up with many different places that are making that state change. If you need control that state change, you want it in a single location, which could be an object. Option 1 is that option which allows you to control that state change.
We do it differently. We built a platform where the case management business rules are captured using domain specific constructs and then we do the hard work of generating the underlying implementation. We can target various technology stacks and even evolve your application as new technology because available.
In general, you shouldn't have `http` or `sql` type imports in your "business logic" classes. The whole point of the controller is to isolate the communication domain, and the repository is to isolate the persistence domain, from your actual business domain. This allows you to test your business logic w/o having to mock half the tech stack. You can also change out those exact implementations of each domain where you're really only bound to your API.
Think long and hard about your apis, the more you promise the harder it is to keep it. Non/blocking apis are viral and switching is non-trivial, consider if it's worth advertising an async api even if your current implementation isn't async.
I loved the flow of the video on how you start with a small (real world problem) and add complexity to that over time.
Lately I've been trying to apply Clean Architecture and CQRD as much as possible (your videos have helped a lot btw :p). Whereabouts would you include Aggregate classes? In the same location as the actual Handlers?
Thanks and keep up the GREAT work
I generally do yes, because I follow more a Vertical Slice Architecture. Some folks like to keep aggregates in the core domain completely separate in another project that has no dependencies in more of a Clean Architecture.
@@CodeOpinion
More of a Clean Architecture or an Onion Architecture if he needs to visualize it better. :)
Transaction script is pain in big applications. But unfortunately there's a lot of domain logic that involves interaction and consistency between different collection of entities -> Eg: checking uniqueness of a customer name - and it's difficult to organize that logic in a way different than walls of service-layer based procedural code. I understand that the alternative is defining aggregates, but their design is somewhat limited by performance concerns, such as having big cardinality in root's child entities.
Uniqueness or duplicates is often brought up. In some situations, duplicates aren't really a problem. In other situations they are absolutely a problem and must be unique. One solution is to use the reservation pattern. I should probably create a video about that to address this.
@@CodeOpinion I'd propose a simpler solution that's sufficient in most cases - when using SQL database, enforce uniqueness via index, as that's probably the reason you are worrying about duplication in the first place. If that's impossible, let's say you are using document database without support for unique indices, domain service is a viable place for such check. But honestly, as you were saying and I think it needs to be emphasized, it's rather rare that you care about uniqueness, and much rarer that it's an actual business logic concern.
@@CodeOpinion
I'm looking forward to that video, I wanted to bring the issue the OP just mentioned.
@@CodeOpinion Corect me if I'm wrong, but the reservation pattern is basically about putting a lock on something across services, what the OP here is referring to has nothing to do with that. See Ardalis's DDD-NoDuplicates GitHub repo, which demonstrates the problem being referred to here.
Fully agree with everything you said. I like the simplicity of fetching the aggregate, doing something with it and saving it.
The only problem i foresee is that the aggregate can become very bloated with logic. Is there also a point where you want do delegate the logic in a bloated aggregate to separate state transitions or domain services?
I don't think an Aggregate can ever be considered bloated if everything you are putting there belongs there and you have done a thorough research on finding good aggregate roots for your domain.
That's a very good point, thank you. However, while using the example in the video, I can imagine more transitions will be added in the future (e.g. PartiallyDeliver, Redeliver, Lost). At some point, a state machine/saga feels more appropriate that might be refactored in multiple classes for readability, especially when the transitions are not just lineair.
It's not so much about behaviors that drive an aggregate bur rather the invariants you need to control and it being a consistency boundary. I've done a video about this exact topic. ua-cam.com/video/64ngP-aUYPc/v-deo.html
@@CodeOpinion Thanks, that video gave some nice insights! I like most parts of DDD, but prefer anemic models over aggregates with behavior. But I can apply the concepts of invariants by moving the behavior part to some kind of domain services. Or is that a big mistake in your eyes?
Absolutely great videos. Do you plan anything on reporting, calculations and composing ui for that? I work with a domain that needs to be queried and calculated with given time range selected. I find that reporting bit gets messy because all the entities from different contexts are coming into one place, creating new business values that are valid only in this particular report. Also UI in reports might be tricky when there is a need to both calculate results for web and to show the same calculations but projected to spreadsheet tables and formulas.
Yes, I'll try and cover some type of reporting in the future. It's also in line in some ways with UI composition. However, in a lot of situations, it's no different than any type of data warehousing that's done on a periodic basis if the data can be a hours or days stale.
Hi, this is really relevant to our app. Would you say the implementation for domain logic you illustrate here is mutually exclusive with Unit Of Work, or could you see a possible approach where both are employed? Thanks
An aggregate is a consistency boundary. So technically you wouldn't need a unit of work. But in the real world, you may want to change more than one aggregate at a time. For this an transaction that spans the aggregates within a logical boundary. It's not that shouldn't use a UoW, but it can be a sign that if you need one, you may want to a look at using an aggregate.
How you would put in the domain level logic some rule where you need to go in the database, eg: say you are adding an order with an item code, and you need to check if the item exists in the database? I meant, I wouldn`t put the Repository inside the entity. I could put the validation in the handler, but then I would not be validating in the domain level :S
It can go in a call/layer above or use double dispatch within your domain. A lot of this depends on requirements around consistency. ua-cam.com/video/uKURpE12Mgo/v-deo.html
what is the purpose of having a lists of events in your aggregate root , if we use an ORM like EF which will keep track of all those events?
As I was adding to the transaction script, I mentioned I was going to publish an event for integration purposes, like sending a push notification to the mobile app. TO maintain that functionality, I was creating the event within the aggregate and exposing that list of events, which ultimately our repository would use after it persisted our state, it would publish those events to our message broker
Maybe I do not get it: to protect entities from external change it's properties (boundaries), the "setters" has to be private. With aggregates you modify the required entity properties. But in your samples you are not held with this principle!? Because there are entity properties with public setters, which made it possible to make un controlled changes. Or!?
Check out this video where I talk about this. ua-cam.com/video/GtWVGJp061A/v-deo.html
A lot of the issues related to this are specifically because of needing to conform to an ORM.
@@CodeOpinion In the video code examples are still public properties; for example: it is still possible to add items with shoppingcartitems Items list which held a public setter. Why? I thought this behaviour is not appropriate, because the root aggregate has a method to add shopping cart item entity.
@@xance The aggregate root is the public API, if you want to segregate that into a separate assembly where it's public but others are internal using access modifiers, so be it.
@@CodeOpinion No offence. Agree, with how far you would go, is how complex you get in the end (a dogma).
I've had the exact same situation in one of my projects, where I used transaction scripts. As business got more and more mature, I found myself calling multiple transactions within a single call. This especially becomes the case when you have lots of business logic validation.
What about the situation where you need to deal with validation across multiple aggregates? Would you let them interact in a handler?
I'm trying to think of a situation I've been in where there is validation across multiple aggregates. An aggregate is a consistency boundary. While you may call some type of validation within a handler, it's not likely going to be consistent with the aggregate. I've talked about that in this video: ua-cam.com/video/uKURpE12Mgo/v-deo.html
@@CodeOpinion extremely helpful video as well!
Basically the key takeaway is to try organizing the aggregates in such a way as to avoid the inter-boundary logic. If that's not possible, we need to ask the business if we can have a "promised" piece of data to live within the context where it's being frequently used from.
I would solve this using a predefined possible state transitions map stateXz to [stateXz2, stateXz3] and check possible actions about the current state of the shipment
I feel silly for asking but… what’s a ‘transaction script’ in the context of this lecture?
The first example where I had a "handler" for a request.
Thank you for such amazing content!
Thanks for watching! Appreciate the support!
Also I think jumping to DDD and effectively 'moving/hiding' that logic away into domain models is a cop out. There is usually ways to refactor and abstract your core business logic out in a way that you can still have nice clean abstractions. That logic still has to exist, it still has to live somewhere, it's just now being moved into a domain model.
IMO that becomes the anti-pattern, the amount of times I've seen that domain model become a dumping ground for state, people piling in more and more logic..
Before you know it, it ends up becoming an application's worth of logic, within it self. It just simply doesn't scale and is hard to keep under control.
Programming to interfaces and abstractions gives you the liberty of making an easier decision, try to fix that problem sensibly before jumping into DDD and hiding logic away..
Also if you need to liberate a monolithic solution into smaller API's or smaller services, if you don't have clean abstractions that do 'one thing' how do you pull it out?
If all your logic is in the DTOs/models.. That will be far more difficult to untangle as your implementation is directly baked to a physical 'thing'.
If you have anemic dto's then changes to the core business logic wouldn't directly impact data persistance, it'll continue to work as-is.
I think there's a place for anemic and a place where it falls short. I don't by the "before you know it" part though. That can happen with anything if you aren't able/willing to do continuous design and make changes when required. "before you know it" can happen and turn into a 10k line transaction script just a a god domain model. If you let it happen.