-
-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Applied events log starts at zero #141
Comments
Hmm, I'm not sure tbh. I think the default itself is sane. If nothing is in the AppliedEventsLog, it should be assumed that it's a completely new Listener that needs to catch up from the start. If that is not intended in some circumstances (like with process managers, that do side-effects and hence replay/catchup is wrong), we should maybe provide means (via CLI command or sth) to "mark all events applied". |
The question is how this could be nicely integrated in an automatic deployment. For projections I introduce the I could imagine a CLI command but it's pretty specific to this case ... Something like:
The first call would set the highest applied sequence number for the given event listener to 1403, no matter what it was before. The second call would set it to 1403 if it was 0 or less than 1403 or leave it alone if it was higher than 1403. |
I agree to what @albe wrote, starting from 0 is the default behavior I would expect. |
Here's an idea we came up with at the weekly: Processes have the very clear concept of a lifecycle, as is described in e.g. https://leanpub.com/esversioning/read#leanpub-auto-versioning-process-managers. This means that a Process knows exactly when it is in effect, or generically if it is already active and in use or not yet/no more. There are some ways this could be implemented:
Pro: nothing to implement from our side, apart from properly document this
Pro: it is very explicit and makes the lifecycle a core concept of the ProcessManager
Pro: Makes it explicit, but on a generic enough level, what the "scope" of the ProcessManager lifecycle is; It is optimal from performance perspective
|
So, it happened again, this time on a bigger scale in production. I refactored the name of a PHP namespace, which contained a couple of process managers. Can you guess what happened? Because the table As far as I can tell, the worst thing which happened was lots of notification emails sent to Beach users, and I need to remove a couple of nonsense events and replay projections. But it could have gone really wrong. My consequence for now is that I rather create a new (Flownative) package with a process manager which has some more protection measures (like I wrote earlier). Let's see, maybe when I have more practical experience with this, the code can be eventually become part of the event sourcing package again. |
What a bummer :/ Sorry that happened (again). Would the last point have avoided the issue? Afais it would, no?
We could implement that pretty quickly to have a solution for now and then rethink a broader concept if necessary. |
The issue sounds to me like we should have less "magic", speak inferred logic in this part. I like the notion of a "durable name" like in https://nats.io/documentation/streaming/nats-streaming-intro/:
And also a feature where a process manager (or event listener) could specify the start position in the stream:
So the the process manager (or event listener) would have more control and e.g. the start position could be specified in a setting for a specific deployment because it might have a special business need. Like I discussed with Robert already I think we have quite some patterns in this package that aren't unique to the event sourcing architecture but instances of a work queue, message publishing with exact once delivery and so on, so we should treat the problems correctly and use the "right" abstractions. |
I share Christophers concern about too much magic.. 1. A new PR should only listen to future EventsFor this I'd expect a new command that allows to (re)set the Alternatively I could imagine some interface that tells the system from what time the PR should be active (we would need a new EventStore filter for that to be performant probably). 2. A PR should not process Events it has already processedInstead of (only) relying on the highestAppliedSequenceNumber a good practice for a PR is probably to manage its state in a way that prevents such things.
and only add new rows for new correlation ids Implementing a "general-purpose" exactly-once-delivery could also be an option, but this is quite complex and maybe impossible ;) |
Yes, this is a very core concept of pubsub systems and basically what we do already with async EventListeners (and hence PMs). The basic concept is that the information of what messages have been processed is stored with/in the consumer. The difference being that the "name" is decided by the EventListener/ProcessManager class.
I like this a lot. I don't have use cases for all from above in my mind, but they sound reasonable. Is there also a case for "from last" vs. "from next"?
Why a command for starting a process? Do we start processes with a command now already? Processes are rather started by some events (a process is an event-driven state machine), and I wouldn't want to have such "process meta information" in the event payloads (or even event metadata). IMO this is an inherent information of the process definition - it specifies the lifecycle of the process. So we're back at what I wrote earlier, no?
See point no. 3 from my post from April.
First of all, this is not what this issue is really about. A new PM that starts will not have anything processed yet, but he still might want to only start processing "from now/point X" (and possibly even only "until point Y", ie. have a clear lifecycle). Anyway, yes, but unless we implement generic exactly-once-processing semantics (which only Apache Kafka managed to do lately), this will be a concern of the process manager implementation. So the best thing we can do (until then), is to clearly document this and give different approaches to achieve the intended goal. Since we currently only provide at-least-once semantics inside the PM, the PM has to make his processing of events idempotent, meaning that a message that is incoming a second time does not change it's state (and hence not produce any side-effects, since those only happen on state transition). So let's go with the example of the e-mail sending process, because it's probably the most obvious use-case, and look what happens in which order:
unless all those three happen transactionally, i.e. all-or-none, this will still be "at-least-once" with possible duplicate processing/side-effects. 1. + 3. can be solved by persisting the PM data inside the same database as the "highestAppliedSequenceNumber" and wrapping both in a transaction (let's discuss this in a separate issue, it would be a cool feature for a lot of use-cases!). Easy enough as long as we're running ontop of a relational database. But the sending of the e-mail can not be achieved transactionally with the database updates. It may still happen that the e-mail is sent and the update to the "highestAppliedSequenceNumber" fails, rolling back the database transaction. The next time, this will be repeated leading to duplicate e-mails. So the user still needs to make a decision if he prefers "possibly duplicate", or "possibly not sent" e-mails (for that he'd need to postpone the actual sending until after the persisting of the applied sequence number). Note: The first is much less likely to happen though, because the "update highest processes sequence number" is very unlikely to fail. Note2: It doesn't matter if we push 1. + 2. into another part of the system by just dispatching a command to "send e-mail" from the PM. We now just have the two problems of transactionally dispatching the command together with the sequence number update and solving the same "exactly-once-processing" issue on the handling side of the command. |
Hey,
A CLI Command ;)
Uh no? I think it's at least related and I know that Robert had issues with that.
Right, that's what I tried to demonstrate with the example table: |
Dang! Unubiquitous language
Exactly :)
Yeah, "do not do the same work twice" vs. "start doing work from point X". And yes, they are related when the latter is in context of "resume from previous", but not if it starts from new and should just ignore everything prior to X.
Better not do that unless unavoidable. But I totally did not see the parallel processing issue here. Needs some more thought |
Addition regarding
This is not as easily possible, because for that the ProcessManager would need a direct dependency on the EventStore (via EventStoreManager) and the EventStore would need to open up the I'm currently in favor of going with
Which in turn would fit what Christopher said about What do you all think? |
I've been thinking about this again, talked to Robert and finally took the time to actually read all the comments here (previously I frankly only skimmed the text between other tasks). @albe the link to Gregs book was helpful! I especially like the part about Event Sourced Process Managers and I think that would be a solid solution for many issues related to the persistence of PMs (in short it means that a PM works like an aggregate and it's state is reconstituted from events whenever it receives a new message). This won't solve the "this PM should only be active from timestamp x"-problem on it's own, but IMO your suggested solution of a "lifecycle filter" works well with that approach: Quite possible that I miss something, but I could imagine the following steps: 1. Make correlationId a core conceptWe should be able to publish events with a $eventWithCorrelationId = EventWithCorrelationId::fromEvent($myDomainEvent, $someCorrelationId);
$this->eventPublisher->publish($streamName, $eventWithCorrelationId); (similar to #152) 2. Extend EventStreamFilter..with 3. Add a method to the PM interface that returns a
|
That would be a good thing I guess. We need to take care of two things though:
We already started a discussion about
I think we're in agreement on this then :) #readytoimplement
I'd prefer this over separate
Getting back to the issue about PMs not being able to replay, since they cause side effects. So I guess you're thinking about replaying the PM State projection. I could imagine the latter something along these lines:
|
Yes, I already assigned the issue to myself and pushed a first PR: #157
With the PR above I tried to make this easier to extend
FSM? Fliegendes Spaghettimonster? :) I think, we can really just re-use most of the logic we already have for the
Question: To which stream to publish those events to. Again I think we can use a mechanism similar to what we already have with the Aggregate Roots, but instead of |
Finite State Machine. But the Spaghettimonster sometimes would fit too ;)
As said, I would make that one option, but not the only one (and maybe not even the default). Seeing the PM as a FSM basically means that it just needs to store it's current state, which is a single value. Of course you can recreate this state by reapplying all events, but
What I found nice is to think about each PM and each Projection defining their own EventStream. Technically this boils down to building an index (like https://eventstore.org/blog/20130218/projections-5-indexing/) or in our case a specific EventFilter, since in MySQL you don't want to create a lot of indexes and building an own table is also not a good solution. But conceptually, you gain a lot of freedom to think about your specific PM/Projection, because you can basically assume they have their own record of things that only belongs to them.
I need to (re)read that part. TBH I'm not so sure about this, because it has some consequences and also doesn't quite fit my concept of a "Manager". I recently read a nice quote about them: "Managers don't do useful things on their own, they just order other people to do useful things" - and IMO this also applies to ProcessManagers in this case. Also, if the PM only raises events but does not open a (HTTP) connection - how would he send an e-mail? He somehow needs to either do it himself (don't do that) or order some other service (local or remote) to do it. |
Yes, absolutely. I could imagine a new interface (
mh, yes, good point. But everything I read about PM implementation (I could not find that much tbh) assumes that a PM operates on events with the same correlation id.
The process starts with the recommendation, which would get a correlation id. If a user registers upon such an invitation, that "was registered" event gets the same correlation id.
I don't really see that. IMO it's exactly the same as with a persistent PM, just with a local state instead of a persisted one. Basically like an aggregate vs a projector.
Yes, that could be a general (optional) interface any EventListener can implement. It would be a powerful tool, but quite "low-levelly"
I'm also not 100% sure yet, why this would be a requirement. But I think it is in order to avoid that a command is dispatched twice (i.e. duplicate email is sent)
Easy, it would publish an event, some |
Here's something which caused quite some trouble for me today:
I introduced a new process manager (the same would apply to any other event listener) which was supposed to run a couple of commands when certain events arrived.
Now, because the "applied events log" starts with 0 if no event has ever been processed by a particular event listener, all events from the beginning of time will be applied. Which means, in my case, that Docker images were built (well, the process manager tried at least) for projects and code which doesn't exist anymore at present, because the events were from a long time ago.
Here's the related code:
I think that the default should be the currently highest sequence number in the event store instead of
0
. Opinions?The text was updated successfully, but these errors were encountered: