jdreaver / eventful

Event Sourcing library for Haskell
MIT License
106 stars 22 forks source link

Add example of where to put side effects #17

Closed ch3pjw closed 7 years ago

ch3pjw commented 7 years ago

I've been reading through the code for this library, including the examples. Perhaps I'm missing something, but I'm struggling to find an example of where to put side effects that I might want to execute when a command has been issued in a particular state or has caused a particular state to be reached.

I've tried reading the code, but I'm not sure I quite understand CQRS enough to understand the command-centric layers of eventful fully.

As a concrete example: if I want to model an email sign-up scenario, I presume I would have states like HasNotRegistered, HasClickedSubmit and HasClickedVerify. I would have SubmitEmail and ClickVerify commands that would move through the states. However, on handling the SubmitEmail command, I not only need to emit an EmailSubmitted event to update the state to contain the submitted email address, but send the verification link via email so the user can click it to submit the next command.

As far as I can see, the existing CommandHandler used in the "counter" example doesn't permit side effects in the type, and nor does applyCommandHandler. I looked at ProcessHandler, but the intent of that just seems to be coordinating state changes across multiple streams. I wondered whether a ReadModel was the right way to go, but it felt like polling in the example wasn't nice, especially in something that's very event-driven to begin with. What am I missing?

Happy to contribute a working example to help others figure this out if there's an obvious answer I'm missing.

jdreaver commented 7 years ago

This isn't something that eventful has the goal of supporting natively. There are way too many variations on how to handle a side-effect to be able to properly create a general system for this. This is part of the general advice of "avoid CQRS frameworks" since evreyone's use cases are too different. The goal is for eventful to provide useful, composable building blocks for CQRS/ES systems, not to be a framework.

If you have some HTTP service that handles when a user clicks the "Submit Email" link, then you could send the email in that service. For example:

  1. The service's handler for email submission receives the HTTP request
  2. It sends off the verification email
  3. It stores the EmailSubmitted event in the event store

Note that there are a lot of variations on this. For example:

Let me know if this helps at all! I hope this explains why something like this isn't built into eventful. Feel free to ask any more questions you have about this situation in particular.

ch3pjw commented 7 years ago

Yes, it's really helpful to understand why there wasn't an obvious place to put this stuff. I ended up going with your third suggestion where I have something that's responding updates, working out if it needs to send emails and then saving an EmailSent Type event in the store. I didn't go with the ReadModel abstraction yet, as I don't think I've quite got my head around it. I've made the thread that's emailing read off a queue that tells it a particular UUID has been updated, then it goes and reads the latest projection for that UUID and acts appropriately. Doing that felt better than polling.

So, I guess my follow on questions would be:

Many thanks!

jdreaver commented 7 years ago

I'm considering scrapping the ReadModel and ProcessManager classes. I created them while working on the bank example, and they work fine. However, they aren't really general enough to deserve those names, and now I see how misleading it can be. Maybe I will rename them or get rid of them.


What's the intended purpose of ReadModels/how do I use them correctly?

I made them while making the examples. I think I'm going to rename them to SimpleReadModel or maybe scrap them. If I can make them more general I'll do that. For now I wouldn't use them as they will change.

Was I correct in thinking that ProcessHandler was for side-effect free coordination of state change across event streams?

Correct. A ProcessManager is a Projection that has functions to get any pending events or commands it wants fired. Again, this isn't the only way to do process managers, so I might rename it SimpleProcessManager or ProjectionProcessManager, or get rid of it and replace with some documentation on how you might build one.

Is triggering an update via a queue a reasonable solution, or is there a better way to get notified in an event-driven way about store updates?

You can use a GlobalEvenStoreReader instead of having a separate queue. This way your thread can poll the event store for any events on any stream. If it sees an EmailSubmitted event, it can send that verification email.

I've heard/read of a great system that I use personally. Instead of always being push based (queue) or pull based (polling) for your events, you can use a combo. By default, poll at some specific interval, like 1 second. However, build into your polling thread some "wakeup" that allows it to skip the rest of its wait period and poll immediately. In your case, your thread that polls for email submitted events can by default just poll every second, but you can use your queue to wake it up when an event of interest is stored. All you have to do is store the last processed SequenceNumber so you know where you left off last.

The advantage of this sleeping polling system is:

  1. There is only every one day to get data, and that is by using whatever function you use when you poll.
  2. Since you can wake up your polling thread when an event gets written, you can still get updates almost as fast as if you were sent the event(s) directly.

I'm thinking of using this in production - is that a good idea?

I would hold off on that 😄 I personally use this in production for a couple systems, but I'm also the author and I currently just change parts of eventful if I find deficiencies. Once I have way more documentation (including docs on how to do things that aren't built into eventful, like in this case), and I've settled in on most of the APIs, I'll make a release announcement in the usual places.

I'm also hoping to flesh out the bank example as much as possible so I can refer to it for common use cases in the docs.

ch3pjw commented 7 years ago

Cool - I like the idea of mashing together the polling/wake-up flow. As you say, it means you only have one way of getting data.

The thing I'm working on is quite small, so I'm not too worried about changing APIs too much. I rather want to see how event-sourcing plays out in the wild for me. I'm trying out Haskell in production for the first time too :grin: I'll let you know how it goes, and I'm really grateful for your detailed responses so far.

As far as the ReadModel/ProcessHandler examples go, I definitely found they took me quite a long way from what you are describing here as the intent. I think, perhaps, if they had been in examples/ rather than eventful-core/ and eventful-memory/ I'd have given them less weight. The names probably would hint at me that I'd want to model my own things on them, but I think I'd rather have just worked out that bit on my own than try to emulate. (That is, a clearer distinction between what eventful does and does not address would have been more helpful to me.)

jdreaver commented 7 years ago

Great, thanks for the feedback and good luck! 😄