Handle Duplicate Messages With Idempotent Consumers | Idempotency

Поделиться
HTML-код
  • Опубликовано: 4 авг 2024
  • ☄️ Master the Modular Monolith Architecture: bit.ly/3SXlzSt
    📌 Accelerate your Clean Architecture skills: bit.ly/3PupkOJ
    🚀 Support me on Patreon to access the source code: / milanjovanovic
    In this video, I will show you how to implement idempotent event handlers with MediatR. We will use the Decorator pattern to introduce our Idempotent event handlers on top of existing event handlers. The idempotent event handlers will first check if the event has been processed and only proceed if it wasn't.
    Idempotent Consumer - Handling Duplicate Messages
    www.milanjovanovic.tech/blog/...
    Join my weekly .NET newsletter:
    www.milanjovanovic.tech
    Subscribe for more:
    ruclips.net/user/MilanJovano...
    Chapters
    0:00 What is Idempotency?
    0:47 Multiple domain event handlers
    2:31 The solution: Outbox message consumer
    3:17 Implementing IdempotentDomainEventHandler
    6:48 Configuring the Decorator pattern
    7:34 How it works
  • НаукаНаука

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

  • @MilanJovanovicTech
    @MilanJovanovicTech  13 дней назад

    Want to master Clean Architecture? Go here: bit.ly/3PupkOJ
    Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt

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

    Funny. Until today I've never heard of Idempotency and coincidentally I clicked on a video earlier explaining this and now Milan have made a video about it. - Very nice explination, Milan.

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

      Thank you, glad you liked it! Was my explanation easy to follow?

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

    Very interesting, you just opened my mind, thank you very much for the content Milan🤩

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

    With your awesome contents and knowledge sharing. I am feeling awesome 🤩

  • @nove1398
    @nove1398 Год назад +4

    +1 for explaining a good method of enforcing idempotency

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

      Thank you. Did you work with something similar?

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

      @@MilanJovanovicTech yes and though i implemented it differently it was the same principle.

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

    Awesome content. You earned a Pateron

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

    Great content! Thank Milan.

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

    Perfect job for the decorator pattern ;-) interesting video - thx!

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

      Thank you. Did you work with Decorator before?

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

      @@MilanJovanovicTech many times - it often fits when adding cross cutting concerns like logging or caching

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

    A great approach using the decorator pattern, I used pipeline behavior to achieve the same, with different base event class. I have mixed idempotent and non-idempotent calls. But overall, great video.

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

      How did you run a pipeline behavior with events (INotification)?

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

      @@MilanJovanovicTech I didn't. It was for IRequest commands only. I didn't notice that this is for notifications only. You have point. We can't use my approach for MediatR notifications, but I learned something after all. Thank you for your replay.

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

    Nice Video keep it up bro.

  • @murat.yuceer
    @murat.yuceer Год назад +8

    If you scale your application, still multiple handler could consumer... Second consumer will throw exception because of duplicate key for consumer table but mail was gone

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

      The question is, if you scale your application. Do you want all instances to be consuming your message at all?
      It's a typical race-condition situation.
      You would have to think about this differently if you want to scale it.

    • @murat.yuceer
      @murat.yuceer Год назад

      @@MilanJovanovicTech actually if you use update skip lock when you get outbox messages problem will solved

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

    Good video!

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

    Excellent content as always Milan. I have a suggestion for another video not too dissimilar to this one. Here you're using an idempotent strategy to prevent processing back-end messages multiple times, but I'd like to see a 'clean architecture' design for tackling idempotency in an API. As an API vendor I might want to allow a consuming client to provide an idempotency key (GUID) via say an HttpHeader (Idempotency-Key) when making non idempotent API calls (i.e. POST). This key would be used to offer consumers a guarantee that a given operation would only mutate state once and once only. My thinking is that you could create an idempotent unit of work that in turn could insert the idempotency key into a table (where the key is constrained to be unique), and by including this in the transaction scope for a command, you'd achieve the desired result?

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

      Definitely a good idea, and numerous ways to implement it. Agree on the Idempotency header for API side. Internally, maybe a pipeline behavior for commands implementing IIdempotentCommand 🤔

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

    Milan, great video content as always. Are you by any chance planning to show the usage of Entity Framework with the Hi-lo algorithm as part of this series?

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

    Nice work Milan. I think you can enter a bad state if after acking the event saving to outbox fails, meaning youd retry it. This should be under tran or two pahse commites I guess?

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

      I think that would be overkill honestly. You'll dig yourself into a bigger and bigger hole if you go down that route.
      When would committing the ACK actually fail? Apart from the database being down.

  • @emanuelrodriguez3155
    @emanuelrodriguez3155 4 месяца назад +1

    Quick question if i want to implement Idempotency but with massTransit i should decorete the IConsumer interface i asume, right?

    • @MilanJovanovicTech
      @MilanJovanovicTech  4 месяца назад +1

      MassTransit has an Outbox, which will prevent double publish: masstransit.io/documentation/patterns/transactional-outbox

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

      @@MilanJovanovicTech thx for the feedback! appreciate it !

  • @garylee867
    @garylee867 Год назад +8

    There are something you didn't mention:
    - There must be an unique constraint on the OutboxMessageConsumer table.
    - The idempotent handler and the decorated handler must be within the same DB transaction.
    Otherwise, the idempotent handler won't work properly in case multiple instances consuming the same message concurrently.

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

      Just a note: Each DomainEvent has its unique Id. I reuse it for the OutboxMessage. That I changed now. Then the Id of OutboxMessageConsumer makes sense.
      I got a bit confused because there were stuff made to Gathering that wasn't there in earlier video (or did I miss something)

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

      Yes, there were some changes Marina

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

      - Unique constraint isn't necessary, as I made the PK to have 2 columns (Id, Name)
      - I can't understand why do they need to be inside the same transaction?

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

      ​@@MilanJovanovicTech
      If they are not in the same DB transaction and there are two consumer instances consuming the same event concurrently, this may happen:
      - Instance 1 check the OutboxMessageConsumer table (return false, which means the event is not consumed yet)
      - Instance 2 check the OutboxMessageConsumer table (also return false)
      - Instance 1 call the decorated handler and commit to DB
      - Instance 2 call the decorated handler and commit to DB
      - Instance 1 save the OutboxMessageConsumer entry (success)
      - Instance 2 save the OutboxMessageConsumer entry (fail, but the changes made by decorated handler are still persisted)
      You need to put them into the same transaction so that all changes made by instance 2 are rolled back at the last step.

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

    what can we do in case SaveChangesAsync fail? for example, after success payment?

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

      Chicken or the egg problem... If you think about it hard enough, there's no 100% guaranteed way to prevent that. Best you can do is add as much idempotency as possible. You'll need a way to check with the payment provider if payment X was already sent.

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

    Nice solution but what if two instance gets same data on same time? In this case, won't the race condition occur? I think yes.
    In my opinion most safer waye is using distributed lock with RedLock algorithm etc or If your db supports the select lock mechanism(SELECT ....... FOR UPDATE SKIP LOCKED) ) it will be safer to use and data consistency will be ensured.

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

      Yes, you'll get a race condition. But this is rarely going to happen. In the event it does, you need some sort of locking mechanism, as you suggested

  • @batressc
    @batressc 6 месяцев назад +1

    What is the workaround to follow when fail to save the OutboxMessageConsumer record after the execution of the DomainEvent?

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

    Hello, thank you for the videos, is there a GitHub repo?

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

      The code in the video is shared with my Patreons.
      But you can find similar examples on my GitHub

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

    What happens saving the notification to db fails after the _decorated.Handle is completed, next time you will perform _decorated.Handle again, how to avoid it (at 6:50)

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

      Indeed, it would run again. But how likely is that to happen?

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

    Nico work you are a very good programmer but can you explain why you use INotificationHandler

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

      It's from the MediatR library, allows you to publish an INotification that can have multiple INotificationHandlers

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

    Good pattern and great explanation, thank you.
    One small nitpick:
    I believe you should not be passing cancellation token to SaveChangesAsync in the IdempotentDomainEventHandler, or rather passing CancellationToken.None. Once the underlying event handler has finished work (in this case, email sent), from my understanding we are past the point of no cancellation, since the work is handled but we need to save to reach consistent state. This could probably lead to double handling in rare cases.

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

      That's a fair argument, indeed. I pass it mostly by convention. But would it be realistic for the cancellation to even happen given that this is a background job?

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

      @@MilanJovanovicTech Hard to say, since I am not familiar with how quartz manages their cancellation tokens.
      Though the way I see it, there are two scenarios:
      - CancellationToken will never be cancelled (ex: perhaps CanBeCancelled is always false for background jobs), so there is not much point propogating the token to begin with.
      - CancellationToken can be cancelled (ex: perhaps cancellation is done on app SIGTERM or maybe different background job library is used, etc), so then handlers should be implemented with cancellation in mind.
      Either way this is a nitpick, since the timing would probably have to be very unfortunate. Though its something that is easy to fix (at least in this case), so if decision is made to have the token - might as well keep point of no cancellation in mind.

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

    What is the nugget package for Decorate method?

  • @microtech2448
    @microtech2448 12 дней назад

    Hi, in background job video you already published event and updated outbox message, till this point event has been published once. Now in this video you are publishing event through decorator, though you are checking it has not already been consumed but outbox message consumer entry was not present for event being checked by this time and it causes same event to be published twice?

    • @MilanJovanovicTech
      @MilanJovanovicTech  11 дней назад

      Outbox is on the producer side. Idempotent consumers are the consumer side. These are two different sides of the same pipe (the queue).

    • @microtech2448
      @microtech2448 11 дней назад

      @@MilanJovanovicTech but same event is handled twice, is that expected or I am missing something

  • @lukaskuchta1010
    @lukaskuchta1010 11 месяцев назад

    Hi, is there any settings for publising strategy? When i made 5 dummy handlers and 3 trhows expcetion 4 and 5 not hits. It seems that when error occure next publishing is stopped.

    • @lukaskuchta1010
      @lukaskuchta1010 11 месяцев назад

      Ok, it is NotificationPublisher settings. By default is publishing loop interrupt when exception occure. It is valid behaviour for production ? I expected o consume all possible handlers if can.

    • @MilanJovanovicTech
      @MilanJovanovicTech  11 месяцев назад

      With MediatR 12 you have an option to publish in parallel

  • @microtech2448
    @microtech2448 19 дней назад

    Hello, what is the definition of IDomainEventHandler and IDomainEvent? How TDomainEvent gets its Id to set into the outboxMessageConsumer?

    • @MilanJovanovicTech
      @MilanJovanovicTech  19 дней назад

      IDomainEvent { Guid Id }
      IDomainEventHandler : INotificationHandler where TDomainEvent : IDomainEvent

    • @microtech2448
      @microtech2448 19 дней назад

      @@MilanJovanovicTech Thanks Milan

    • @microtech2448
      @microtech2448 17 дней назад

      Hi, I am getting error
      "Could not find any registered services for type 'MediatR.InotificationHandlwr
      When using services.Decorate from scrutor.
      I am using latest version of scrutor and . net 8

  • @danielohirsch
    @danielohirsch 3 месяца назад

    Why not just use local memory cache to track this with some cache invalidation time on items so that these tracked "handles" (which are transiently important) don't have to hit a db to check or save - and they aren't so important after success?

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

    Hi Milan, what is the code for IDomainEventHandler please ? thanks

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

      using MediatR;
      public interface IDomainEventHandler : INotification;

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

      @@MilanJovanovicTech more exactly public interface IDomainEventHandler : INotification ?

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

      @@MilanJovanovicTech where come from the property "Id" of TDomainEvent notification please ? I try to improve Pragmatic Clean Architecture I recently bought with this feature

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

    Is there any non payable source code available ? 🥺

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

    Nice content, pls do share the code

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

      I share the code with my Patreons! Consider joining this month?
      I share both the *before* and the *after* versions of the code, so that you can follow along with me during the video.

  • @microtech2448
    @microtech2448 10 дней назад

    Hello, can anyone please clarify, idempotency consumers along with publishing events through background job in another video in this series, are raising the same event twice? How can we avoid that?

    • @MilanJovanovicTech
      @MilanJovanovicTech  10 дней назад

      Do you mean the consumer is running twice? I think that's a DI issue, can't recall exactly how to fix it though.

    • @microtech2448
      @microtech2448 10 дней назад

      @@MilanJovanovicTech hi, no consumer is running once but I am using both background job to publish event and idempotent consumer together, which results in publishing events twice, one from background job and another from idempotent consumer. And further I could not use scruter library to register idempotent handler so I used inbuilt services.Scoped(...) method to register it, if it makes any difference.

    • @microtech2448
      @microtech2448 10 дней назад

      ​@@MilanJovanovicTechNo but I am running a background job which publishes events and idempotent consumer together. So event is processed first when background job fetches outbox message and second time when idempotent consumer handles it. This way, an event is processing two times.

    • @microtech2448
      @microtech2448 9 дней назад

      @@MilanJovanovicTech it seems resolving now without additional changes. I just tried again scrutor library which was first day giving an issue, this time same version of library seems working as it should. Though not sure, what happened but it works at the end as it should. Thanks!

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

    I would have add this name and Id in cache like key. After proccessing, remove this Key. The speed is better and don't accumulate database with multiple connections by request.

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

      But the consistency is a concern. And what will happen in a distributed environment?

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

      @@MilanJovanovicTech I think that be variable by scenarios. For example, I add that idea in invoice system to town hall, and work it.
      If persistence is really needed, I think Redis solve this problem.

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

      @@MilanJovanovicTech Also, can you have a community in Discord?

  • @10199able
    @10199able Год назад +1

    TIL that you can cheat at Visual Studio