Let's assume that we want to implement a small security subsystem for a financial application that warns the users via email if a strange pattern is detected. For this example, the pattern will consist in three transactions as the ones depicted. The security subsystem can read events from the main system from a queue.
What I would like to get is an alert that is a direct consequence of the events that happen in the system, without an intermediate representation that models the current state of the pattern.
- Monitoring activated
- Transaction processed
- Transaction processed
- Transaction processed
- Alert triggered (id: 123)
- Email for alert sent (for id: 123)
- Transaction processed
Having this in mind, I thought event sourcing could apply here very well, although I have a question without a clear answer. The alert triggered in the example has a clear side effect, an email needs to be sent, a circumstance that should only happen once. Therefore, it shouldn't happen when replaying all the events of an aggregate.
To some extent, I see the email that needs to be sent similar to the materializations generated by the query side that I have seen so many times in the CQRS/Event sourcing literature, with a not so subtle difference though.
In this literature, the query side is built from event handlers that can generate a materialization of the state at a given point reading again all the events. In this case, though, that can't be accomplished exactly like that for the reasons explained before. The idea that every state is transient does not apply so well here. We need to record the fact that an alert was sent somewhere.
An easy solution for me would be having a different table or structure where you keep records of the alerts that were previously triggered. As we have an ID, we would be able to check if an alert with the same ID was issued before. Having this information would make the SendAlertCommand idempotent. Several commands can be issued, but the side effect will only happen once.
Even having that solution in mind, I don't know if this is a hint that there is something wrong with this architecture for this problem.
- Is my approach correct?
- Is there any place where I can find more information about this?
It is strange that I haven't been able to find more information about this. Maybe I have been using the wrong wording.
Thank you so much!