Show Calendar - Part 1: Setting Up the API

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.


We're going to set up a GraphQL API that'll serve as our entry point into the show calendar system.

The first step I usually take when starting my implementation is to think of the data model.

The Show data model

I want to keep it simple—we don't need anything fancier than a single type Show. Yes, we could have a more advanced data model, but in the name of simplicity, this is a reasonable starting point.

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

I know this model has problems. For example, a show may, in reality, have more than one host. But instead of planning for that I'm going to later introduce it as a change of requirements. One of the points of this project is to see how easy or hard it is to handle this type of change.

(It'll be interesting to see how things go... especially if there's a pre-existing "legacy database" with a bunch of data that was added before we "knew" about the "new" requirement. Alas, onwards!)

CQRS... ?

We're going to apply the Command Query Responsibility Separation pattern, or CQRS. This means splitting the functionality of reading and writing (e.g. creating, reading, updating) into separate things—Queries and Commands.

Queries are for reading data.

Commands are for writing data.

Our system will be simple and have only one query.

## Show Calendar Queries ##
GetShow -> Shows  

The most basic command in the system is adding a show. For the time being we have no business rules. In other words, we can add multiple shows with exactly the same information.

## Show Calendar Commands ##
AddShow(title, time, host, location) -> void  

And that's it for now. Later on we might add more commands or queries, but for simplicity's sake this is enough.

It's coding time!

Implementing stuff always makes me understand things better. The Show Calendar app will be implemented in Node.js with a GraphQL API.

If you haven't already, get the starter kit here: Part 0 - Starter Kit.

Set up the GraphQL API

In src/schema/index.js, set up one query allShows and one mutation addShow. At the same time, add the Show GraphQL type.

  
const { makeExecutableSchema } = require('graphql-tools');  
const gql = require('graphql-tag');

const typeDefs = gql`  
  type Show {
    title: String!
    time: String!
    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: {
    Query: {
      allShows: () => []
    },
    Mutation: {
      addShow: (_, data) => true
    }
  }
});

The CQRS pattern's Query and Command concepts map really nicely onto GraphQL's Query and Mutation types.

CQRS Query—GraphQL Query

CQRS Command—GraphQL Mutation

Now, start the server with npm start and go to the GraphiQL playground (localhost:3000/graphiql - don't forget the the i!) and paste the below query and mutation.

Note: npm start from the starter kit will automatically restart the server when it detect that you've saved a code change. No need to start and stop!

query GetShows {  
  allShows {
    title
    time
    host
    location
  }
}

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

Using the play button, run the GetShows query and you should receive the following response.

{
  "data": {
    "allShows": []
  }
}

Running the AddShow mutation should result in the following.

{
  "data": {
    "addShow": true
  }
}

However, if you run the GetShows query again we still receive an empty array—there's nothing hooked up to the API yet.

Before fixing that let's fix another tiny issue.

The time value can currently be any string, but only valid time strings should be allowed.

Making sure time is time

In the Shows type, we've accepted String as the GraphQL type for the time field. While this could work for some applications I'd rather have it be a type that actually represents date and time.

Let’s import GraphQlDateTime from the graphql-iso-date package and add it as the DateTime scalar to the GraphQL API.

I'm not going to explain exactly what a scalar is, just know that if the DateTime receives anything else than a valid ISO time string it'll throw an error.

  
const { makeExecutableSchema } = require('graphql-tools');  
const { GraphQLDateTime } = require('graphql-iso-date');  
const gql = require('graphql-tag');

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: () => []
    },
    Mutation: {
      addShow: (_, data) => true
    }
  }
});

Now you'll receive a GraphQL error if you attempt to pass an invalid time string, like "2017-13-01T12:00:00Z" (the 13th month?).

Let's return some data

We're done with the GraphQL API for now, but it'd be fun if the allShows() query returns some shows.

For the time being, let's return some mock data.

  
module.exports = makeExecutableSchema({  
  typeDefs,
  resolvers: {
    DateTime: GraphQLDateTime,
    Query: {
      allShows: () => [
        {
          title: 'Dance Party',
          time: '2018-10-27T13:00:00Z',
          host: 'Caramba',
          location: 'The Beach'
        },
        {
          title: 'Electro Club',
          time: '2018-09-12T21:00:00Z',
          host: 'Das Haus',
          location: 'Bäsëmënt'
        },
        {
          title: 'New Years',
          time: '2018-01-01T00:00:00Z',
          host: 'The Gregorians',
          location: 'The World'
        }
      ]
    },
    Mutation: {
      addShow: (_, data) => true
    }
  }
});

Running GetShows is now a little bit more interesting.

Summing it up

Although playing with mock data is cool and all, it's way more interesting if the AddShow command saves the show so the GetShows query can read it. Time for some plumbing...

If you want to you could now hook this GraphQL API up to a frontend app (for example using apollo-client).

In Part 2 we'll make the API work by using a re-e-eally simple in-memory storage: an array. We'll also talk a lot about events and how what we store is, like totally, a simple variant of Event Sourcing.


Previous: Part 0: Intro & Starter Kit
Next: Part 2: Implementing Event Sourcing (for Dummies!)