Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Real world example of GraphQLInterfaceType and GraphQLUnionType

I'm having a hard time understanding when to use GraphQLInterfaceType and GraphQLUnionType.

I've RTFMs:

  • http://graphql.org/docs/api-reference-type-system/#graphqluniontype
  • https://github.com/mugli/learning-graphql/blob/master/7.%20Deep%20Dive%20into%20GraphQL%20Type%20System.md

Can anyone offer up a real world example when these would be useful to get it through my thick head?

like image 826
Matt K Avatar asked Jan 11 '16 16:01

Matt K


2 Answers

Both are meant to help you design a schema with a heterogeneous set of types, and you could achieve the same functionality using both, but GraphQLInterfaceType is more suitable when the types are basically the same but some of the fields are different, and GraphQLUnionType when the types are totally different and have totally different fields.

Ultimately whether to use one or the other depending on your schema design.

For a real world example, let's say you have a list of blogs, but blogs using framework A use username and password as authentication, and blog using framework B use email and password. We design it with a GraphQLInterfaceType like this:

const BlogType = new GraphQLInterfaceType({
  name: 'Blog',
  fields: {
    url: { type: new GraphQLNonNull(GraphQLString) }
    password: { type: new GraphQLNonNull(GraphQLString) }
  },
  resolveType: resolveBlogType 
});

const BlogAType = new GraphQLObjectType({
  name: 'BlogA',
  interfaces: [Blog],
  fields: {
    url: { type: new GraphQLNonNull(GraphQLString) }
    username: { type: new GraphQLNonNull(GraphQLString) },
    password: { type: new GraphQLNonNull(GraphQLString) }
  }
});

const BlogBType = new GraphQLObjectType({
  name: 'BlogB',
  interfaces: [Blog],
  fields: {
    url: { type: new GraphQLNonNull(GraphQLString) }
    email: { type: new GraphQLNonNull(GraphQLString) },
    password: { type: new GraphQLNonNull(GraphQLString) }
  }
});

function resolveBlogType(value) {
  return value.username ? BlogAType : BlogBType;
}

When we create a new blog sending username, it will create a BlogA.

We can query like this:

query MyQuery {
   blogs: {
     url
     password
     ... on BlogA {
       email
     }
     ... on BlogB {
       username
     }
   }
}

Now let's get the same functionality but using GraphQLUnionType, because we prefer to use simply one type of blog, and 2 types of authentication methods:

const AuthAType = new GraphQLObjectType({
  name: 'AuthA',
  fields: {
    username: { type: new GraphQLNonNull(GraphQLString) },
    password: { type: new GraphQLNonNull(GraphQLString) }
  }
});

const AuthBType = new GraphQLObjectType({
  name: 'AuthB',
  fields: {
    email: { type: new GraphQLNonNull(GraphQLString) },
    password: { type: new GraphQLNonNull(GraphQLString) }
  }
});

const AuthType = new GraphQLUnionType({
  name: 'Auth',
  types: [AuthAType, AuthBType]
  resolveType: resolveAuthType
});

const BlogType = new GraphQLInterfaceType({
  name: 'Blog',
  fields: {
    url: { type: new GraphQLNonNull(GraphQLString) }
    auth: { type: AuthType }
  },

});

function resolveAuthType(value) {
  return value.username ? AuthAType : AuthBType;
}

We can query like this:

query MyQuery {
   blogs: {
     url
     auth {
       ... on AuthA {
         username
         password
       }
       ... on AuthB {
         email
         password
       }
     }
   }
}

As you can see in this example we achieve the same thing with the interface or the union, but one or the other might be more appropriate depending on your schema design.

For example, let's say you want to add a blog framework C that also use email and password. You would need to include another field to be able to differentiate it from blog framework B in our resolveBlogType function. Let's add the type field. In our Union example, since we only have access to the fields within the Union, you would to add type to the Union. If in the future we wanted to add another Union with same fields for multiple frameworks, we would need to add the type field there as well. Not so nice to have type duplicated multiple times in our schema. It could be a better idea to use a Interface, and have a single type field accessible at the resolveBlogType function by all the Objects using the Interface.

like image 120
Jesús Carrera Avatar answered Oct 07 '22 10:10

Jesús Carrera


The sematic of GraphQLInterfaceType is like most program language's interface . and graphql add some more behaviors for it. like check if the derived class implement all the fields,dynamic resolving to derived instance.
The sematic of GraphQLUnionType is not a Union ,but something like OR.(a little bit like the flowtype's type check?)
A real world example is example in Relay => Relay's Node design .
GraphQLInterfaceType is completely unrelated to GraphQLUnionType.
I think maybe you was confused by this?

interface Node{
  id: string
}

type Dog implements Node{
  id: string
}
type Cat implements Node{
  id: string 
}
union Animal = Dog | Cat

type User{
  node: Node
  animal: Animal
}

If be confused by this, you should get some book of strong type language to read.(like C# or Java or something else. maybe you should have a look at Flow too, this usage Dog|Cat is a type restriction)

like image 28
chen x Avatar answered Oct 07 '22 09:10

chen x