Domain Events | Clean Architecture & Domain-Driven Design from scratch | Part 17

Поделиться
HTML-код
  • Опубликовано: 28 сен 2024
  • Get the source code: / amantinband .
    In today's video, we'll implement Domain Events from Scratch.
    We'll see why domain events are such a fundamental part of Domain-Driven Design and how we can use domain events to "communicate" between aggregates.
    Link to the full playlist: • ASP.NET 6 REST API Fol...
    Connect with me on 'em socials:
    Twitter: / amantinband
    LinkedIn: / amantinband
    GitHub: github.com/ama...

Комментарии • 90

  • @pedrofreitaslima
    @pedrofreitaslima Год назад +1

    Thank you Ami, i watch all videos in playlist "REST API following CLEAN ARCHITECTURE & DDD Tutorial" and help me understanding the Clean Architecture.I really liked your teaching, and like you use the most relevant and newest package in this lessons, such as Mapsters, MediatR, FluenValidations, EntityFramework. I will use this learnings to develpoment my capstone project.

  • @alan-
    @alan- Год назад +8

    I really do appreciate the content, but following along with this is made unnecessarily difficult by there being many hidden changes in the underlying code from episode to episode, meaning that it is impossible to follow without being a member on patreon and even with that, it is very awkward and off-putting. It would really help if you could put an on-screen summary sheet of what or where the changes are, so we don't bump into them and then have to piece together what the differences are from the patreon source code in order for the code to run. I am very grateful for the content, it is great to have such in depth detail on a complex topic, but it would be easier to follow along if there was a brief summary sheet at least outlining where the additional changes are. The most significant changes were when many of the IDs changed from guids to strings between one set of episodes, then in a later episode, they had changed back to guids again.

  • @indeem1507
    @indeem1507 Год назад +6

    I really liked how you explained and showed how to implement domain events. Whats kind of missing for me is, why? Because you did not implement the notification Handler i could not really see how domain events are useful.

    • @irfanshaik1302
      @irfanshaik1302 4 месяца назад

      From what I understood, the handler will contain the logic of adding the menu id to particular dinner(using dbcontext) as mentioned in the beginning of the video. So simply put, handler also contains db operations, which are outside the scope of the aggregate, but these all operations will run at once because we are calling these handlers before save changes, and they should run at once because they are related operations.

  • @salarrabbal4059
    @salarrabbal4059 6 месяцев назад

    Thank you for providing such a comprehensive explanation. Over the years, I've encountered various implementations of this concept, yet many seem detached from practical application in the real world. For instance, in your approach, what would happen if domain events were cascaded, or situations arose where the use of IDbContextFactory to handle aggregates in batches precluded injecting the current instance of your Repository in event handlers? In my experience, addressing these challenges often leads to encountering significant accidental complexities, which may prompt a shift towards embracing eventual consistency over immediate event publication prior to saving changes. Once more, I value the insights shared in your informative videos.

  • @LoZioIAR
    @LoZioIAR Год назад +1

    Thank you as usual!! Should be very good if you'll add a windows that show what key are you writing!

  • @DrHeinzDoofenshmirtz
    @DrHeinzDoofenshmirtz Год назад +1

    Great!
    I would really have liked to hear you talk about (maybe with an example) how other entities would react to the domain event. For example, as you are talking about the two-way reference issue. Would the Menu be created and store a reference to the DinnerId, and when the event is triggered, a specific event handler would take the dinner id, fetch the dinner from the database, and then update its reference to the Menu?
    Or I guess it is the other way around: When the Dinner is created and a DinnerCreated event is fired, the Menu would add it to its inner list of dinners?

    • @amantinband
      @amantinband  Год назад

      Thanks! Yup the second one. The event handler would sit under the Menus folder in the application layer (FYI for Patrons: the Patreon source code includes this exact example)

    • @DrHeinzDoofenshmirtz
      @DrHeinzDoofenshmirtz Год назад

      @@amantinband That is a very good reason to become a Patreon 😊

  • @zero8_8
    @zero8_8 2 месяца назад

    Good afternoon!
    I really liked the course! Is it possible to find out if there is a sequel? I would like to go through the following stages, where all the functionality of the application will be analyzed in detail and the final architecture will be shown. I am especially interested in whether not only authorization and authentication will be covered, but also other important functions.

  • @avi7278
    @avi7278 8 месяцев назад +1

    what is the app that you're using for presentation in your video?

  • @weilei888
    @weilei888 Год назад +3

    wow.great video..

  • @kmcdo
    @kmcdo Год назад +2

    public record TreeFell(bool madeSound) : IDomainEvent;
    If there's no handlers, did the event even happen? ;-)

  • @batiku1
    @batiku1 11 месяцев назад +1

    Why can't we pubish the domain event(s) right from the domain layer?

    • @TuanNguyen-ed9rb
      @TuanNguyen-ed9rb Месяц назад

      because your domain isn't persisted to the database yet. You don't want to send an email to customer before their order is successfully saved

  • @samehhakim4340
    @samehhakim4340 Год назад

    👍👍👍👍👍
    Thank you,
    your explanation is really very simple

  • @jamesroot9777
    @jamesroot9777 Год назад

    Hey Amichai, absolutely love the videos. Have followed from part 1, and watched all the videos a few times already. I'm in a course right now, and we are tasked with wiriting a simple Forum RESTful API, but I got invested into what you are doing and wanted to adapt it. I've since hit a roadblock.
    Could you give me some advice on how I can scale down the project so it makes sense for someone whose closer to a beginner like myself?
    Would the User, Post and Comment aggregates need to follow what you've done so far, including the AggregateRoot, Entity and Value Object models?
    How do I handle roles, as in "Admin" and "Basic User"?
    What do you think I can ignore from your DDD, keeping in mind my project is extremely small compared to what you're doing, and we are not expected to have even half of the features and implementations you're using. (Although, I'd love to keep all of 'em, favourite is ErroOr).
    Keep the videos coming!

    • @mohamedal-qadeery6530
      @mohamedal-qadeery6530 Год назад

      im a beginner too but i think its going to be something similar to the guest/host aggregates where u would have value object of userId

  • @AlexGait
    @AlexGait Год назад +2

    Hey, Amichai! Great video as always!
    Is there a reason you prefer to have Domain Events in the Entities and not the Aggregates?
    Will an Entity ever raise a Domain Event?
    Also, when do you think the testing videos will be released?

    • @allannielsen4752
      @allannielsen4752 Год назад

      I too was wondering why they were not in the aggregate. The other thing I have always been told was that domain events exist inside their bounds and integration events cross bounds. It might just be semantics, but I find that coupling using this method leads to the same spaghetti this is supposed to "fix". Furthermore, I was surprised to see you adding the menu instance into the event and not just the ID, could you explain that further too please, as now you are exposing the menu domain object to outside too.

    • @amantinband
      @amantinband  Год назад +3

      Thanks, Alexandros!
      Both approaches are very common. I've used both, and the main difference is whether the inner entities need to communicate back to the aggregate root that something happened. The main benefit of domain events in the aggregate root is a single, organized place for all events. Since we are organizing our domain layer by feature, it is clear which domain events are associated with which aggregate, which is why I prefer the Entity approach. Does that make sense?

    • @amantinband
      @amantinband  Год назад +1

      @@allannielsen4752 Hey Allan. Domain Events capture an event that happened within a bounded context and are used to implement side effects on other aggregates within the same bounded context. In our application, BuberDinner *is* the bounded context and the side effects are updating zero or more aggregates within BuberDinner. Regarding event parameters - it all depends on your use case. You can pass as little or as much as you need.

    • @AlexGait
      @AlexGait Год назад

      @@amantinband I am not sure I see the benefit, but I guess it is more of a preference. Thanks!

    • @pilotboba
      @pilotboba Год назад +2

      @@AlexGait The number one benefit I see from using domain events is that it helps with single responsibility and open/closed. (probably the two most important SOLID factors).
      Right now, we have 1 thing that has to happen after a menu is created. If that's in the create menu handler, fine. But down the road a second thing needs to happen when a menu is created. So, now you have to edit the handler and add code to it. So, that handler has multiple reasons to change now.
      With domain events, you would simply add a second handler for that event. The more your code never needs to be modified to add features and just added, the better. You're less likely to break something that is already working.
      Anyway, this is just my opinion. Take it for what you paid for it. :)

  • @sneigee
    @sneigee Год назад

    that awesome. thanks for this. and please try and release it early. Stay bless bro

  • @alexlo5655
    @alexlo5655 Год назад +1

    Hey, Amichai! Great video as always!, Could you plz make the "code" screen(s) larger? I can hardly see your code.

    • @amantinband
      @amantinband  Год назад +1

      Thanks! Curious if others want this as well. If yes I’ll bump the font size. thanks for the feedback, Alex 🙏🏼

    • @pilotboba
      @pilotboba Год назад +2

      @@amantinband It seems fine to me, and I can zoom my screen if needed.

  • @CharlesSyms
    @CharlesSyms Год назад

    Hello! I've been racking my brain with this. I have been following along all of your videos and understand the concept, but I'm hitting a roadblock when creating the migration, where it is creating a shadow property on a foreign key. This would be the equivalent in your example between dinner and reservations.

  • @patrykk.4630
    @patrykk.4630 Год назад

    Hi, I am back after a long pause. Yesterday I made changes to my aggregate roots (identity paradox) and my mapper stopped working for AuthenticationResult -> AuthenticationResponse. It says 'Cannot convert immutable types, please consider using MapWith'.

  • @juliangzr4998
    @juliangzr4998 Год назад

    Hello! thank you for your video. I have a question: How can i do to throw domain events when i delete an entity? in that case it's not possible to save the domain event inside the entity because you are deleting it. I usually delete the entity through the repository.

  • @wilfriedkinda
    @wilfriedkinda Год назад +1

    Hi Amichai very good and informative video as always just need an open mind for me who uses mongoDb as a database how to set up an interceptor!? or what logic I can use
    I'm a beginner.

    • @TheFeljoy
      @TheFeljoy Год назад

      An interceptor is just a nice way of adding this logic to the save method of entity framework. The simplest way would be to create a new method that calls whatever save method you use for mongodb inside it and calls publish domain events just before that. Then you always use your custom save method instead of the default one. For bigger projects with many contributors it’s important to build things in a way where you couldn’t do things the wrong way anymore (because it’s not possible) but for your own project this is much less important.

  • @TheOneAnOnlyGuy
    @TheOneAnOnlyGuy 10 месяцев назад +1

    How do you justify adding MediatR to the domain? That is a dependency that does not belong in the domain, imo.

    • @Marfig
      @Marfig 10 месяцев назад

      There is nothing wrong about adding external library dependencies to the domain layer. Even more so if they are utilitarian libraries like MediatR. One common misconception is reading too literally the ol' adage "the domain layer shouldn't depend on anything". What that should read is, the domain layer shouldn't depend on any other structural layer of your project. It's well within DDD best practices to support your domain layer with external libraries that don't create those dependencies, like MediatR for Domain event support. Another example are discriminated union libraries to reduce exception handling in your code.

  • @alexanlp
    @alexanlp Год назад

    Sorry for my question, bur which font you're using in VS Code?

  • @issacproton7885
    @issacproton7885 9 месяцев назад

    would you like to share how do you use Tools like a pro, it feels like Vim on VS Code as well as Visual Studio when you use it.

  • @zakustolondoo
    @zakustolondoo 6 месяцев назад

    Hi Amichai, How can I get the source code as a member?

    • @amantinband
      @amantinband  6 месяцев назад

      Hey man! It’s a perk for patrons. Being a member gives you only early access to videos as I upload them

    • @zakustolondoo
      @zakustolondoo 6 месяцев назад

      @amantinband Apologies if I joined the wrong group. I joined because I need access to the source code. How can I transfer my subscription to patron without having to pay again?

    • @amantinband
      @amantinband  6 месяцев назад

      Moving the subscription isn’t possible. I think you can cancel the membership and you won’t be charged (or maybe you’ll be charged only for the day you were a member)

  • @marcinz9379
    @marcinz9379 Год назад

    Hey, I have question about DDD. For example we have aggregate Blog, and this Blog has many Posts, and any Posts has many Comments. How to add new Comment to Post? Blog is my AggregateRoot. And now i have problem. I don't want to get from db my whole Blog with all Posts and nested Comments. It's not efficient and also I don't have any aggregate rules for Comments. For example we have: AddCommentCommand(PostId PostId). Can I simply get this Post from db, create new Comment object and save this to db? Or it all should be under our Blog aggregate?

    • @stunna4498
      @stunna4498 Год назад +1

      in this case comment should be an aggrregate itself and you would have a reference to the Post by using the PostId. Then when you query the post you would go to the comment repository and get comments by id or whatever.

    • @amantinband
      @amantinband  Год назад +2

      Check out my videos on domain modeling. As Joel suggested, You would probably want the Post and/or the Comment to be an aggregate root as well (one of the reasons we prefer more aggregates is not having to read big aggregates when we need to make an update to a nested entity)

    • @marcinz9379
      @marcinz9379 Год назад

      Thank you guys

    • @nicolasundiano8406
      @nicolasundiano8406 Год назад

      ​@@amantinband but what happens if we want to query related data of the Post Aggregate, for example the list of Comments Aggregate included. Do we have to do multiples queries to the db and put them in a PostWithCommentsResponse?

    • @pilotboba
      @pilotboba Год назад

      @@nicolasundiano8406 Why are you querying it? For your query side or for your command side? If for the query side, you can create a read model that just does a simple query on the db and doesn't use the aggregates at all.

  • @jfeedass
    @jfeedass Год назад +1

    Ačiū!

  • @mohamedal-qadeery6530
    @mohamedal-qadeery6530 Год назад +1

    Hello i want to start this playlist how many videos are remaing for this to finish ? this the best content on youtube !

  • @parthadutta1168
    @parthadutta1168 6 месяцев назад

    How to implement domain events when domain entities and persistence entities are different, i.e. domain entities are persistence ignorant?

  • @ANTONZUBAREV
    @ANTONZUBAREV 9 месяцев назад

    Отлично! Но слишком сложно/ Great! But it is a too diffcult

  • @Steven-hq6df
    @Steven-hq6df 3 месяца назад

    I'd be interested in an implementation without depending on ef core

  • @timur2887
    @timur2887 5 месяцев назад

    Don't materialize enumerable twice or more in one function

  • @verified_tinker1818
    @verified_tinker1818 4 месяца назад

    What software are you using in these videos?

  • @ferventurart
    @ferventurart Год назад

    Great work Amichai!! But whats happen if you have identity Id. For example, if we follow this tutorial we always created at entity with Id 0, and if on our domain event want insert on another table it throws error.

  • @evgeniysir4220
    @evgeniysir4220 Год назад

    Great video! Thank you! Maybe not exactly on the topic of the video, but I have a problem that I don't know how to solve yet using the existing architecture. Namely: I add a function to the repository to search for an entity by identifier, I pass an identifier object to the function as a parameter (HostId hostId hostId as an example), inside the function I use FindAsync(hostId). When executed, the exception "System.ArgumentException: The key value at position 0 of the call to 'DbSet.Find' was of type 'HostId', which does not match the property type of 'AggregateRootId'" occurs. Considering that AggregateRootId is an abstract class, how to solve this problem? I would be grateful for a hint.

  • @reshiefaisal1477
    @reshiefaisal1477 Год назад +1

    Hey man it's a great help that you're making these videos. These videos have helped.
    I kindly request to make a video on Test Integrations as well. That will be very kind full of yours.

    • @amantinband
      @amantinband  Год назад +5

      I have a whole sub series planned on testing. This will include integration tests as well

    • @reshiefaisal1477
      @reshiefaisal1477 Год назад +1

      Thank you. That will appreciated.

  • @z_prospective160
    @z_prospective160 Год назад

    Very cool! I like it! The overhead of the structure is worth it.. and hopefully you are able to put this boilerplate in a common library you can reference from a nuget feed. I never thought to publish the domain events from persistence layer as a part of save changes. I have done things like modify the entity's audit fields in the save changes method before. This is very cool and I'll be using this for sure. thank you!

  • @issacproton7885
    @issacproton7885 9 месяцев назад

    very insightful!

  • @issacproton7885
    @issacproton7885 9 месяцев назад

    I loved it!

  • @SureshKumar-rz7dn
    @SureshKumar-rz7dn Год назад

    Great Video as always :-) Can we use this pattern for sending events(integration event) externally to a service bus queue?

    • @ZeBobo5
      @ZeBobo5 Год назад

      You can add bus publishing in a domain event handler. And why not inside another assembly

  • @mikefellowes8910
    @mikefellowes8910 8 месяцев назад

    What is your opinion on request handlers orchestrating changes to multiple aggregates directly, rather than using domain events to orchestrate that logic? Using domain events exclusively to communicate between aggregates could make understanding the flow of a particular use case challenging, couldn't it? If the use case logic is clear then it seems to me that using one handler would be the best approach. I can see that using domain events is flexibile, along with the advantages of the pub-sub nature of INotification, but do you think there is a place for both approaches?

    • @amantinband
      @amantinband  8 месяцев назад

      Completely agree. That's why I have the following section in Clean Architecture template on GitHub:
      "Note: Eventual consistency and the domain events pattern add a layer of complexity. If you don't need it, don't use it. If you need it, make sure your system is designed properly and that you have the right tools to manage failures."

    • @mikefellowes8910
      @mikefellowes8910 7 месяцев назад

      Thanks for the reply @@amantinband - fantastic videos btw, you have a new subscriber! For anyone interested, I came across this Microsoft article making the same argument for domain events. Since I cant paste the link here, do a web search for the section in the article titled: "Domain events as a preferred way to trigger side effects across multiple aggregates within the same domain".

  • @PelFox
    @PelFox Год назад

    There's still a problem with consistency. Events could be published and then SaveChanges fails or the SaveChanges fails and some process within an event handler fails.
    Everything also runs in process, so need to keep that in mind if you have users waiting for a call.

    • @amantinband
      @amantinband  Год назад

      Note that all handlers and inner “save changes” are part of the original transaction. If any fail, all changes are rolled back. As long as all side effects are on aggregates within the system, this solution resilient to failures

    • @PelFox
      @PelFox Год назад

      @@amantinband Yes, if they all act within the same transaction and database. What I often see is domain eventhandlers publishing integration events, those could fail and that would result in state changes without messages being published.

    • @amantinband
      @amantinband  Год назад

      Exactly. I talk about this somewhere in the video

  • @modgenesis3868
    @modgenesis3868 Год назад

    why would you have domain events in the domain objects, the logic could also be handled in the command handlers right?
    The thing about this approach is that code gets executed at place A but then somewhere in the app it gets fired in another part, this will be hard to debug in the future.
    But Thanks for this video man!

    • @stunna4498
      @stunna4498 Год назад

      your domain events will be called in the domain objects itself. but the code that handles that event will be in the application layer so it won't be hard to debug since you will allways know where that event is handled

    • @amantinband
      @amantinband  Год назад

      Yes. The 2 main approaches are orchestrating all the changes from the handler and the domain events pattern I demonstrated on this video. Which is better? That depends on your preference, app, and goals.

    • @pilotboba
      @pilotboba Год назад

      See my reply above. The reasons are to keep your code more solid by following the single responsibility property and the open/closed principle.

  • @novelhawk
    @novelhawk Год назад +2

    Way cleaner approch on the Publish logic in my opinion is the following:
    - Remove Clean method and instead create a Drain method that cleans and returns what it has cleaned
    - Replace linq with
    var events = dbContext.{...}
    .SelectMany(entry => entry.Entity.DrainDomainEvents())
    .ToArray();
    // publish events

    • @amantinband
      @amantinband  Год назад +2

      I've taken a similar approach in some projects. If you don't need to read the events (without "popping" them), what you suggested is a safer approach, IMO.

    • @pilotboba
      @pilotboba Год назад

      @@amantinband So the domain events could be a stack instead of a list?

  • @vincentcifello4435
    @vincentcifello4435 Год назад +1

    1)"Aggregates are transactional boundaries"
    Menu and Dinner are separate aggregates. Therefore, they are separate transactional boundaries, by definition.
    Yet, they are being saved within one database transaction.
    This is self-contradictory. It is also a seed of destruction..
    2)"Other solutions require a lot of ceremony"
    This entire video substitutes an elaborate, in process, technical solution, ultimately described as "a lot of boilerplate code", in order to avoid a minimal transaction script that is exactly the same, except that it is explicit.
    Dinner.Add(myMenu)
    Menu.Dinners.Add(myDinnerId) ----this one line is the reason for this video
    dbContext.SaveChanges()
    3)Dinner aggregate holds a MenuId That is all that is needed for the relationship between Dinner and Menu. Having a Menu aggregate holding a list of DinnerIds duplicates data. This appears to be the reason that both of these aggregates are being saved in one transaction. Seed.Of.Destruction.
    I am criticizing the way DDD gets implemented, not Amichai. It is always a convoluted, self-contradictory mess.