Search code examples
graphqlgraph-databasesgraphql-js

GraphQL resolve more than one interface


I am working with a native graph database.

My data objects are composed from many smaller sets of attributes, and I would like to be able to query all of them at once without adding extra artificial layers in my schema. The attributes are very well defined, but the composition of them is not.

Assume it is easy (trivial) for me to output this object from a query:

{
  "name": "Zapdos",
  "wingspan": 40,
  "airspeed": 120,
  "maxVoltage": 100000000
}

Assume that the wingspan and airVelocity properties come from a FlyingType attribute, and maxVoltage from an ElectricType attribute. These and other attributes are well defined. Other pokemon may mix and match attributes that apply to them, and defining all combinations of them might not make sense.

Ideal Scenario

Is it possible to run the following query and get data in the same format back?

Would graphql-js have to handle an array returned by resolveType function? Does the default resolveType function stop at the first true value from calling isTypeOf?

query {
  Pokemon(name: "Zapdos") {
    name

    ... on isFlyingType {
      wingspan
      airVelocity
    }

    ... on isElectricType {
      maxVoltage
    }
  }
}

what is probably the case

Do I actually need to build a new type with everything that I need? I have many attributes that compose my data, and that would mean A LOT of types to account every combination of interfaces. E.g. must I do the following?

type FlyingElectricPokemon implements isPokemon, isFlyingType, isElectricType {
  name: String!
  wingspan: Int
  airVelocity: Int
  maxVoltage: Int
}
query {
  Pokemon(name: "Zapdos") {
    name

    ... on FlyingElectricPokemon {
      wingspan
      airVelocity
      maxVoltage
    }
  }
}

Am I just fighting too hard to avoid the strong typing of GraphQL on my rather weakly-typed graph data?

Work around but with added complexity

I would prefer to avoid the following add complexity:

type Query {
  Pokemon(name: String): Pokemon
}

type Pokemon {
  name: String!
  attributes: [isAttribute!]
}

interface isAttribute {
  type: String!
}

type FlyingType implements isAttribute {
  type: String!
  wingspan: Int
}

type ElectricType implements isAttribute {
  type: String!
  maxVoltage: Int
}
query {
  Pokemon(name: "Zapdos") {
    name
    attributes {
      ... on FlyingType {
        wingspan
        airVelocity
      }
      ... on ElectricType {
        maxVoltage
      }
    }
  }
}

requiring data in the following format

{
  "name": "Zapdos",
  "attributes": [
    { "type": "flying", "wingspan": 40 },
    { "type": "electric", "maxVoltage": 100000000 }
  ]
}

This latter bit I actually got to work on Launchpad.


Solution

  • That's an interesting scenario. I don't think vanilla GraphQL really offers a vehicle to achieve what you describe. The problem is that, in the end, any object being returned has to have exactly one type. __resolveType cannot return an array, and even if you declare a __isTypeof for multiple types that will evaluate true for a given object, only the first one that evaluates true will be associated with that particular object.

    At the end of the day, if your intent is to return a subset of possible attributes (based on the Pokemon's type or types), the simplest solution may be to just make all possible attributes into fields of the Pokemon type and not worry about trying to implement interfaces around the types. This achieves the flat data structure you want in your response. The biggest downside is you will have a possibly large number of null fields returned as part of the response.

    You can also look into something like graphql-s2s, which supports type inheritance and generics.