Shawn should become USA's next president. This is too good!
@craigmunday3707Ай бұрын
My understanding of domain events is that the application layer has a hand in handling domain events and calling the aggregate. In your example the application layer isnt involved when the stream of events are rehydrated back to the aggregate. I like the approach but wonder if what being stored in the event data is not only the domain event but also the delta of the aggregate.
@necrotikSАй бұрын
Your videos are helping me a lot. Those have the best explanations I have seen so far, I'm really starting to understand it. I haven't finished the course, you might answer these questions later, but I'll make them so I don't forget: Questions: 1. You're passing only the EmployeeId, but, let's say our business requires us to check if the Emploeey has permission to confirm an Order. Where/How would you make this permission check, since you're passing only the Id? 2. Your passing a "Products" object, which is a Collection of Product objects. Why aren't you passing an array (or Collection) of ProductId's? How do you know when you need to pass objects or just Ids? 3. Let's say you're running a SaaS, and using the same database for every Tenant, and the products table have a tenant_id. Where would you make the check that the Products passed to the Order actually belong to the correct Store (Tenant), for example? Maybe there's already an answer for these type of questions, if you have a video or content to recommend me, I'd appreciate it.
@ShawnMcCoolАй бұрын
I don't think I'm going to be able to go in much depth with many of the questions, but I'll do my best to try to explain how I would think about them. I hope that I'm not too flippant about these concepts and send you in the wrong direction. I'm just unable to spend the time to consider them too deeply right now. 1. If you have a completely separate authorization system, then you don't have authorization events in your model. You simplify the Order model and you still can have employee ids communicating who made what changes, but a separate subsystem can handle authorization. There are many authorization frameworks you might even choose to use. 2. The Products type could just be an object that ensures that you've constructed a valid collection of Products. It could have convenience functions on it etc. In the code example linked in the description I seemed to have used an array. 3. Perhaps I would make it so that the Products are only ever loaded such that they're protected behind the correct tenant id. It's hard to say for me exactly how I might do this. But there ARE things you could do --for example-- include a tenant id in your event store (if that's relevant, I don't know), which would ensure that when loading event streams you're always limited to the correct tenant scope. I think you need to look at the various needs you meet and toy with the idea in order to find what's right for you.
@necrotikSАй бұрын
@@ShawnMcCool thank you for the answer. And I wasn't expecting for a deeper explanation, I know this is a complex topic, this was already very helpful. What I got from your answer is that I can do those things is "several" different ways. I could have some Service that before building the Aggregate, let's say, placing the Order, makes all those check I talked about, and then the Order Aggregate is responsible only for the "Order" itself, meaning, it expects the data passed to it to already be "correct", because we know we used some "Middleware", for example, before reaching that code. So, about the correct Tenant data, I could assume, on my Domain code, that all data inside it has already passed through some Tenant check mechanism. I don't have to check it there. This would be specific to my use case. But for some other system, let's say, they might want to check Tenant inside a Aggregate... It all depends on the needs.
@SuperKizle4 ай бұрын
You explain the concepts of event sourcing by far in the clearest and most intuitive way I've found anywhere. Big kudos!
@ShawnMcCool4 ай бұрын
Thank you for the kind words. It's really nice to get a notification like this out of nowhere!
@mentisdominus4 ай бұрын
Hi @ShawnMcCool, did you ever create a video that explains the command bus?
@mentisdominus5 ай бұрын
@ShawnMcCool Thank you for the great instruction. I got tripped up at time 5:03, "it's important to observe the time that we use for the check should be the day that in the HourHasPassed event. We're not guaranteed these events will make it to our process manager immediately." I think the first sentence would be clearer as "...the *hour* that we use for the check must *include* the day that is *contained* within the HourHasPassed event's timestamp". Check my understanding. It means that the HourHasPassed event must include the full date time. The event could be processed by the process manager some days later. For example, a cron job generates an event at 2021-08-09T23:00:00Z. Due to an overwhelming number of events, the Process Manager does not process that event until 2021-08-10T01:05:03Z (early morning the next day). If the Process Manager incorrectly uses the day the system processes that event, then the wrong set of carts would be identified as being abandoned -- some abandoned carts would be ignored and some additional carts would be included. This throws of the consistency of the state of the system and causes confusion when analyzing the output of the system.
@ShawnMcCool5 ай бұрын
I don't mind relying on the event system being fairly up to date, as it's all monitored with alerts. You ultimately have to rely on the fundamentals of system. But it doesn't mean you shouldn't consider the network when it comes to implementation. Handling duplicated messages, messages out of order, and considering timeouts and delays are all important.
@ShawnMcCool5 ай бұрын
Also thank you very much for the reply. Probably you're right in that it would be clearer.
@mentisdominus5 ай бұрын
@@ShawnMcCool Awesome, thank you for pointing out these additional considerations.
@mentisdominus5 ай бұрын
19:15 The fulfill intention's invariant check for fulfilledBy should not be negated. @ShawnMcCool thank you for this excellent lesson and series!
@DodaGarcia5 ай бұрын
Your content is honestly amazing. I use custom software to power most of my business and have dabbled with event sourcing for some of the systems for years, so I've seen lots of resources on the topic and can say the depth of your explanations of event sourcing (not just as a technical concept but how it relates to business domains) is unparalleled on KZbin.
@ShawnMcCool5 ай бұрын
That's a very nice thing to say to someone. Thank you so much, you made my day.
@bakre_dev87286 ай бұрын
1A
@PedroGarcía-b5r7 ай бұрын
Incredible explanation!!! Thank you so much!
@BelkaRuda18 ай бұрын
This video is amazing
@RohitKumar-fg1qv8 ай бұрын
superb explanation
@jasontruter49819 ай бұрын
State carried transfer vs notifications.
@SPribyt10 ай бұрын
best explanation
@ShawnMcCool10 ай бұрын
Thanks, I appreciate it mate!
@jwbonnett Жыл бұрын
You can still have data is 3NF, you can grab your "event_data" and split it into key value pairs, store each in another table "event_data" with fields (id, event_id, key, value) that is linked to the "event_store" table with the fields (id, stream_id, event_name, meta_data, stored_at). No event type tables needed.
@ShawnMcCool Жыл бұрын
If I understand correctly, you're suggesting creating relations for each event type. May I ask why you would want that?
@jwbonnett Жыл бұрын
@@ShawnMcCool No, that is what was described in the video. I was giving an alternitive which would be 3NF. A table of events, and a table for key value pairs for each bit of data associated with said event, the event type is a field within the events table for the event that has happned. Doing this can mean you don't need to create extra copies of data for things like the reservation pattern, as you already have the data normalised and able to query it in a effective way.
@clintwinter4802 Жыл бұрын
Having never read about event sourcing before, after you revealed the body of the `rebuildFrom` method I was like "oh shit, that's sweet!"
@ShawnMcCool Жыл бұрын
Yeah fun stuff! The state of an aggregate is a fold over the event stream.
@paradoxeng3210 Жыл бұрын
"Order is the process, aggregates are modeled processes" I spent some time on the net trying to understand what's the aggregate, But I just understood now thanks to this video. Thank you very much for sharing this knowledge for free.
@Ffaarg7 Жыл бұрын
Thanks for this series. We are starting to do build reporting models off a business domain events. The challenge is definitely getting everything happening through a business operation that raises a well formed business event. On a technical level. Would you still use a UUID. I switched to ULIDs when I saw the performance problems - not an event sourced application. I know the latest UUID versions are time ordered as well and that you can store them as binary. Just wondering what you use now for IDs.
@ShawnMcCool Жыл бұрын
Hi there, in relatively large scale systems we're using UUIDs. They can be indeed be stored as binary. Largely I don't think that the exact algorithm matters so long as it doesn't require any other knowledge in order to be generated. If they can be generated in isolation and be unique, it's fine.
@Ffaarg7 Жыл бұрын
@@ShawnMcCoolThanks for the feedback.
@sillycoder9690 Жыл бұрын
This is pure gold!!! Thank you very much for the best explanation I have ever got about model, consistency, identity, business rules and how they all intertwine.
@ShawnMcCool Жыл бұрын
🥲Thank you so much. That's a very nice thing to hear.
@Schneller2000 Жыл бұрын
The fact that one instance of Money can access private variables from other instances of Money would actually be worth it's own episode 🤯
@shawnmc Жыл бұрын
Haha yeah. I remember when I learned that visibility was about knowledge encapsulation, it really made me rethink some things.
@joerithissen51632 жыл бұрын
I binge-watched this entire series last weekend. These are some of the best examples of applying DDD-principles in code I have come across. Where I work, we're currently in the process of rewriting our legacy back-end using DDD. It's very interesting to see how similar our technical approaches are, but there are some very interesting differences as well. Overall I would say your architecture is simpler and cleaner. There are definitely some ideas in these series I will be discussing with our team! Are you perhaps planning to visit DDD Europe or any other conferences this year?
@ShawnMcCool2 жыл бұрын
Thank you for the kind words! I worked hard on the series over the course of a year to put it together. I'm not particularly skilled at video production so it was a labor and to hear that you appreciated it means a lot. I really hope to make it to DDD Europe this year, it's my favorite conference. But it comes down to what my company chooses to do.
@AmirHosseinHonardust2 жыл бұрын
I'm a bit confused, I don't understand PHP that much. what does "->" and "raise" mean on line 6 at 5:11?
@ShawnMcCool2 жыл бұрын
The arrow is when you call a method on an object. Like object.method() in another language. raise is a method that we built into the aggregate class that accepts and handles a new event.
@envueltoenplastico2 жыл бұрын
Great stuff! Thanks for this. Possibly overthinking but I am wondering about an example here, let's imagine I am creating domain events for a system where someone enters some personal details and their address as part of an initial enquiry for some service. Is it better to create a single domain event named something like `EnquiryOpened` with the personal details and address as nested data in this event, or is there merit in decomposing this into multiple domain events; e.g: `EnquiryOpened` and `AddressRegistered` or something like that? Maybe in the future they might want to change their personal details or address (implying domain events like `PhoneNumberChanged` or `AddressChanged`. Cheers!
@ShawnMcCool2 жыл бұрын
Domain events are about business occurrences that need to be reacted to. If an address is corrected because it was incorrect or if a person moves to a new home, those might be interesting because the application needs to react to those events in order to trigger the correct behavior. You can't tell the difference between the two if the event is just address changed. It's about what kind of event needs to be reacted to, and not just what data changed. You can always react to enquiry opened by creating new specific effects that are generic, like a person's address changed, if that's useful. It's really based on your model of reaction.
@tqwewe2 жыл бұрын
How to we ensure our event handler is idempotent? Do we need to store the last event version in a separate database, and perform our sql queries in a transaction, updating our read model, and also our last event id?
@ShawnMcCool2 жыл бұрын
There's a lot of paths. Sometimes, you can just get away with every mutation inserting or updating specific values and the result is that any event that comes in, you can project into the data set in a way that isn't sequence-specific. Another way is to store data for each mutation that comes in, and compare the values that have already come in before updates. Etc. I personally don't have one right way or right answer. I've seen a number of patterns work for different teams.
@Ffaarg7 Жыл бұрын
@@ShawnMcCool Would you capture a UUID for each event that is raised. Handlers can then use this for idempotency. This is a path that I have followed but not in an event sourced system and wondering if you think it is applicable. There is also concurrent processing of events. Borrowing from message brokers I assume that the aggregate ID can be used as a message group to partition processing. I am thinking about external systems or adjacent applications responding to these events e.g. when the order is completed that triggers the creation of a fulfillment process or an order fulfillment aggregate. But the fulfillment system has responded to the order aggregate domain event. I realise this is tangental to event sourcing.
@ShawnMcCool Жыл бұрын
@@Ffaarg7 yes this all makes sense. We tend to try to find an idempotency mechanism that doesn't require extra state storage. But if not possible then event id is useful. We use mutex locks on state changes to prevent concurrent processing.
@nalcow2 жыл бұрын
Great stuff on event sourcing tóopics! Puré gold!
@diegoromero-lovo51122 жыл бұрын
crystal clear
@axelbrinck_2 жыл бұрын
amazing tutorial!!!
@ruicsf2 жыл бұрын
Finally, a video that explains it well and to the point. Thank you!
@hannananan94272 жыл бұрын
Underrated video, thanks!
@MM-iu4rg2 жыл бұрын
Very clear explanation. Thank you!
@erenylmaz16472 жыл бұрын
Great Content. Thank you
@manuelpineda4022 жыл бұрын
Thanks a lot! Amazing explanation.
@CursosDesarrolloWeb2 жыл бұрын
Awesome, thanks a lot!
@CursosDesarrolloWeb2 жыл бұрын
Very nice example, thanks a lot
@CursosDesarrolloWeb2 жыл бұрын
Really helpful, 100% agree, awesome.
@SU-yh1dj2 жыл бұрын
Wow this made a lot of sense. Thanks!
@romanmahotskyi68982 жыл бұрын
Hi, thank you for such a good explanation. Notifying clients via email whenever something happened in a domain seems clear to me as well as notifying other services (in other subdomains) that depends on this event. Are there other examples of what domain events are for?
@ShawnMcCool2 жыл бұрын
Events are a decoupling pattern. You can use them whenever 'one thing happening' should trigger 'another thing'. Sometimes this is done by making a method call, you make a method call, get back a value.. maybe it's an entity or boolean or maybe you just ran a method and now know that it's time for 'the next thing'. This connection exists in code, in a specific place. In order to add anything (for example) you would need to modify that place in code. With events as a decoupling pattern, you can raise the event and then anywhere you can add an event listener to the dispatcher, you can extend the code with more behavior, without having to modify the exact place in the code where the event was dispatched.
@alikaviani64782 жыл бұрын
Nice work Shawn!
@alihammadshah3 жыл бұрын
Order seems like a finite state machine with events as inputs to change the state.
@ShawnMcCool3 ай бұрын
Everything is a state machine you're right. :)
@alihammadshah3 жыл бұрын
Thank you so much. This is invaluable content. Explaining models as abstracted conceptual version of reality was really helpful for me. It sounds similar to physical models, where models are conceptual intermediaries (representings) of the physical world (represented) and, are validated through scientific method. This similarity really helped in understanding models. Respresentings and represented are also isomorphic and the examples scholar gives is of algebra (representing) and geometry (represented). So all i all I now understand models similarly as in they are isomorphic to business objects and processes and validation I suppose is done through unit testing.
@alihammadshah3 жыл бұрын
Another thought was about physical models essentially modeling regularities in the physical world similarly model probably can be thought of concepts defining regularities in a business.
@tydalm.96653 жыл бұрын
+1 for trying to teach Laravel users software development. Evereybody who thinks active record anti-pattern, accessing a persistence layer through a static function, or having business/domain code depending on a framework is a good idea, needs help seriously. But who knows, even Laravel users could turn into software developers over time.
@pavelnemoi3 жыл бұрын
Thanks for the video! But isn't it just easier to make a cart an aggregate/entity to raise its own events and have appropriate listeners for such a use case like AddProductIntoCart, etc? Then with a cron cmd (or any other similar approach) check if some users have abandoned their cart and if so raise an event(s) or trigger command(s).
@pavelnemoi3 жыл бұрын
This use case with the process manager seems interesting but also more complicated to maintain than an example above I gave ) It seems we need some sort of persistent storage anyway to be fault-tolerant here which means keeping at least one record in such storage (SQL, Redis, etc.) for each user. So as we already have a Cart entity for everyone maybe it is a good candidate for such state just. Anyway, interesting stuff, thanks for sharing! )
@ShawnMcCoolNL3 жыл бұрын
A cron job is an event. It's just not modeled as such. If your system has a cronjob triggering hourly events, daily events, etc.. then your various processes can listen to these events without adding more and more cronjobs.
@pavelnemoi3 жыл бұрын
@@ShawnMcCoolNL Yep, thanks!. This approach makes it flexible somehow. Would like to hear from you what do you think about my first comment. Is it possible/appropriate as some sort of alternative to process manager if we make a Cart an aggregate to raise own events?
@ShawnMcCoolNL3 жыл бұрын
@@pavelnemoi Yeah so, I've reviewed the video again and I'm not sure that cart isn't an aggregate already which raises its own events. That doesn't mean that the aggregate has to be responsible for everything that has to do with a Cart. An aggregate is about transactional consistency, the process of contacting someone whose cart is timed out can exist outside of that consistency barrier. In actuality, it's entirely possible that we don't even care to make a cart aggregate, because the consistency concerns just aren't there. Could you help me know more about what you're asking? I feel like I'm not getting a full picture.
@pavelnemoi3 жыл бұрын
@@ShawnMcCoolNL Got it, thanks! I am just curious about a possible case to avoid process manager somehow maybe for simplicity. Like cart raises its events like an aggregate and some listeners react (update read models, etc.). If we need to check abandoned carts we can have a cron job for example which queries the cart's read model and checks the product's state inside. If the cart is abandoned this cron job sends an email, etc. So the point is to avoid an intermediate state in database (process manager). I also don't see process manager as a too complicated approach, it is ok for sure but try to find a bit easier alternative maybe.