Choose Your Own Adventure for client web services

With

Dave Anderson

@dvndrsn

before the adventure

A little bit about me

PROLOGUE TO THE ADVENTURE

As a developer we face choices everyday..

PROLOGUE TO THE ADVENTURE

Those choices have consequences..

PROLOGUE TO THE ADVENTURE

GraphQL offers an alternative to REST...

PROLOGUE TO THE ADVENTURE

What to expect on your journey...

Choose GraphQL or REST

Review examples

Advanced Considerations

Chapter 1

API Architecture and Design

You are a developer.

If you choose a REST, advance to the next section.

One things is for sure: you need a robust API.

What do you choose?

If you choose GraphQL, advance to the following section.

Choose Rest

Architecture and Design

You choose to build your API using REST web service architecture.

Choosing REST is a familiar design decision.

Choose Rest

Architecture and Design

You use HTTP verbs semantically to describe operations on your resources

GET /api/v1/stories/
POST /api/v1/stories/
GET /api/v1/stories/{id}/
PUT /api/v1/stories/{id}/
DELETE /api/v1/stories/{id}/

Choose Rest

Architecture and Design

You build many different endpoints, for each resource.

GET, POST /api/v1/stories/
GET, PATCH, DELETE /api/v1/stories/{id}/
GET, POST /api/v1/stories/{id}/passages/
GET, PATCH, DELETE /api/v1/stories/{id}/passages/{id}/
GET, POST /api/v1/stories/{id}/passages/{id}/choices/
GET, PATCH, DELETE /api/v1/stories/{id}/passages/{id}/choices/{id}/

Choose Rest

Architecture and Design

You choose how to structure your response.

// Hypermedia as the Engine of the Application State - HATEAOS
// GET api/v1/stories/
{
    "id": 1,
    "title": "REST",
    "author": "Dave A.",
    "description": "Can have many response structures",
    "links": [
        {
            "href": "1/passages/",
            "type": "GET",
            "rel": "passages
        },
        // ... etc
    ]
}

Choose Rest

Architecture and Design

You receive many requests to add new fields to the API.

 

..but now some clients are fetching fields that they don't need.

 

You want to deprecate old fields or refactor the structure of the API by introducing a new set of versioned endpoints.

 

..but now you're supporting multiple versions of each API resource forever.

Choose Rest

Architecture and Design

You are attacked by a sea monster with as many arms as your API has endpoints and versions.

 

You die.

Wait, wait, wait.. let's skip back and choose GraphQL.

Choose GraphQL

Architecture and Design

Your friend tells you about a new specification for designing API introduced by Facebook in 2015, called GraphQL.

You choose to explore GraphQL deeper.

Choose GraphQL

Architecture and Design

GraphQL is a query language for your API.

 

It allows clients

to ask questions of your server

and only get the data they need

in a single request.

Choose GraphQL

Architecture and Design

You define a schema...

type Story {
  id: ID,
  title: String,
  author: String,
  passages: [Passage]
}

type Passage {
  id: ID,
  name: String,
  description: String,
  is_ending: Boolean,
  choices: [Choice]
}

type Choice {
  id: ID,
  description: String,
  toPassage: Passage
}

type Query {
  story(id: ID): Story
}

Choose GraphQL

Architecture and Design

Then you write queries against that schema..

query myStory {
  story(id:1) {
    id
    title
    passages {
      id
      name
      description
    }
  }
}

Choose GraphQL

Architecture and Design

And you get a JSON response.

{
  "story": {
    "id": "1",
    "title": "Choose Your Own Adventure with GraphQL",
    passages: [
      {
        "id": "1",
        "name": "Beginning",
        description: "Your adventure begins",
      },
      {
        "id": "2",
        "name": "GraphQL Architecture",
        description: "Choose GraphQL!",
      },
      // ...
    ]
  }
}

Choose GraphQL

Architecture and Design

If you need to change data, write a mutation!

mutation newStory {
  createStory (input: {
    title: "GraphQL",
    author: "Dave A",
    description: "Its pretty cool"
  }) {
    story{
      id
      title
      description
      author
    }
  }
}

Choose GraphQL

Architecture and Design

You use awesome tools to help you build your application.

Like GraphiQL...

Choose GraphQL

Architecture and Design

You use awesome tools to help you build your application.

Or GraphQL voyager...

Choose GraphQL

Architecture and Design

You use awesome tools to help you build your application.

Or GraphQL Faker...

Choose GraphQL

Architecture and Design

Open sourced by Facebook in 2015.

 

Since open sourcing, we now have a lot of choice!

 

Which platform do you choose?

If you choose a Python, advance to the next section.

Seriously, choose Python, advance to the next section.

Choose Python + Graphene

Architecture and Design

You choose to use the Graphene framework in Python.

Choose Python + Graphene

Architecture and Design

Graphene Django provides some nice built in Views to handle parsing GraphQL requests.

# Makes GraphiQL available at http://oursite.com/graphql !
urlpatterns = [
    ...
    path(
        'graphql/',
        GraphQLView.as_view(schema=schema,graphiql=True)
    ),
    ...
]

Choose Python + Graphene

Architecture and Design

You write your schema.

class StoryType(graphene.ObjectType):

    class Meta:
        interfaces = (graphene.Node, )

    title = graphene.String()
    description = graphene.String()
    author = graphene.String()
    
    passages = graphene.List(PassageType)
    ...

class Query(graphene.ObjectType):
    story = graphene.Node.Field(StoryType)
    ...

schema = graphene.Schema(query=Query)
query myStory {
  story(id:1) {
    id
    title
    passages(first: 2) {
      id
      name
      description
    }
  }
}

Choose Python + Graphene

Architecture and Design

You write resolvers for each of the fields.

class StoryType(graphene.ObjectType):
   ...
   def resolve_title(self, info, **args):
       return self.title
  ...


class Query(graphene.ObjectType):
    ...
    def resolve_story(self, info, **args):
        id = args.get("id")
        return Story.objects.get(id=id)
    ...
query myStory {
  story(id:1) {
    id
    title
    passages(first: 2) {
      id
      name
      description
    }
  }
}

Choose Python + Graphene

Architecture and Design

You write mutations for any requests that result in changes to data.

class StoryInput(graphene.InputObjectType):
    title = graphene.String(required=True)
    description = graphene.String()
    author = graphene.String()

class CreateStory(graphene.Mutation):
    class Arguments:
        input = StoryInput(required=True)

    story = graphene.Field(StoryType)

    @classmethod
    def mutate_and_get_payload(cls, root, info, **input):
        story = Story.objects.create(*input)
        return cls(story=story)

class Mutation(grapene.ObjecctType):
    create_story = graphene.Field(CreateStory)
    ...

schema = graphene.Schema(
    query=Query,
    mutation=Mutation
)
mutation newStory {
  createStory (input: {
    title: "GraphQL",
    author: "Dave A",
    description: "Its pretty cool"
  }) {
    story{
      id
      title
      description
      author
    }
  }
}

Choose Python + Graphene

Architecture and Design

You add new fields easily.

 

You modify existing fields without worrying about breaking clients.

 

You signal for fields to be depreciated with annotations on your schema.

Choose Python + Graphene

Architecture and Design

What a relief!

You made a great API! Advance to the next section.

Your GraphQL API is coming along so well that you've earned some space shore leave.

CHAPTER 2

Advanced API Considerations

Let's rewind some of our REST decisions and continue.

Your Adventure CONTINUES

Authentication and Rate limiting

Now that you've designed a wonderful schema, it's time to share it with the world.

 

You need authentication.

 

Both REST and GraphQL are unopinionated.

 

Rate limiting is similar for both, but more complex schemes might be appropriate for GraphQL.

If you chose REST OR GraphQL, advance to the next section.

Your Adventure CONTINUES

Authentication and Rate limiting

Arbitrary nesting! Maybe not on a public API..

Your Adventure Continues

Scaling and Performance

Traffic is growing day by day,

you better scale that API before it crashes!

 

What kind of API did you build?

If you chose to build a REST API, advance to the next section.

If you chose to build a GraphQL API, advance to the following section.

Success! You've built a robust API with all the data and features you need.

Choose Rest

Scaling and Performance

If you've designed your API well, scaling is something that REST does great.

While challenging, it is a well documented task.

Choose Rest

Scaling and Performance

REST lends itself to HTTP caching.

You can also manage complex requests with per endpoint tuning.

Advance to the next section to see what happens with GraphQL.

Seems to be smooth flying..

Choose GraphQL

Scaling and Performance

Scaling is a little more challenging with GraphQL.

 

HTTP level request caching not generally possible.

 

Tuning at a per resolver level.

Choose GraphQL

Scaling and Performance

Be wary of serial resolution of fields

Story.title

Story.passage

Passage.name

Passage.choices

Passage.choices

Choices.description

Choices.passage

Passage.description

Time

Choose GraphQL

Scaling and Performance

Data loader and proper configuration can help.

Story.title

Story.passage

Passage.name

Passage.is_ending

Passage.choices

Choices.description

Choices.passage

Passage.description

Time

Choose GraphQL

Scaling and Performance

You try to scale your GraphQL service.

 

Alone out in space,

the documentation on edge cases is thin...

and StackOverflow posts on scaling are sparse.

 

You send out an SOS.

Hopefully everything turns out ok.. Advance to the next section.

CHAPTER 3

Frontend Design and Architecture

Let's rewind one last time and continue.

Your Adventure Continues

Building a client web application

Your API is now built, secured and ready to share with the world.

If you chose to build a REST API, advance to the next section.

If you chose to build a GraphQL API, advance to the following section.

Now its time to build a rich web client application.

Choose Rest

Building a client web application

You now have a robust REST API.

There is no standardized way to document REST endpoints, but frameworks like Swagger can help.

Service discovery can be a challenge.

Choose Rest

Building a client web application

You're ready to write some code.

Unfortunately, you need to write boilerplate to handle interactions with the API such as:

 

Asynchronous  fetching...

Error handling...
Data normalization...

And caching.

Choose Rest

Building a client web application

 Further optimization for data fetching may be needed.

Your frontend code may need to deal with underfetching and optimize
N+1 requests.

Choose Rest

Building a client web application

Your frontend code becomes a cage of complexity.

 

You are surrounded by N+1  HTTP requests and sharks and there is no escape!

 

Gulp!

That's no good.. let's try that again with GraphQL.

Choose GraphQL

Building a client web application

You choose to build a web application using your GraphQL API as a backend.

 

You rapidly prototype queries using tools and autogenerated documentation.

Choose GraphQL

Building a client web application

Relay and Apollo are powerful frameworks that allow you to focus on writing application code.

 

However, they are opinionated about your application structure and your GraphQL schema.

Choose GraphQL

Building a client web application

const Story = ({ loading, error, data }) => {
  if (loading) {
    return (<div>Loading the story!</div>)
  }
  if (error) {
    return (<div>There was an error: {error.message}!</div>)
  }

  return (
    <div>
      <div>Title: {data.story.title}</div>
      <div>Author: {data.story.author}</div>
      <div>Description: {data.story.description}</div>
    </div>
  )
}

const client = new ApolloClient({
  uri: 'MY_COOL_GRAPHQL_URL'
});

ReactDOM.render(
  <ApolloProvider client={client}>
    <StoryWithData storyId={1} />
  </ApolloProvider>,
  root
)
const query = gql`
  query myStory ($storyId: String) {
    story(id:$storyId) {
    id
    title
    passages {
        id
        name
        description
      }
    }
  }
`

const options = (props) => ({
  variables: {
    storyId: props.storyId,
  }
})

const withStoryData = graphql(query, {
  alias: 'withStoryData',
  options,
})

const StoryContainer = withStoryData(
  Story
)

Choose GraphQL

Building a client web application

const withCreateStory = graphql(mutation, {
  alias: 'withCreateStory',
  props: mapMutationToProps,
})

const StoryFormContainer = connect(
  withStoryData,
  withCreateStory,
)(StoryForm)

// callback is available in StoryForm component now!
// Pass in values to call the mutation.

const StoryForm = ({ handleCreateStory }) => ({
  <button
    onClick={() => handleCreateStory(
      'Title',
      'Author',
      'Description',
    )}
  >
    Create a story!
  </button>
})
const mutation = gql`
  mutation newStory ($input: StoryInput) {
    createStory (input: $input) {
      story{
        id
        title
        description
        author
      }
    }
  }
`

const mapMutationToProps = ({ mutate }) => ({
  handleCreateStory: (
    title,
    author,
    description
  ) => {
    mutate({
      variables: {
        input: { title, author, description },
      }
      // optimisticResponse: ...,
      // update: ...,
    })
  }
})

Choose GraphQL

Building a client web application

You make more efficient requests from your server.

 

You write less boilerplate code.

 

You can easily improve perceived performance through caching and optimistic mutations.

Choose GraphQL

Building a client web application

The web application with GraphQL and Apollo is really coming together.

Everyone shares some cake and pie in the space break room to celebrate.

Mmm, mm.. Turn to the next section.

EPILOGUE TO Your Adventure

A parting word before the next adventure

It was long, hard work, but you built a robust web application and API backend.

 

GraphQL was useful as a general purpose API with productive frameworks for development.

But REST is still a proven and scalable solution.

 

Next time what will you choose?

If you choose to build a REST API, advance to the next section.

If you choose to build a GraphQL API, advance to the next section.

The end

Thanks for listening!

Questions?

       @dvndrsn

The Rabbit Hole Podcast: http://bit.ly/2Jous2O

Stride Consulting: https://www.stridenyc.com/careers​

Joor: https://jooraccess.com/careers​

Dave Anderson

References

Thanks for listening!

Icons8 (open source icons):

https://icons8.com/

 

Open Clip Art:

https://openclipart.org/

 

Code Sample:

https://github.com/dvndrsn/cyoa-story

 

Graphene (Python):

http://docs.graphene-python.org/en/latest/

 

Apollo (JavaScript):

https://www.apollographql.com/docs/

Choose Your Own Adventure for Client Web Services with GraphQL

By dvndrsn

Choose Your Own Adventure for Client Web Services with GraphQL

In the style of a modern, web-development choose your own adventure, we'll explore the benefits and trade offs of GraphQL and REST. We'll build up an example schema using a choose your own adventure story as the model and leverage that schema in frontend code. We'll also learn about some advanced considerations of GraphQL like performance and scaling.

  • 1,391