Neo4j-graphql-js: Lack of support for polymorphism

Created on 29 Jan 2020  ·  3Comments  ·  Source: neo4j-graphql/neo4j-graphql-js

Object Polymorphism

Imagine I have a large existing Neo4j graph with the following node 'types' (aka 'labels'):

# typeDefs.ts

type Query {
  SportsCar(uuid: ID): [SportsCar]
  FamilyCar(uuid: ID): [FamilyCar]
  CompactCar(uuid: ID): [CompactCar]
}

type SportsCar { ... }
type FamilyCar { ... }
type CompactCar { ... }

Using makeAugmentedSchema, I can quickly make GraphQL endpoints to lookup each of these node 'types'.

But really I need my GraphQL API user to be able to lookup any type of car by a uuid. If I understand correctly, I now need to add a Car label to all existing nodes in my Neo4J graph. So in effect SportCars etc become a sub class of Car. I would do this with:

MATCH (n)
SET n:Entity
RETURN n

then I can do:

# typeDefs.ts

type Query {
  Car(uuid: ID): [Car]
  SportsCar(uuid: ID): [SportsCar]
  FamilyCar(uuid: ID): [FamilyCar]
  CompactCar(uuid: ID): [CompactCar]
}

interface Car = { ... }
type SportsCar implements Car { ... }
type FamilyCar implements Car { ... }
type CompactCar implements Car { ... }

There's lots of boiler plate inside of my types now, but this is essentially fine. There is some discussion about using unions here I think? To avoid having to add the Car 'label'? It's not perfect but things seem to work...

The API user can do e.g.:

query {
  oneCar: Car(uuid: "22877232-ca7c-4ed9-b5e5-edfa2a24d55a"){
    uuid
    name
    ...on SportsCar {
      horsepower
    }
  }
  compactCars: Car(filter: {type: "CompactCar"}, limit: 100){
    uuid
    name
    ...on CompactCar {
      turningCircle
    }
  }
}

Object Polymorphism in Relationships

The problem really hits when you start to work with relations. Imagine I only have one type of relation, but it can occur between any two nodes of type Car. Super tenuous example (I can't think of anything better 🤦‍♂️), but the relation could be:

{
  "name": "COMPONENT_INSPIRED_BY",
  "component": "engine"
}

So now I add a type:

type ComponentInspiredBy @relation(name: "COMPONENT_INSPIRED_BY") {
  from: Car
  to: Car
  component: String
}

But this doesn't really work right, because in order to do this I'd need to add:

interface Car {
  componentsInspiredBy: [ComponentInspiredBy]
}

Now it's in the interface I need to implement it in each of the sub types - but that isn't possible because I need to specialise the type ComponentInspiredBy.

I've tried lots of different variants of how to model this, but all of them have problems. I can add more details on those variants if needed.

What I really really want to avoid is hitting the n² problem, where on each type I have to do e.g.:

type SportsCar {
   componentsInspiredBySportsCars: [SportsCarComponentInspiredBySportsCar]
   componentsInspiredByFamilyCars: [SportsCarComponentInspiredByFamilyCar]
   componentsInspiredByCompactCars: [SportsCarComponentInspiredByCompactCar]
}

🙀

Does anyone have any thoughts on how to handle this? Seems like this is pretty fundamental to exposing the power of Graph databases and Cypher?

Most helpful comment

v2.13.0 adds improved handling of interface and union types based on multiple labels in Neo4j, please see the docs here for more info: https://grandstack.io/docs/graphql-interface-union-types.html

All 3 comments

This seems like an issue with how you're modelling the data more than anything. You're building the model from the most specific properties of a given possible entity and pulling them up into a generalized type with the car interface. I would suggest that you go the other direction. A car type, with entities representing the relationships. Something like...

// This is the entity responsible for maintaining unique relationships
type CarType @relation(name:"CAR_TYPE", direction: "OUT") {
    someProperties: String
}

type Car {
    uuid: ID!
    brand: String
    name: String
    type: CarType!
    inspiredBy: [CarType]
}

type SportsCar @relation(name:"CAR_TYPE", direction: "IN"){
    topSpeed: Int
}

type EvCar @relation(name:"CAR_TYPE", direction: "IN"){
    mpg: Int
}

type Query {
    Car(uuid: String): Car
}

I'm away from my setup atm, so this is a general idea and is untested. I might get some time tonight to play with it and see how it goes. If you have any feedback or if you found something that does work, I'd love to hear it.

v2.13.0 adds improved handling of interface and union types based on multiple labels in Neo4j, please see the docs here for more info: https://grandstack.io/docs/graphql-interface-union-types.html

@johnymontana thanks for posting, will take a look

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mobsean picture mobsean  ·  4Comments

emregency picture emregency  ·  5Comments

purplemana picture purplemana  ·  3Comments

NathanPimlott picture NathanPimlott  ·  4Comments

albert-the-creator picture albert-the-creator  ·  3Comments