StepZen is now part of IBM. For the most recent product information and updates go to
https://www.ibm.com/products/stepzen

Use Interfaces to Access Multiple Backends

Design a schema with interfaces to enable access to multiple backends

An interface is a data type with implementations that map the type to different backends (i.e., to define more than one backend against the same type). This enables StepZen to connect multiple backends to a single type.

Typical examples of GraphQL show how different data can be assembled from different domains. For example, a query like this:

{
    customer (email: "john.doe@example.com") {
        name
        orders {
            createdOn
        }
        weather {
            temp
        }
    }
}

assembles customer, order and weather data.

But what if you have multiple backends that provided customer information? You will want to do two things:

  1. Combine the responses into one format.
  2. Make sure that the right backends get called with the right protocol and parameters.

GraphQL has a powerful feature called interface. We will leverage that to show how this feature can be used to collect data from the various backends. In StepZen, it works like this:

Combine responses into one format

As a first step, define an interface and its common fields (this is pretty straightforward). And define (concrete) types that implement this interface, one for each backend that needs to be supported.

The concrete type must implement all the fields of the interface, but it is free to implement more. This ensures that all responses from the backends can be combined into one interface.

image interface-types

Ensure that the right backends are called

And now define queries on the interface, and queries for concrete types with a new directive @supplies.

This directive lets StepZen know that when a query comes on an interface it must call the concrete query that supplies the interface query. These queries can be against REST, Database, GraphQL or any backend. The only requirement is that the return types of these queries must be the concrete types.

image interface common queries

Let's break this down with a full example, and then examine its various parts.

Example: Using Interfaces to Access Multiple Backends

  1. Create a working directory and cd to that directory.
  2. Create a file interface.graphql with the following code in it:
    interface Customer {
        id: Int
        name: String
        email: String
        city: String
        street: String
        zip: String
        state: String
        country: String
    }
    type Query {
        customerById(id: ID!): Customer
        customersByCity(city: String!): [Customer]
    }
  3. Build the first backend that connects to a REST endpoint. Create a file customer1.graphql with the following code in it:
    type Customer1 implements Customer {
        id: Int
        name: String
        email: String
        city: String
        street: String
        zip: String
        state: String
        country: String
    }
    type Query {
        customer1ById(id: ID!): Customer1
            @supplies(query: "customerById")
            @rest(endpoint: "https://json2api-anant-p2axj4bzta-uw.a.run.app/customers/$id",
                  transforms: [{pathpattern:"<>*", editor: "jq:.[]|{name,email,id,city:.address.city,state:.address.stateProvince,street:.address.street,zip:.address.postalCode,country:.address.countryRegion}"}])
        customers1ByCity(city: String!): [Customer1]
            @supplies(query: "customersByCity")
            @rest(endpoint: "https://json2api-anant-p2axj4bzta-uw.a.run.app/customers?q=address.city+eq+$city",
                  setters: [{field: "city", path: "address.city"}, 
                  {field: "state", path: "address.stateProvince"}, 
                  {field: "country", path: "address.countryRegion"}, 
                  {field: "street", path: "address.street"}, 
                  {field: "zip", path: "address.postalCode"}])
    }
  4. Build the second backend that connect to a MySQL database. Create a file customer2.graphql with the following code in it:
    type Customer2 implements Customer {
        id: Int
        name: String
        email: String
        city: String
        street: String
        zip: String
        state: String
        country: String
    }
    type Query {
        customer2ById(id: ID!): Customer2
            @supplies(query: "customerById")
            @dbquery (type: "mysql", query: "select c.email, c.id, c.name, a.street, a.city, a.postalcode as zip, a.countryRegion as country, a.stateProvince as state from customer c, address a, customerAddress ca where ?=ca.customerId and ca.addressId=a.id", configuration: "mysql")
        customers2ByCity(city: String!): [Customer2]
            @supplies(query: "customersByCity")
            @dbquery (type: "mysql", query: "select c.email, c.id, c.name, a.street, a.city, a.postalcode as zip, a.countryRegion as country, a.stateProvince as state from customer c, address a, customerAddress ca where c.id=ca.customerId and ca.addressId=a.id and a.city=?", configuration: "mysql")
    }
  5. Give the credentials for the MySQL. Create a file config.yaml with the following code in it:
    configurationset:
        - configuration:
              name: mysql
              dsn: testUserIntrospection:HurricaneStartingSample1934@tcp(35.224.227.100)/introspection
  6. Finally, create a file index.graphql with the following code in it:
    schema @sdl (files: ["interface.graphql", "customer1.graphql", "customer2.graphql"]) {
      query: Query
    }
  7. Now issue stepzen start, pick an endpoint, and issue the following two queries.
    query MyQuery {
      customersByCity(city: "Boston") {
        email
        name
      }
      customerById(id: "101") {
        email
        name
      }
    }
    The first query goes against both the backends and picks all customers who live in Boston. And the second query picks the right customer from one backend (in this case REST).

Deconstructing the GraphQL endpoint

  1. The file interface.graphql defines the types and queries for the interface.

  2. The file customer1.graphql defines the concrete implementations of these interface types and queries for the REST backend.

    1. type Customer1 implements interface Customer. Pretty straightforward. You must implement all the fields of the interface, but can implement others. In this case, there are no others.

    2. type Query contains two queries, one for each query in the interface. The query definition is as follows:

    • Its return type is the concrete type.
    • It @supplies the interface type (so that StepZen knows to call this query).
    • Its implementation is a REST call made against an endpoint.
    • If you do the following curl and note the shape of the response, it needs to be modified to conform to the GraphQL type structure. The transforms argument in the first query does that.
    curl https://json2api-anant-p2axj4bzta-uw.a.run.app/customers/101` 

    In this case, we use jq to do the transformation. The pathpattern can be ignored, with jq editor, it will always be "<>*". The jq specification is a simple JSON transformation that takes the response from the backend, and converts it into the JSON that is right for the GraphQL type Customer1.

    • If you do the following curl
    curl https://json2api-anant-p2axj4bzta-uw.a.run.app/customers?q=address.city+eq+Boston` 

    and note the shape of the response, it needs to be modified to conform to the GraphQL type structure.

    The setters argument is a different way of shaping the response from the backend into a JSON shape that conforms to the concrete type declarations. It is a different and simpler way to do these transformations, but cannot do the rich stuff that jq can do. These two examples here are equivalent, pick whichever works for you.

  3. The file customer2.graphql defines the concrete implementations of these interface types and queries for the MySQL backend.

    1. type Customer2 implements interface Customer.
    2. type Query contains two queries, one for each query in the interface. The query definition is as follows, as in the customer1.graphql file:
    • Its return type is the concrete type.
    • It @supplies the interface type (so that StepZen knows to call this query).
    • Its implementation is a SQL call made against an endpoint (it is doing a three way join). The database is the same as the database in this blog. Feel free to explore it.
    • The shaping of the response is done using the as clause.

That's it. StepZen routes a query on the interface to the right concrete queries that supply that query.

Each of those queries, in turn, get called with the right protocol (in this case REST, but could be SQL or GraphQL). And the transforms ensures that the shape of the returned data conforms to the type.