Something worth pointing out is that the GraphQL and Go type systems have some pretty glaring differences, and, at least for me, it can be hard to know which mental model to reason with when working with Go code generated from a GraphQL schema. For instance, GraphQL requires explicitly stating that a type implements an interface in order for it to be used as that interface type.
Here's a list of type system differences:
- Unions exist in GraphQL, but not Go, so they are somewhat mocked through Go interfaces
- The
Is{UnionName}()
signpost functions leverage the Go compiler to help alleviate some of the confusion here - Without the signposts, a programmer implementing a query resolver would need likely need to jump to the GraphQL schema to figure out what types are included in the union
- The
- Interfaces in GraphQL differ from Go interfaces in two key ways
- A GraphQL interface definition includes only fields, where Go interfaces contain methods
- Getter functions are generated to preserve the fields defined in the GraphQL interface
- GraphQL types must explicitly declare that they're implementing an interface to be used as that interface type, where Go does this implicitly, i.e. if a type has the same methods that an interface defines, it can be used as that interface type
- The signpost functions prevent Go types from inadvertently implementing GraphQL interfaces
- As an example, imagine two different GraphQL interfaces that only contain
ID: ID!
- As an example, imagine two different GraphQL interfaces that only contain
- The signpost functions prevent Go types from inadvertently implementing GraphQL interfaces
- A GraphQL interface definition includes only fields, where Go interfaces contain methods
- GraphQL makes you use special
input
types if you need structured input to a query or mutationinput
types can't be used as returns, solely as inputs- Go's type system has no analog, and no great way to enforce this
- GraphQL types have the notion of "required" fields, e.g.
Int!
, where Go only has pointers- For primitives, it is somewhat convenient to just use the type directly for required fields, and pointers for optional fields
- For structures, this becomes a hard decision, since using a structure type directly in Go will force copies to be created
- The compromise here is that structure fields are always generated as pointer in Go, and, if they're marked as required in the GraphQL schema, the resolver has to enforce that the field in the Go structure is non-nil at runtime
That's what I have off the top of my head. Any other footguns you have encountered?