3 ms·
While I use event sourcing w/ Akka persistence, I find it is not a panacea for the problems that the blog post may reference. I find that oftentimes people stor
by kodablah 11y ago
While I use event sourcing w/ Akka persistence, I find it is not a panacea for the problems that the blog post may reference. I find that oftentimes people store the resulting state in memory which may become unwieldy. And if you're storing it on DB/disk (not to be confused w/ storing your events/snapshots) you're basically back to the way traditional CRUD works except you have a stream of events if you need a new data structure from scratch. Also, many times event sourcing offers eventual consistency which, while it is obviously preferred to develop your app that way for scalability reasons, not all apps can do that and need atomic state.
- hesdeadjim 11y agoFor atomic updates we implement this via CAS-like semantics. A caller who obtains a point-in-time aggregate view of an object (equivalent to the 'R' in CRUD) is also given the sequence number of the latest event. They can then conditionally push new events into the object's queue and request a rejection if the current sequence id is not equal to the one they have. Since we use Redis for the queue, the event pushes have a consistent view of the queue at the time of the attempted push.
- kodablah 11y agoWhile scalable, this basically turns it to bidirectional by adding a side-channel for conflict resolution which can add system complexity. This is why many event sourcing approaches recommend making the event represent the mutation (e.g. count + 1) instead of the result (e.g. count = 15)...when it does have to be atomic, I just give up and use a centralized place like you have with Redis. Such is life with atomic, distributed systems...they can only be so distributed and still reject conflicts in real time.
- hesdeadjim 11y agoMost of our events do in fact represent the state change as the mutation. However I see no complexity issue with how we implement CAS semantics. Its 100% equivalent to any other implementation with all the implications -- reduced concurrency being the primary issue. We avoid this at all costs for most objects and, like you suggest, resolve conflicts by de-duping events or combining state changes to the final aggregate.
- numbsafari 11y agoI generally use a combination of both approaches. For entities with a complex lifecycle or which lots of updates or concurrency, I tend to prefer to ES approach. But for value objects, enumerations and lookup values, I'll go more with the approach he describes. In the past I used the RDMBS trigger approach he describes, but have now moved to doing this more in the application layer (typically with ORM signals/events) because you can more easily write unit tests for it and make your code more portable. Neither of those are always requirements, though. Would be interesting to see an ORM that could express triggers and generate trigger code in a declarative manner. Haven't seen anything like that, but could provide for some performance advantages in certain cases. Probably more complexity than it's worth, though.