Show Calendar - Part 2: Implementing Event Sourcing (for Dummies!)

The Show Calendar project explores developing an app with the Event Sourcing and CQRS patterns. The goal is to understand how good these patterns are for fast-changing projects, like startups.

The project is implemented in JavaScript with a GraphQL API, but the ideas should work in any language.


In Part 1 we created a GraphQL API that allows us to list all existing shows and add new shows, only... it didn't really allow us to add shows. Mock data is only so much fun.

Now it's time to remove the mock data and implement a super basic, but functional, event sourced backend.

Where we left off in the previous article, the resolver functions just straight up return literal values. What we need to do now is create two functions getShows() and addshow() that we'll use in our GraphQL resolver functions.

  
module.exports = makeExecutableSchema({  
  typeDefs,
  resolvers: {
    DateTime: GraphQLDateTime,
    Query: {
      allShows: () => getShows()
    },
    Mutation: {
      addShow: (_, data) => {
        addShow(data);
        return true;
      }
    }
  }
});

Note: GraphQL resolver functions receive request variables via the second data argument.

There's an opportunity right here to create some structure in the project—so let's move the addShow() and getShows() functions into their own files.

Remember, addShow() is a Command—so let's put it in src/commands/addShow.js.

  
module.exports = function addShow(data) {  
  // code to add show
};

And since getShows() is a Query it goes in src/queries/getShows.js. We'll return an empty array for now.

  
module.exports = function getShows() {  
  return [];
};

The AddShow Command

In order to add a show we need to do two things.

  1. Ensure the data values are good ("business logic").
  2. Save an event that says we added the show (we'll call this "committing an event").

Perhaps a bit backwards, I'll explain events first and then the business logic stuff.

What is an event?

An event can be defined in tons of ways, but we're going to be pretty specific about it. Our definition has one simple requirement for naming.

Events should always be named in past-tense.

What's the point of that? Why should we name an event ShowAdded instead of AddShow?

Events are things that have already happened.

I struggled to understand this when first learning about Event Sourcing.

It's simply a convention (that you can choose to ignore!) that helps us be sure the event log only contains things that have already happened. Knowing for sure that something has happened makes it easier to reason about the meaning of our events.

Linking it to our commands and queries might make it clearer.

Commands should determine if an event can happen and then save the event to the event log or throw an error.

Queries should return a value based on the events in the event log.

Let's think about what events mean for our queries. Imagine we have a stream like this:

  1. ADD_SHOW
  2. SHOW_ADDED
  3. ADD_SHOW
  4. SHOW_ADDED

It should be safe to assume that ADD_SHOW and SHOW_ADDED contains the same type of data—that they come in pairs... so one of them might be redundant.

  • If we keep only ADD_SHOW we can answer the question "Which shows were we asked to add?"
  • If we instead keep only SHOW_ADDED we can answer the question "Which shows have we added?"

The answer to both questions might be interesting, and if it's important to know both answers we can leave both variants.

But is ADD_SHOW really an event? The name ADD_SHOW implies that something should happen... and that sounds a lot like it's a command, right?

So, when we store ADD_SHOW what we're really storing isn't an event, but rather a command. When we mix commands (desired future) and events (certain past) it's no longer clear what to do with the stored value. Should ADD_SHOW add something to our app's state, or should we ignore it?

Only storing events makes it straightforward to interpret what the event means—something has happened that's important enough that we need to keep track of it.

(If you're curious, storing only commands is also a concept, and it's called Command Sourcing.)

If you really really REALLY want to be able to differentiate between when a user started to add a show until it was added then you could avoid the confusion by naming the event something like STARTED_ADDING_SHOW.

Hopefully the above is enough to convince you that it's a good idea to name events in past-tense.

... RANT_ENDED.

Let's implement addShow()

The command for adding a show will produce an event with the following shape.

ShowAdded {  
  title: String
  time: DateTime
  host: String
  location: String
}

Earlier I mentioned that commands should handle business logic, like validating data. Let's say we have two business requirements.

  1. The fields title, host and location must exist and be at most 50 characters long.
  2. date must be a Date object.
  
const max50 = str => str.length <= 50;

module.exports = function addShow({ title, time, host, location }) {  
  if (!title || !max50(title)) {
    throw new Error(`title can't be longer than 50 characters`);
  }

  if (!(time instanceof Date)) {
    throw new Error(`time must be a date object`);
  }

  if (!host || !max50(host)) {
    throw new Error(`host can't be longer than 50 characters`);
  }

  if (!location || !max50(location)) {
    throw new Error(`location can't be longer than 50 characters`);
  }

  // Data good ... Save a ShowAdded event
};

At the end of the addShow() function we should now be certain that the incoming data fulfills our business requirements and that we're ready to commit a ShowAdded event to the event history... but where do we save it?

The answer is... the event store.

Adding Events to the Event Store

I'm going to let the code speak for itself for our minimal event store implementation. Go ahead and put the below code in src/event-store/index.js.

  
module.exports = {  
  createStore() {
    let events = [];

    return {
      commit(event) {
        events = [...events, event];
      }
    };
  }
};

The function createStore() creates an object that has one function commit(event). Calling commit(event) appends the event to the internal array events.

We could add a ton more functionality, but since we're aiming to learn, this naive implementation is good enough. We'll just have to trust that we Do The Right Thing™ and only add properly formatted events (e.g. don't ever use this in production).

In order to share the event store between commands and queries, e.g. addShow() and getShows(), we'll pass it as an argument to the command/query functions.

Let's update src/commands/addShow.js to use the event store.

  
const max50 = str => str.length <= 50;

module.exports = function addShow(  
  eventStore,
  {
    title,
    time,
    host,
    location
  }
) {
  if (!title || !max50(title)) {
    throw new Error(`title can't be longer than 50 characters`);
  }

  if (!(time instanceof Date)) {
    throw new Error(`time must be a date object`);
  }

  if (!host || !max50(host)) {
    throw new Error(`host can't be longer than 50 characters`);
  }

  if (!location || !max50(location)) {
    throw new Error(`location can't be longer than 50 characters`);
  }

  eventStore.commit({
    type: 'ShowAdded',
    payload: {
      title,
      time,
      host,
      location
    }
  });
};

The last step before being able to add shows to the event store is to update our GraphQL resolver with two changes.

  1. Import addShow(), getShows() and createStore().
  2. Create the event store.
  3. Pass the event store to the queries and commands.
  
const { makeExecutableSchema } = require('graphql-tools');  
const { GraphQLDateTime } = require('graphql-iso-date');  
const gql = require('graphql-tag');

const { createStore } = require('../event-store');  
const addShow = require('../commands/addShow');  
const getShows = require('../queries/getShow');

const eventStore = createStore();

const typeDefs = gql`  
  scalar DateTime

  type Show {
    title: String!
    time: DateTime!
    host: String!
    location: String!
  }

  type Query {
    allShows: [Show!]!
  }

  type Mutation {
    addShow(
      title: String!
      time: DateTime!
      host: String!
      location: String!
    ): Boolean
  }
`;

module.exports = makeExecutableSchema({  
  typeDefs,
  resolvers: {
    DateTime: GraphQLDateTime,
    Query: {
      allShows: () => getShows(eventStore)
    },
    Mutation: {
      addShow: (_, data) => {
        addShow(eventStore, data);
        return true;
      }
    }
  }
});

Now we can add shows, but we still need to make the query return the list of added shows.

The GetShows Query

Let's update the getShows() query so it returns the list of shows.

  
module.exports = function getShows(eventStore) {  
  return eventStore.getEvents()
    .reduce((shows, event) => {
      switch (event.type) {
        case 'ShowAdded':
          return [...shows, event.payload];

        default:
          return shows;
      }
    }, []);
};

For this simple case we add the payload of each ShowAdded event to an array and then return it. Later we might need more advanced checks, for example if we have events that edits or deletes shows, but that's for a later article.

If you paid attention you noticed that the code above called getEvents() (line 2) on the event store, but this function doesn't yet exist. Let's add it.

  
module.exports = {  
  createStore() {
    let events = [];

    return {
      getEvents() {
        return events;
      },
      commit(event) {
        events = [...events, event];
      }
    };
  }
};

And since we already updated the GraphQL resolvers to pass the event store to getShows() we're done!

Now, play.

Go ahead and run the GraphQL queries again, using the GraphiQL playground (localhost:3000/graphiql). Here's the queries from my previous article.

query GetShows {  
  allShows {
    title
    time
    host
    location
  }
}

mutation AddShow {  
  addShow(
    title: "Trolling"
    time: "2018-04-01T13:37:00Z"
    host: "Trollface"
    location: "Reddit"
  )
}

Summing it up

There's a lot more to cover, like how we handle updates and deletes (hint: it's by adding more events!) and how we can avoid going through the entire event history on every query, but this is a fully functioning event sourced backend.

I hope you're as inspired as I was when I first learned about Event Sourcing. It really opens up a new angle of implementing systems, and it's being used to orchestrate all sorts of things across microservices. Our example is simplified to the extreme, and I'm sure you can imagine where you'd want to go from here for your projects (like adding databases, or in-memory caches, or versioning the events to help with concurrency, or...).

And for any frontend devs that have used Redux—I know—CQRS is basically like Redux and Event Sourcing is kinda like the Redux Dev Tools... which was what initially drew me in.

Please let me know (@marcusstenbeck) what you think and/or what more you'd want me to explore further. Thanks for reading!

If you didn't code along you can browse around the code in my show-calendar repo.


Previous: Part 1: Setting Up the API
Next: Part 3: To be determined


A note on Commands not returning a value: You may have noticed and questioned why the command doesn't return anything. I wrote a short article explaining why, but in short it's because commands should only determine if an event is possible given the input data and current circumstances, and then commit that event to history. Actually writing to a database or doing something else is the consequence of committing an event to history.

So, in our case, how do we get the updated show list?

Simple, use the queries. Or implement something even cooler, like a GraphQL subscription that notifies the frontend of the change.