Introducing AppSync to your team has its challenges. But it's doubly so if the team is unfamiliar with GraphQL (GQL). As the AppSync "expert," you will be expected to explain not only AppSync's APPSYNC_JS resolvers, merged APIs, and how to test changes, but also GQL types, schemas, and queries.
In my experience, most teams pick up GQL basics quickly, but struggle with Union and Interface types. When should they choose one over the other? What makes them different? Does the team need them at all?
None of the above
Let’s tackle the last question first. Do we need to use an interface or union at all? In short, no, you don’t. But interfaces and unions add something to your schema that is otherwise difficult to express. Let’s look at an example.
Imagine an error type that informs the user that his input is invalid. In its basic form, it has the following properties:
In GQL, that can easily become a type as follows:
Specific types of this error may extend this basic set of properties to provide relevant details. Let's look at two: a "missing properties" error and an "improper age" error. They both share the properties code and message, so we can add optional properties like so:
The caller can check the error type and look for a new property to get more details about the response. But what can we not express with this schema?
Other than through simple (English) semantics, the schema doesn’t tell you that the properties minimumAge and missingProperties are mutually exclusive; you’ll get one error type or the other, not both. Other schema tools, such as OpenAPI, include constructs like oneOf to address this. GQL has to use other approaches to make this exclusivity explicit: interface and union types.
The GQL Interface
The idea of an interface is well known to most programmers. In Object-Oriented languages, an interface is a data type that acts as an abstraction of a class. It defines methods and properties that an implementing class must support.
A GQL interface is even simpler. It defines a set of fields that any implementing type must expose. It only defines properties (no methods). Let’s look at an example that builds on the error response from earlier.
We recognized that we want some common error properties, but we also want to display additional information relevant to the type of error returned. We can use a GQL interface to represent the common properties and types that implement the interface for the specific, like so:
Now we could define a mutation with the following signature:
And execute the query with the following projection:
Here is where GQL interfaces start to diverge from OO interfaces. With an OO interface, you cannot (or rather, you ought not) tell what the implementing type is. In GQL, the implementing type is always returned as a special, undeclared property called __typename. This lets us add inline fragments to pull in the additional properties. For example, we could expand our saveInput projection like so:
When you get a result back from this query, you will have your base properties alongside either of the named error properties. The fragments indicate the either-or nature of minimumAge and missingProperties.
Can’t we do the same thing with a GQL union type? Yes, with some caveats. Let’s look at that implementation.
The GQL Union
A GQL union looks similar to a GQL interface, but it serves a different purpose. An interface defines what is common. A union defines separate, and (hopefully) disparate, types.
With one notable exception (detailed below), I consider it poor API design for your endpoint to return dissimilar types. Imagine a REST API that could return Shoes, Ships, or Sealing Wax. How useful is that? Not very. Normally, I would want separate GET routes for /shoes, /ships, and /sealing-wax. That’s a better design!
The big exception to this rule is full-text search. It often returns anything. If you are building a GQL API that touches search, you will be hard-pressed to avoid using a union type. Given that, let’s look at what would happen if we implemented our existing error example with unions.
We could express our errors using unions if we make three distinct types and one union from them:
Now we can invoke our mutation with almost the same projection. I say "almost" because we can no longer ask for the "common" properties outside of a fragment. This is because we are no longer defining anything in common; each type is as separate as a shoe or a ship. Here is the new projection:
We have maintained the mutual exclusivity, but we have lost the commonality. This is key to your decision to use a union versus a type: is there something in common? If so, use interfaces; otherwise, use a union.
GQL union/interface code smells
Kent Beck defines a code smell as “a hint that something has gone wrong somewhere in your code.” Here are a few GQL smells to watch out for when dealing with interfaces and unions.
The suppressed interface
If your type has optional properties that are mutually exclusive. This is a good candidate for an interface. An example could be the overloaded Error type we saw earlier:
The anemic interface
Just because you share some properties between types doesn’t mean you have a candidate for an interface. Interfaces require query fragments and add cognitive load. Don’t use them if you don’t need them. An example could be types that all have an id property. Adding an identifiable interface, with one property (id), buys you little while adding complexity.
Interfaces on full-text search results
In most cases, you should treat interfaces on your full-text search results with suspicion. This is where unions typically shine. Remember our example with shoes, ships, and sealing wax? Use a union here.
Unions on non-full-text search results
The corollary to the above is also true: any non-full-text search endpoint that has a union is suspect. You are probably looking for an interface here. However, if your source API has already blown it and has one badly designed endpoint for fetching shoes, ships, or sealing wax, you may be stuck.
Summary
Understanding the differences (and similarities) between GQL unions and interfaces is valuable when introducing AppSync to a team. They will look to you for answers and advice. Having examples and a few rules of thumb to guide decision-making can go a long way toward helping your team succeed.
Happy building!
References
AWS AppSync GraphQL Developer Guide: Interfaces and unions in GraphQL
GraphQL.org: Schemas and Types










Top comments (0)