What is the recommended approach to only do things once ?
The user press 'confirm' on the checkout process and now the event 'order is placed' is raised. The mailing service handles that event and sends a confirmation email.
The external email service was down. I want to replay events that should have triggered emails when it's back online. Obviously, I don't want to resend the email twice. Is the recommended approach to also send an event like 'email sent' which would be associated with the 'order is placed' event ?
Should I only use events for state changes and never to trigger commands and rely on something else to have commands ?
I was reading on Saga and there were stateless and simply triggering behaviors on events. Not sure how that applies when you can't trigger the behavior if its already done (sending emails twice)
Is mixing result of command and commands ok ?
What is the recommended approach to only do things once ?
Use durable information transfer, rather than transient information transfer.
When events are "raised", copy them into a durable queue, so that they are available when the email service comes back on line (ideally, orders and emails should never needs to be running at the same time; we only need that the queue/feed/mailbox is available when the services need them).
That ensures that information doesn't get "lost" during an outage.
Furthermore, you want to design your event messages in such a way that the email service can identify duplicate copies of events that have already been processed (for instance, a unique identifier for each message about an order). The email service keeps track (for some finite amount of time) of which messages have already been handled, so that it knows to skip that message if it sees it again.
In other words, the handling of the messages by the email service should be idempotent.
Recommended viewing: Greg Young on Polyglot Data.
This doesn't quite get you exactly once; if the email service crashes between sending an email and writing down that it sent an email, then you may sometimes see a duplicate. You can try writing things down first, but now a crash means that the message is never sent.
We have to make informed choices about whether we want our implementation to send message at-least-once or at-most-once.