In Part 1 we covered how clients interact with GraphQL. In this post, we’ll look at the implementation of a GraphQL server using Go and PostgreSQL.

Implementing the Server Side of a GraphQL Application

We wrote our server code in Go but the basic model for server-side GraphQL is language-independent. GraphQL servers are built around the idea of a resolver. Resolvers provide the implementations for accessing fields of our GraphQL types. In the excellent Go server we’re using (github.com/neelance/graphql-go), a root resolver has methods for each of the queries and mutations defined in our schema. Here is the resolver for our simplest query:

func (r *Resolver) LogLevel() *string {
    levelstr := log.GetLevel().String()
    return &levelstr
}

There’s not much going on here because a log level is just a string. Things get more interesting when we’re resolving an instance of a structured type:

func (r *Resolver) Feature(
    ctx context.Context,
    args *struct{ ID string }) *featureResolver {
    return GetFeatureById(ctx, args.ID)
}

func GetFeatureById(ctx context.Context, id string) *featureResolver {
    var name string
    var f feature
    err := queryRowDB(featureByID, id).Scan(&name)
    if err != nil {
        makeWarnFunc("retrieving feature", id)(err)
        return nil
    }
    f.ID = id
    f.Name = name
    return &featureResolver{ctx, &f}
}

In this example, a “Feature” is an aspect of the application that can be turned on or off for A/B testing. Features are simple named values so the feature type just has two fields: ID and Name. The Feature method of the root resolver looks up the feature by id. The lookup function GetFeatureById does a database query and constructs a sub-resolver for feature. This resolver is used to retrieve fields of the feature object:

func (r *featureResolver) ID() string {
    return r.f.ID
}

func (r *featureResolver) Name() string {
    return r.f.Name
}

When a feature resolver is returned to the GraphQL server, it invokes these methods as required to fetch fields mentioned in a query or mutation.

Since features are simple objects, the buck stops here. What happens though, you might ask, when a sub-resolver has references to other structured types (or lists of them)? As you likely suspect, more of the same. The methods for those fields return other resolver objects, etc.

Given this one-level-at-a-time fetch-as-needed model, the most direct way to implement these resolvers is to have the methods for reference fields perform queries on demand. This is, in fact, how we initially implemented our server. As you can probably imagine, however, this leads to a lot of queries. Consider the problem of user authorization. In order to authorize users, we need to know the set of organizations a user can access, along with whether or not they are admins or writers for said organizations. We also need to know about the plan associated with each organization, etc. Here is the basic query for establishing context:

query {
    userByIdentity(service: "github", identity: ${user.username}) {
        terms
        orgs {
            id name maxWriters plan {name} planElements{name} owner {identity}
            writers {identity} admins {identity}
        }
    }
}

In the trivial case we started with above (remembering that we now call him “snake”) we get:

{
    "userByIdentity": {
        "orgs": [
            {
                "admins": [
                    {
                        "identity": "snake"
                    }
                ],
                "id": "7aa6ad6e-0e85-11e7-8a21-df7b3b475de9",
                "maxWriters": 1,
                "name": "snake",
                "owner": {
                    "identity": "snake"
                },
                "plan": {
                    "name": "Freemium"
                },
                "planElements": [
                    {
                        "name": "downloadComposeFiles"
                    }
                ],
                "writers": [
                    {
                        "identity": "snake"
                    }
                ]
            }
        ],
        "terms": true
    }
}

This is the simplest case, in which the user is only associated with a single organization: his freemium individual organization. Even in this case, though, the number of queries in a naive approach is relatively large: nine queries are needed for a user with only one organization and no other users associated with the organization. The numbers grow rapidly as the number of organizations and org members increase. It would be nice to get more of the data up front and reduce the number of queries. How should we do that, though? Relational queries are not generally well suited to deeply nested structures as the result tables are flat (leaving aside group queries which really wouldn’t help here).

One possibility for optimizing queries that comes to mind is to cache fetched values. This is certainly possible and even fairly easy in a single server case. A cache could simply map (id, fieldname) pairs to values and flush them when such fields are updated. Of course, in a multi-server scenario, this would require a distributed cache. This is still not necessarily a deal breaker. However, it turns out that PostgreSQL has our backs here. Some extensions to the relational model supported by PostgreSQL make it a uniquely powerful fit to GraphQL.

You may have heard that PostgreSQL now has JSON support. If so, you’d be forgiven for assuming that just means having a JSON column type (possibly with query support for selecting based on fields within JSON documents). Well, it certainly does have this sort of functionality, but that’s only the beginning. In fact, PostgreSQL’s JSON support combined with its array support is the answer we’re looking for. Let’s look at a simple example. The definition of a “Plan” in the schema for our organizational model is:

# A plan (platinum, etc.)
type Plan implements FeatureOwner {
    # The ID of the plan
    id: String!

    # What others call this plan
    name: String!

    # The orgs associated with the plan
    orgs: [Org!]!

    # The number of users with write privileges
    maxWriters: Int!

    # The plan elements associated with the plan
    elements: [Element!]!
}

Our initial implementation of a plan involved one query that returned the simple values (id, name, maxWriters) and three other follow-on queries to obtain the related instances. By far, the most commonly queried connected set is the elements (it’s even in our basic context query above). Is there an easy way to retrieve the elements for a plan in the initial query? Yes, there is! Here’s a PostgreSQL query to select a plan with its elements:

SELECT planname, maxwriters,
    ARRAY_TO_JSON(
        ARRAY(SELECT JSON_BUILD_OBJECT('ID', id::text, 'Name', elementname)
              FROM planelements, elementmap WHERE plan = $1 AND element = id))
FROM plans WHERE id = $1

Let’s unpack what’s going on here; we’re selecting rows containing three columns each: planname, maxwriters, and a JSON array containing plan element JSON objects. PostgreSQL allows us to turn the results of a query into an array and then turn the array into JSON. Nice.

Let’s run the query in psql for a particular plan and generate HTML output:

postgres=# \H
Output format is html.
postgres=# SELECT planname, maxwriters,
    ARRAY_TO_JSON(
        ARRAY(SELECT JSON_BUILD_OBJECT('ID', id::text, 'Name', elementname)
              FROM planelements, elementmap WHERE plan = '2a1fcf62-0da7-11e7-a2d0-233e9294c3b3'
                                            AND element = id))
FROM plans WHERE id = '2a1fcf62-0da7-11e7-a2d0-233e9294c3b3';
<table border="1">
  <tr>
    <th align="center">planname</th>
    <th align="center">maxwriters</th>
    <th align="center">array_to_json</th>
  </tr>
  <tr valign="top">
    <td align="left">BasicTeam</td>
    <td align="right">5</td>
    <td align="left">[{&quot;ID&quot;
    : &quot;2a1f4c7c-0da7-11e7-a2d0-57f24a2d7be7&quot;, &quot;Name&quot; 
    : &quot;downloadComposeFiles&quot;},{&quot;ID&quot; 
    : &quot;2a1fa88e-0da7-11e7-a2d0-dfe6f61e9e53&quot;, &quot;Name&quot;
    : &quot;privateModels&quot;}]</td>
  </tr>
</table>
<p>(1 row)<br />
</p>

The generated table is below:

planname maxwriters array_to_json
BasicTeam 5 [{“ID” : “2a1f4c7c-0da7-11e7-a2d0-57f24a2d7be7”, “Name” : “downloadComposeFiles”},{“ID” : “2a1fa88e-0da7-11e7-a2d0-dfe6f61e9e53”, “Name” : “privateModels”}]

(1 row)

Fantastic!

With a bit of work, this can be extended to arbitrary nesting levels. We’re able to obtain all the data in the context GraphQL query above in a single database query; so much for latency! I won’t include that full query as it looks a bit intimidating. Rest assured, however, that it’s just a straightforward extension of the same approach. Problem solved.

Summary

  • GraphQL is a new approach to JSON-based web apis that provides significant flexibility advantages over REST.
  • GraphQL apis are driven by schemas that define types and entry points for queries and mutations.
  • A GraphQL api is exposed through a single HTTP end point.
  • GraphQL allows the specification of arbitrarily nested queries.
  • Relational databases are a good match to GraphQL schemas.
  • PostgreSQL provides array and JSON extensions that make it easy to process nested queries for optimization of complex GraphQL requests.
  • Docker helps future proof service development by minimizing cycle times and reducing friction in deployment.

Thanks for reading!


Interested in learning more about Yipee.io? Sign up for free to see how Yipee.io can help your team streamline their development process.