The Best Code Is No Code: Leveraging GraphQL Assets with Gloo API Gateway and Remote Executors

Jeff Atwood, the creator of Stack Overflow, famously wrote a 2007 blog entitled “The Best Code is No Code At All.”

Bill Gates expressed a similar sentiment: “Measuring programming progress by lines of code is like measuring aircraft building progress by weight.”

Great frameworks apply code-shrinking principles and give us the opportunity to leverage existing software assets while minimizing the amount of custom logic we craft ourselves. GraphQL is a good example of this. It provides a simple, intuitive query language that can stitch together the schema of multiple backend systems to deliver just the information the client requires in an efficient way.

The Gloo Gateway platform takes GraphQL a few steps farther by allowing engineers to:
1. Discover non-GraphQL API contracts like OpenAPI / REST and gRPC and convert them into GraphQL schema;
2. Specify all of this declaratively. (“The best code is no code at all.”); and
3. Deploy this in an existing cloud-native API Gateway. No dedicated GraphQL server required.

In short: Reuse existing assets. Declaratively. And simplify operations.

Much of the focus in the early days of Gloo GraphQL has been on the reuse of non-GraphQL interfaces, and rightfully so. Delivering the ability to publish OpenAPI and gRPC interfaces as GraphQL while writing ZERO code is indeed headline material. In Gloo-speak, this is called a Local Executor. It means that Gloo’s GraphQL filter in Envoy is using its own configuration to ascertain its schema and the query and mutator operations available to it. And that’s perfect for cases where you simply want to convert existing interfaces to GraphQL without the underlying application being aware that it’s publishing a GraphQL interface.

But that’s not the end of the story. What if you already have project teams who have built out GraphQL interfaces? Don’t you want to leverage those too? Wouldn’t you want to publish and maybe even stitch those graphs into supergraphs that span both types of interfaces? Of course you would.

That’s the purpose of Remote Executors. This is a new feature in the latest versions of Gloo GraphQL that allow you to reuse existing GraphQL-aware assets and compose them with other assets that are also being managed by Gloo’s GraphQL Envoy filter.

In this blog post, we’ll work through an example where we demonstrate how to solve this problem using the standalone Gloo Edge API Gateway with its GraphQL component. Feel free to follow along with us in your own Kubernetes cluster.

(And don’t worry. For fans of API gateways integrated more tightly with an Istio-based, multi-tenant-aware service mesh, the Gloo Mesh product will be releasing this same capability Real Soon Now. Watch this space for more details.)

Prerequisites

To complete this guide, you’ll need a Kubernetes cluster and associated tools, plus an instance of Gloo Edge Enterprise. Note that there is a free and open source version of Gloo Edge, but the GraphQL capabilities are only available to Enterprise subscribers. Use this guide if you need to install Gloo Edge Enterprise.

We ran the tests in this blog on Gloo Edge Enterprise v1.12. Note that versions of Gloo Edge prior to v1.12 will not work with this example.  If you are using an Enterprise v1.12 beta to run this exercise, make sure that is at or later than v1.12.0-beta12.

If you don’t already have access to the Enterprise bits of Gloo Edge, you can request a free trial here. Be sure to ask for the GraphQL add-on if you’d like to follow along with your own installation.

We used GKE with Kubernetes v1.22.8 to test this guide, although any recent version with any Kubernetes provider should suffice.

For this exercise, we’ll also use some common CLI utilities like kubectl, curl, and jq. Make sure these prerequisites are all available to you before jumping into the next section. I’m building this on MacOS but other platforms should be perfectly fine as well.

Explore the Countries App

The Countries app is a great little GraphQL example that’s available on the public Internet. You can access its GraphQL playground here. In the screenshot below, we’re using the playground to query Countries for information about the country whose country code is CW. Now you know something about the Caribbean island nation of Curacao (which I highly recommend as a diving and snorkeling destination.)

Install an Upstream

Let’s install a Gloo Edge Upstream to our cluster that acts as a facade for the public Countries service. Since this service lives outside our cluster, we’ll create a simple static Upstream ourselves to point to it.

apiVersion: gloo.solo.io/v1
kind: Upstream
metadata:
  name: countries-us
  namespace: gloo-system
spec:
  static:
    hosts:
      - addr: countries.trevorblades.com
        port: 80

Gloo Edge also has the ability to automatically discover and build Upstreams for services installed in our cluster, including facilities to parse OpenAPI / Swagger and gRPC interfaces to automatically build GraphQL schema. But we’ll save that topic for another time. You can learn more about automatic service and schema discovery here and here.

Run this command to apply our static Upstream to your Kubernetes cluster:

kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-edge-use-cases/main/graphql-remote-executor/01-countries-us.yaml

You should see this response:

upstream.gloo.solo.io/countries-us created

Create a GraphQLApi for the Countries app

The GraphQLApi object in Gloo GraphQL is a custom Kubernetes Custom Resource that specifies the details of the schema, including Queries and Mutators, that are supported by the underlying service. One of the nicest features of Gloo GraphQL Local Executors is that they have the ability to construct this schema for you automatically based on the underlying API contracts.

However, the Countries app is neither under our control nor installed in our cluster. It is managed remotely, hence the name Remote Executor to describe how we will access it.

Building a Remote Executor is a two-step process:
1. Grab the schema from the remote app; and
2. Build a GraphQLApi object that specifies the GraphQL schema you want to use and a reference to the Upstream object that represents the remote system that supports this schema.

To get the schema itself, we’ll take advantage of the fact that the Countries app exports its schema via its GraphQL playground, as shown below in the Schema panel on the right.

We downloaded this schema from the playground and added it unchanged to a new GraphQLApi object along with a reference to the Upstream we created in the previous step. Here are some of the key bits of this new object:

apiVersion: graphql.gloo.solo.io/v1beta1
kind: GraphQLApi
metadata:
  name: countries-remote
  namespace: gloo-system
spec:
  executableSchema:
    executor:
      remote:
        upstreamRef:          # <<<< This upstreamRef tells Gloo where requests for this schema should be directed
          name: countries-us
          namespace: gloo-system
    schemaDefinition: |       # <<<< This is the schema definition we copied over from the Countries playground
      directive @key(fields: String!) on OBJECT | INTERFACE
      directive @extends on OBJECT | INTERFACE
      directive @external on OBJECT | FIELD_DEFINITION
      directive @requires(fields: String!) on FIELD_DEFINITION
      directive @provides(fields: String!) on FIELD_DEFINITION
      type Country {
        code: ID!
        name: String!
        native: String!
        phone: String!
        continent: Continent!
        capital: String
        currency: String
        languages: [Language!]!
        emoji: String!
        emojiU: String!
        states: [State!]!
      }
... clipped ...

Run this command to apply our static GraphQLApi to your Kubernetes cluster:

kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-edge-use-cases/main/graphql-remote-executor/02-countries-gql.yaml

You should see this response:

graphqlapi.graphql.gloo.solo.io/countries-remote created

Install a Virtual Service

VirtualServices are Gloo Edge custom resources that manage routing and policy enforcement on behalf of an upstream service, like the Countries service in this case. We will begin with a simple configuration that forwards requests for any domain with the /graphql path to the Countries service. In addition, we configure this policy to use the GraphQLApi spec that we created in the previous step to manage the interaction with this GraphQL service.

The VirtualService spec to enable this is remarkably simple:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: countries-vs
  namespace: gloo-system
spec:
  virtualHost:
    domains:
    - '*'
    routes:
    - graphqlApiRef:
        name: countries-remote
        namespace: gloo-system
      matchers:
      - prefix: /graphql

Let’s apply this VirtualService now.

kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-edge-use-cases/main/graphql-remote-executor/03-countries-vs.yaml

This is the expected response:

virtualservice.gateway.solo.io/countries-vs created

Test, Test, Test

Curl the Countries App Directly

We discussed earlier how to use the GraphQL playground to query the Countries app, as shown below. If we use the COPY CURL button, we can get a magic command to issue this same query from a command shell.

Let’s take the curl command generated by the playground, add just a bit of formatting with jq, and confirm that it works just as in the playground.

curl 'http://countries.trevorblades.com/' -H 'Accept-Encoding: gzip, deflate, br' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'DNT: 1' -H 'Origin: http://countries.trevorblades.com' --data-binary '{"query":"{\n  country(code: \"CW\") {\n    name\n    native\n    capital\n    currency\n    languages {\n      code\n      name\n    }\n  }\n}"}' --compressed -s | jq

You can expect the same response you saw in the playground:

{
  "data": {
    "country": {
      "name": "Curacao",
      "native": "Curaçao",
      "capital": "Willemstad",
      "currency": "ANG",
      "languages": [
        {
          "code": "nl",
          "name": "Dutch"
        },
        {
          "code": "pa",
          "name": "Panjabi / Punjabi"
        },
        {
          "code": "en",
          "name": "English"
        }
      ]
    }
  }
}

Note that this invocation does not touch the Gloo Gateway endpoint at all. It is being processed by the Countries app directly.

Curl the Gloo-Managed Endpoint

Now this is the moment of truth. We have demonstrated that the service works both in the playground and from the curl CLI. What happens if we replace the countries.trevorblades.com endpoint with a reference to the Gloo proxy that applies our GraphQLApi version of the schema and manages its forwarding to the Countries app?

Let’s find out by issuing our slightly modified version of the curl command. Note that we use glooctl proxy url command to locate the address on which our API gateway is listening. And we add the /graphql path to trigger the routing of the request to our countries-vs VirtualService.

curl $(glooctl proxy url)/graphql -H 'Accept-Encoding: gzip, deflate, br' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'DNT: 1' -H 'Origin: http://countries.trevorblades.com' --data-binary '{"query":"{\n  country(code: \"CW\") {\n    name\n    native\n    capital\n    currency\n    languages {\n      code\n      name\n    }\n  }\n}"}' --compressed -s | jq

Expect to see a response that precisely matches what we saw when we issued the query directly against the trevorblades.com endpoint.

{
  "data": {
    "country": {
      "name": "Curacao",
      "native": "Curaçao",
      "capital": "Willemstad",
      "currency": "ANG",
      "languages": [
        {
          "code": "nl",
          "name": "Dutch"
        },
        {
          "code": "pa",
          "name": "Panjabi / Punjabi"
        },
        {
          "code": "en",
          "name": "English"
        }
      ]
    }
  }
}

Voila! You have configured a Remote Executor to bring an external GraphQL service under Gloo Gateway management. In other words, you now have a live service available from your environment with a schema that you can stitch into other, even non-native-GraphQL services in your cluster. And publish all of that to clients with a single, seamless GraphQL interface. Most importantly, you did all that using Configuration-as-Code and without writing a single line of custom logic.

“The Best Code is No Code at all.” You are a Beast. Nice Work!

Next Steps

But we’re just getting started with all the cool things you can do for your newly managed external GraphQL service. Here are some other ideas you might want to explore.

Try some more Countries queries

The nice thing about GraphQL schema is you have an almost limitless supply of permutations to your query that require zero changes to the supporting services. For example, what if you’d like a complete list of all the Countries supported by the service, along with their two-character country codes and capitals? Not a problem. Try out this query:

curl -s -X POST \
-H "Content-Type: application/json" \
-d '{ "query": "{ countries { code name capital } } " }' \
$(glooctl proxy url)/graphql | jq .

You’ll receive a lot of data in return:

... snip ...
        "capital": "Pretoria",
        "code": "ZA",
        "name": "South Africa"
      },
      {
        "capital": "Lusaka",
        "code": "ZM",
        "name": "Zambia"
      },
      {
        "capital": "Harare",
        "code": "ZW",
        "name": "Zimbabwe"
      }
    ]
  }
}

Stitch the Countries schema together with your apps

A huge value of GraphQL in general and Gloo Gateway’s approach in particular is the ability to take unrelated services and integrate them using GraphQL to coordinate the service interactions. The magic sauce that makes this happen is schema stitching. We’ll revisit this in future blogs. But for now take a look at these Gloo Gateway docs.

Mix in some gateway security goodness

What if you want to publish your GraphQL creations externally, but you need to secure it from bad actors? Maybe you’d like to encrypt external communication using TLS, or add rate limiting, or integrate with an external IdP via OIDC to authenticate external users, or unleash Open Policy Agent to authorize those users.

You’re in luck! Because your GraphQL APIs are now integrated with a world-class, cloud-native API gateway, you can configure policies to do all of those things for free. And they’ll work not only for your GraphQL endpoints, but for any service endpoint. No longer is your GraphQL server a unicorn that is managed separately from the rest of your operational infrastructure.

Cleanup

If you’d like to clean up the work you’ve done, simply delete the Kubernetes resources we’ve created over the course of this exercise.

kubectl delete upstream countries-us -n gloo-system
kubectl delete graphqlapi countries-remote -n gloo-system
kubectl delete virtualservice countries-vs -n gloo-system

Expect to see these responses.

upstream.gloo.solo.io "countries-us" deleted
graphqlapi.graphql.gloo.solo.io "countries-remote" deleted
virtualservice.gateway.solo.io "countries-vs" deleted

Learn More

If you’ve followed along with us this far, then congratulations! You’ve not only navigated my gnarly Nemo puns and asinine alliterations, but you’ve also learned how to configure Gloo Edge to handle lavishly large message payloads.
For more information, check out the following resources.