Skip to content

Scheme & Proposal Base Interfaces #505

@dOrgJelli

Description

@dOrgJelli

Current Behavior

Currently, the subgraph schema's solution to adding new types of scheme & proposal types requiring changing the base type (ControllerScheme & Proposal). This is because each derived type is added as a nullable field in the base type like so:

type Base {
  id: ID!
  baseProp: String!
  typeName: String!

  # Implementations
  derivedA: DerivedA
  derivedB: DerivedB
}

In order to query all Base entities, you'd have to form a query like so:

bases {
  id
  baseProp
  typeName
  derivedA {
    # All derivedA props
    someFieldA
  }
  derivedB {
    # All derivedB props
    someFieldB
  }
}

And in order to know what sub type each entity in the collection is (derivedA or derivedB) you have to check the typeName, then access the correct field that corresponds to that entity:

if (res.typeName === "DerivedA") {
  res.derivedA.someFieldA
} else if (res.typeName === "DerivedB") {
  res.derivedB.someFieldB
}

Opinion

Doing things this way is a "non-standard" way of implementing polymorphism in GraphQL. Here are two ways in which GraphQL supports polymorphic types: https://www.apollographql.com/docs/apollo-server/schema/unions-interfaces/

Unions make sense for when types don't have shared properties. Interfaces make sense for when types have shared "base" properties.

Proposal

Utilize GraphQL interfaces for both Proposal and Scheme types. Here's an example of what this would look like:

interface Base {
  id: ID!
  baseProp: String!
}

type DerivedA implements Base {
  id: ID!
  baseProp: String!
  someFieldA: String!
}

type DerivedB implements Base {
  id: ID!
  baseProp: String!
  someFieldB: String!
}
// Generate an array of fragments for each known type
const fragments = []
for (const derived of DerivedTypes) {
  fragments.push(derived.Fields)
}
query Bases {
  bases {
    __typename
    id
    baseProp

    # Can be generated from a list of known types
    # and appended to the query string, making this code
    # open for extension, closed for modification
    ... on DerivedA {
      DerivedAProps
    }
    ... on DerivedB {
      DerivedBProps
    }
  }
}
${fragments}

Then we can instantiate the different types from __typename:

new DerivedTypes[res.__typename]()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions