Skip to content

Orphaned Types #568

@twavv

Description

@twavv

I've been opening up several issues on this project lately (because I think it's pretty darn cool!). I'd like to devote this one to talking about orphaned types as it doesn't seem there's a single place where that discussion has been taking place.

In the 0.18 beta, there is a concept of orphan types, that is, types that exist (are declared with decorators) but aren't explicitly referenced in the schema. The reason for this is the desire to support non-global schemas (e.g., we want to support more than two schemas in the same node process).

The main issue with the current state of things (as of the 0.18-beta) is that it doesn't work super well for interfaces (at the very least, the behavior is unintuitive). Suppose you have an interface Animal, a query getAnimal that returns an Animal, and several implementations such as Cat, Dog, etc.. Currently, if Cat and Dog aren't included elsewhere in the schema (e.g., in a query that explicitly returns a Dog instead of an Animal), then Cat and Dog are considered oprhaned types and are not added to the GraphQL schema by default. The end result of this is that there is no way to actually use the Animal type (since all implementations aren't actually defined in the schema).

Proposals

Explicit orphanedTypes argument (status quo)

This is the status quo.

  • Pro: Avoids all potential issues with duplicate schemas
  • Con: Has issues with interfaces (described above)
  • Con: All orphaned types have to be supplied in buildSchema which makes it harder to be modular
  • Con: This is very unintuitive behavior (especially if you're used to the existing type-graphql behavior)

Add an implementedBy field to @InterfaceType decorator

Proposed in #110 (comment).

  • Pro: More modular than first option
  • Con: Likely to have issues with circular dependencies

Auto-register interface implementations

This is my preference. Essentially, internally have some metadata associated with each interface type, and when the interface type is added to the schema, also register all known implementations at that time.

  • Pro: Fully modular
  • Pro: No circular dependencies
  • Pro: More intuitive (at least with respect to current type-graphql usage)
  • Con: It's possible to "forget" to include a class (if it's not explicitly imported). This is the case with 0.17 and also not that big of a deal since if it hasn't been imported, it can't be instantiated anyway (assuming you're using class instances).

Explicit Schema Registration

This is a huge departure from the way things are now, but I thought I'd mention it. In (for example) Python's Flask, you can register routes using decorators:

@app.get("/foo")
def get_foo():
    ...

We could do something similar where we have an explicit schema object and the decorators are instance methods.

@schema.ObjectType()
class ... { /* ... */ }

Alternatively, the schema could be an option to the decorator, and if not provided, use a global default.

@ObjectType({
  // If omitted, this defaults to the "global" schema
  schema: internalSchema
})
class ... { /* ... */ }
  • Pro: Very explicit about what objects are associated with which schemas
  • Con: Huge departure from the way things are today
  • Con: Circular dependency issues (Flask has lots of these)

The third proposal (Auto-register interface implementations) is my favorite I'd be happy to take a stab at this. It's also a minor change from the way things are in the 0.18-beta codebase.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions