Curled Cloudy Code

How to automatically load graph types in the DI container.

GraphQL.NET relies a lot on DI containers to get instances for certain graph types. At first it might be a bit daunting to come across an error along the likes of this:

GraphQL.ExecutionError: No service for type '...' has been registered.

It is GraphQL’s way of telling the world that it could not find an instance of a certain type in the DI container.

Solving it the easy way

The usual way to solve this problem is to register a new instance with the DI container which usually goes like this:

services.AddSingleton<SomeQuery>();

It works. But for large GraphQL api’s it doesn’t scale well, and it’s annoying for the developers to be remembered to register types with the DI container everytime a new type has been added.

Solving it for once and for all

Due to habit I tend to cluster the same types of classes together in the same folder/namespace.

My folder structure looks a bit like this:

Graph
|- Types
   |- Enum
   |- Input
   |- Interface
   |- Object

The nice thing about this is that all different types of ObjectGraphType derivatives are stored in the Graph.Types namespace, while also having a distinction between the several subtypes.

Now in order to solve the registration problem for once and for all you can add the following code block to the place where you usually register your graph types.

Assembly
    .GetExecutingAssembly()
    .GetTypes()
    .Where(q => q.IsClass
        && (q.Namespace?.StartsWith("Graph.Types") ?? false))
    .ToList()
    .ForEach(type => services.AddSingleton(type));

The code shown above invokes some reflection magic to retrieve all classes in the executing assembly (which in my case is also the assembly in which I have defined the graph types), filter them for the namespace, which is supplied as string, and add them to the DI container.

The only thing you need to do is to replace the namespace, and verify whether you pull your metadata from the correct assembly.

Use of reflection is being scrutinized because it is ‘slow’ and there are usually better alternatives. In this case it will lead to more maintainable code, and it is only ran during startup either way ¯\_(ツ)_/¯

Implementing pagination with GraphQL.NET and Relay

Trigger warning:
In this article I am bashing quite a bit on traditional REST api’s. Not that you can’t have a great architecture with REST api’s, but from my experience these types of API’s are badly implemented more often than not. Same goes for the tech described in this article. If used incorrectly you’ll end up voluntary amputating one of your limbs.

As a friend noted: “Talk to REST and get a certain object, or ask GraphQL and retrieve some information”.


0. Contents

  1. Introduction
  2. Background
    2.1 Cursor Based Pagination
    2.2 Connection Specification
    2.3 Specification, but simplified
    2.4 The Connection’s Structure
    2.5 Generating Cursors
  3. Overview
  4. Architecture
  5. The GraphQL Connection Endpoint
  6. Data Retrieval
    6.1 [Contextual] Model and Graph Types
    6.2 Resolving Arguments
    6.3 Data Filters
    6.4 Slicing for Pagination
    6.5 Creating the Connection object
  7. Overview of a real-world implementation
    7.1 The Connection
    7.2 Data access function
    7.3 Defining order on a connection
  8. Other Resources

1. Introduction

Pagination is always a topic that’s a bit tricky to tackle. The goal is to give as much freedom as possible for (client side) queries, while staying in control of the data that goes out.

GraphQL is no difference, but thankfully the Relay extension has been developed on top of GraphQL. Relay consists of a set of conventions which solidifies the GraphQL specification a bit more so that client side tools can quickly scaffold application logic for said imaginative api.

For the assumptions Relay makes about a GraphQL server, see this document.

2. Background

While quite poorly documented in the GraphQL.NET docs (at the time of writing), functionality for Relay compatible endpoints has been implemented. Some of this logic resides in the main graphql-dotnet package, while other tools and helpers reside in the relay package.

Because Relay is only a specification of assumptions about interfaces and available methods, it is fairly easy to implement yourself. Consider most of the helper methods provided by the graphql-dotnet/relay package as suggestions, and think about writing specialized variants which suit your use-cases. More on that later.

If you are already familiar with the design philosophy that goes with GraphQL, and indirectly with the graphql-dotnet package, feel free to skip to the overview.

2.1 Cursor Based Pagination

The biggest part of the Relay specification consists of details on the Connection types. A connection is a type which which describes a list of objects, and arguments to filter and slice this list. The arguments available in a connection by default:

These 4 arguments make up for a powerful concept which is called cursor based pagination. With this model every object has an unique identifier (you most certainly already have one already anyway), which you can use in order to create slices of data.

These slices of data are relative to one another. For the first call to the api you could provide the first argument to a connection. For all subsequent calls you would use the cursor of the last element in this list to retrieve a new slice of data.

This type of pagination, although it takes a bit more work implementing than traditional offset based pagination, is a delight to work with. On the client side it allows you to implement both traditional page based pagination methods, as well as more progressive and user-friendly paradigms. Another improvement over offset based pagination is that this method works fine with realtime data. A lot of complexities that are inherent to combining realtime data and offset based pagination just simply disappear.

If you look on Google you’ll find plenty of reasons why we should stop doing offset based pagination. Honestly I do not care about the technical implications at all, but the thing is that it’s just not user-friendly. The beginning of this article digs into that a bit deeper.

2.2 Connection Specification

For a detailed specification about connections and cursors, see this document: https://facebook.github.io/relay/graphql/connections.htm. Item 4.3 of the Relay specification covers expected behaviour of the pagination algorithm.

2.3 Specification, but simplified

The specification describes the way data should be retrieved. The following steps could be followed:

  1. Apply your data filtering operations
  2. If after is specified:
    • Take all data starting from this cursor
  3. If before is specified:
    • Take all data before this cursor
  4. If first is specified:
    • Take the specified amount of entities from the beginning.
  5. If last is specified:
    • Take the specified amount of entities from the end.
  6. Return data

The specification defines the cases where all parameters (after, before, first and last) are specified. There are few use-cases where using all these parameters at the same time will make any sense at all. It does provide some consistency and predictability to the use of cursors, though.

While it is possible to run all filter operations on the .NET runtime, I would totally NOT recommend doing so. It would have the implication that all data has to be loaded locally, which does seem to result in the infamous OutOfMemoryException in my case. If it works for you now but your data starts scaling later, it would be incredibly difficult to keep it all running smoothly after a while. I recommend to invest some time researching the different options for cursor based pagination within your data provider. E.g. MsSql, MySql, ElasticSearch or whatever your tool of choice is. If you still decide you want to shoot your own foot off and do pagination in-memory, go ahead. There are some methods available in the graphql-dotnet/relay project for which I have created the following extension method:

public static Connection<TSource> ToConnection<TSource, TParent>(
    this IEnumerable<TSource> items,
    ResolveConnectionContext<TParent> context,
    int sliceStartIndex,
    int totalCount,
    bool strictCheck = true)
{
    return ToConnection(items, context, sliceStartIndex, totalCount, strictCheck);
}

But again, this is definitely the unfun way to do it! If this is all you’ve been looking for you can close this article now. This article will further elaborate on offloading these filter and slice operations to an external data-store.

2.4 The Connection’s Structure

The connection is not only these four arguments you can slice your data with. Another part of the connection types are some subtypes to help query through the data. The main structure looks as follows:

friends(first: 10, after: "opaqueCursor") {
  edges {
    cursor
    node {
      id
      name
    }
  }
  pageInfo {
    hasNextPage
  }
}

As taken from this example.