Skip to content

Instantly share code, notes, and snippets.

@tonyhb
Last active March 26, 2022 20:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tonyhb/46d5826e63410c620ccd7d3d8fba9c45 to your computer and use it in GitHub Desktop.
Save tonyhb/46d5826e63410c620ccd7d3d8fba9c45 to your computer and use it in GitHub Desktop.
Event sourcing thoughts

I've been planning out an event sourcing like system for our healthtech startup, and you're definitely right re namespacing and versioning.

I explicitly keep a version in the event - which is a date. It's similar in how Stripe versions their API. We're also planning to handle the events in the same way as stripe's API: each event has side effects; the side effects may change depending on the version and each version has its own application logic (cascading, so you can have 2018-08-01 run all of 2018-07-30 plus its own changes).

This lets us replay events as they happened, run an event using two different versions and perform only the diffs etc.

Our system is probably not a typical CQRS/event sourcing setup.

The event system itself idempotent: you take an event with all input data necessary to run the event (form data, necessary current state), so the system can run independently. This means that every event is typed such that the input data dictates what is necessary.

The event handler validates the event, returning errors if necessary.

Then, the event handler runs all side effects and returns operations to perform: update model X attributes to Y, insert new record Z.

In effect, we go:

User -> API -> (generate event) -> Event Handler -> (error|response) -> save event and side effects in a transaction.

This means our DB is a cache of the event and all previous data, so we're not really event sourcing — we're audit trailing.

The main benefits are:

  1. Medical records are complex and we always need audit trails.

  2. If a doctor submits a prescription, we can show all side effects that happened for visibility (ie. this triggered a lab task, push notification, sent this message). We can verify this in the UI and see what happened for each patient without relying on assumptions.

  3. Engineers know that the API produces events and can look up exactly the side effects that happen when an event occurs (we're using Rails for the API logic right now and this isn't always obvious).

  4. We can ensure that we validate when an event happens based on input and current state without complex code, catching edge cases.

  5. We can choose to save the event and side effects or not. This lets us "preview" actions or "replay" actions without actually changing any world state (you toggle a "test" flag in the event which also means the event handlers know not to trigger outside side effects).

  6. The "side effects" response from the event handler can be sent to a websocket observable and consumed by frontends, ensuring that the doctor UI always has an up to date version of patient data.

Random thoughts:

  • It's really just a framework for the logic of an application controller that's typed and ensures everything is consistent. Plus, similar to Stripe, it allows us to version events and write migrations/upgrade paths etc.

  • What about conflicts? We have a plan to use hashes of the previous data to ensure consistency with medical records: if you're modifying fields A, B, C, you send over a hash of the previous data for A, B, C alongside the request. If the event handler can't verify the hash the data must've changed in the meantime.

  • (non-serious) Want full blockchain? Add a merkle tree in the mix with your events so that they can't be modified. Each event for a user/account is chained from the previous event and has a non-zero but trivial cost.


We're producing events now but the handlers aren't yet in place, so this is currently still being planned. Essentially, we're using the API as authentication, authorization, routing/HTTP management, transaction/database management while the event/controller logic is being placed into a structured framework to ingest form data, current state and produce output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment