graphql
graphql copied to clipboard
Unions / Interfaces at top level
As mentioned, unions no longer work at the top level, and interfaces no longer work at the top level or on relationships.
This is a major issue for me, and I hope that we can find a way to maintain this functionality in the new library. I'd like to highlight several use cases that are relevant to me:
1. Unions for Search
Search is the canonical example for unions, and it's not clear how we can replicate this behavior without them.
union SearchResult =
Album
| Song
| Artist
| UserPlaylist
| FeaturedPlaylist
| Podcast
type Query {
Search(string: String!): [SearchResult!]!
@cypher(
statement: """
CALL db.index.fulltext.queryNodes('names', $string+'~') YIELD node, score
RETURN node
"""
)
}
2. Interfaces on Relationships for Activity Feed
Assuming the below schema excerpt, we may want to query for all of a user's activity, ordered by createdAt
time
interface Activity {
id: ID!
createdAt: _Neo4jDateTime!
user: User! @relation(name: "BY_USER", direction: "OUT")
}
type AudioListen implements Activity {
id: ID!
createdAt: _Neo4jDateTime!
user: User! @relation(name: "BY_USER", direction: "OUT")
audio: Audio! @relation(name: "AUDIO_LISTENED", direction: "OUT")
}
type VideoView implements Activity {
id: ID!
createdAt: _Neo4jDateTime!
user: User! @relation(name: "BY_USER", direction: "OUT")
video: Video! @relation(name: "VIDEO_VIEWED", direction: "OUT")
}
type User {
id: ID!
name: String
createdAt: _Neo4jDateTime
activity: [Activity!]!
@cypher(
statement: """
// get all activity, sorted by createdAt field
"""
)
}
3. Top-Level Interface to Query Across Related Types
Assuming the below schema excerpt, we may wish to query across all Audio
types.
interface Audio {
id: ID!
name: String!
url: String!
listens: [AudioListen!]! @relation(name: "AUDIO_LISTENED", direction: "IN")
}
type Song implements Audio {
id: ID!
name: String!
url: String!
artists: [Artist!]! @relation(name: "BY_ARTIST", direction: "OUT")
albums: [Album!]! @relation(name: "HAS_SONG", direction: "IN")
listens: [AudioListen!]! @relation(name: "AUDIO_LISTENED", direction: "IN")
}
type Podcast implements Audio {
id: ID!
name: String!
url: String!
host: User!
listens: [AudioListen!]! @relation(name: "AUDIO_LISTENED", direction: "IN")
}
Each of these works fine with the existing neo4j-graphql-js
package. I would advocate strongly for ensuring that these and similar use cases can be easily implemented with the new package.
Hello. Thank you for sharing your usage here. We realize that not supporting unions and interfaces the same way is causing some issues & will get back to you with a solution.
@danstarns Just checking in on this.
Is there any update on how the new package will handle these features?
Hey @smkhalsa ! The state of this had not changed and we still don't have a solution. Apologies for the time this is taking.
@danstarns @smkhalsa
Is it possible to use additional labels on nodes to facilitate this approach? A proposal could be to use a directive on an interface declaration as in:
interface Activity @label {
id: ID!
createdAt: DateTime!
user: User! @relationship(type: "BY_USER", direction: OUT)
}
...
Whenever creating a node, any implementing interfaces that have the @label
directive attached to it are affixed with an additional label corresponding to the name of the interface. In @smkhalsa's example every :VideoView
node would become a :VideoView:Activity
node. In this way the additional labels are coupled to the GraphQL schema with no naming conflicts if valid.
Any interface using the @relationship
directive must have the @label
directive on its declaration. When returning the activity on the User
Object
...
type User {
id: ID!
name: String!
createdAt: DateTime!
activity: [Activity!]! @relationship(type: "BY_USER", direction: IN)
}
it could generate a cypher query of (node:Activity)-[:BY_USER]->(this:User)
. Each returned node should have a list of labels that includes a label that corresponds to an Object type which will be what the node will resolve to, e.g. a returned node with labels ["VideoView", "Activity", ...]
would resolve to a VideoView
GraphQL Object. create
-ing Activity
through the createUsers
and updateUsers
would have to be disabled, unless a way of creating specific Activity
i.e. VideoView
or AudioListen
through the api can be found.
This could be applied to top-level interface querying. Again citing @smkhalsa's example:
interface Audio @label {
id: ID!
name: String!
url: String!
listens: [AudioListen!]! @relationship(type: "AUDIO_LISTENED", direction: IN)
}
type Song implements Audio {
id: ID!
name: String!
url: String!
artists: [Artist!]! @relationship(type: "BY_ARTIST", direction: OUT)
albums: [Album!]! @relationship(type: "HAS_SONG", direction: IN)
listens: [AudioListen!]! @relationship(type: "AUDIO_LISTENED", direction: IN)
}
type Podcast implements Audio {
id: ID!
name: String!
url: String!
host: User!
listens: [AudioListen!]! @relationship(type: "AUDIO_LISTENED", direction: IN)
}
Top-level queries with the exception of createAudio
could be generated for the Audio
interface since it has the @label
directive. (createAudios
would be impossible to implement since every node must have a label corresponding to some GraphQL Object type and ostensibly createSongs
and createPodcasts
exists). In this way query { audio(where: {}) { ... } }
could return nodes with :Podcast:Audio
or :Song:Audio
labels that can be resolved to the appropriate Object types.
Without understanding the approach it is difficult to know if this is a useful implementation or not. I, and I think others, would be happy to hear what the current thinking on this topic is. I really appreciate all of the work that is going into this project. Thanks again.
Hi @dmoree . Thank you for taking the time to write this up! additionalLabels
is something we have not yet ported over from neo4j-graphql-js
but of course, something we need to look into more.
Our current thinking in regards to Unions and Interfaces is; Firstly, we found problems with the way they were treated... that being, things broke when users plugged the library into an existing dataset, and secondly, multiple labels is a more advanced use case and lead to an harder to reason about implementation, and then we have the Relay RFC here neo4j/graphql-tracker-temp#7 where we want to leave interfaces and unions alone till we come to a final and implemented solution for Relay, and finally, we have to consider how unions and interfaces would work with are newer features such as @auth
, The OGM and Nested Mutations.
As it stands, on fields, you can use Unions and I will say; the given examples from both @smkhalsa & @dmoree for sure highlights the need for top-level union queries.
Once again thank you both for expressing your concern and we do realise the missing functionally is making it difficult to implement particular use cases. My best suggestion for you, for the time being, would be to use the @cypher
directive.
@danstarns Any update on this? I see that v1.0 is now live, and this is one of the few issues preventing me from migrating from the old lib.
Hi @smkhalsa. Still no change, sorry to hear it's blocking your migration.
@danstarns is this actively being worked on? Can you give an estimated timeline?
@smkhalsa We are not actively working on this, it's being put aside until we come to a solution for relationship properties and relay, as we think there is going to be overlap with interfaces.
@danstarns you stated "As it stands, on fields, you can use Unions" -- can you please point to a working example of such a union on a field? Then at least there is a way to work around this issue.
@danstarns you stated "As it stands, on fields, you can use Unions" -- can you please point to a working example of such a union on a field? Then at least there is a way to work around this issue.
Hi. Specifically, you can use Unions on a @relationship
field. There is an example here https://neo4j.com/docs/graphql-manual/current/type-definitions/unions-and-interfaces/. Sorry, there is not a solution and we realise the need for the requested. Once relationship properties are in we can dive deeper into this.
Hi, I have a use case where it’s practical to group classes/nodes into a super-class or even within a hierarchical tree. However, I have seen that multiple labels are not recommended for such relationships and even « is-a » relationship with caution (https://community.neo4j.com/t/graph-modeling-labels/27690 ). The author prefers unions (https://neo4j.com/docs/graphql-manual/current/type-definitions/unions-and-interfaces/ ), which is also advised by Danstarns in the current thread. But you don’t always remember all members of a union. According to what I see in this last post, there was no attribute inheritance in the former « Interface » anyway. Danstarns also suggests to use the @cypher directive. Could you please point to an example ? Besides, in a webinar dedicated to data science this afternoon, I saw a « part-of-group » relationship. Could it better fit than the former options ? More over, there are classification algorithms in GDS. How does Neo4J handle the resulting classes ?
Sorry, there is not a solution and we realise the need for the requested. Once relationship properties are in we can dive deeper into this.
@danstarns Now that relationship properties have shipped (awesome work!) in v2, can you provide any update on using interfaces at the top level / in relationships?
Hey @smkhalsa, we're about to shape up the work for some or all of:
- Top-level unions
- Top-level interfaces
- Relationship interface fields
Unlikely we'll start work on all of these straight away, so we're going to have to make a judgement call on what's going to be most valuable at this stage. 🙂
@darrellwarde that's great news 🎉. Thank you!
My vote would be to start with relationship interface fields.
@darrellwarde If you’re looking for community feedback on this I would vote along with @smkhalsa for relationship interface fields
100% keen for community feedback on this - I encourage all to voice their opinion!
@darrellwarde any updates on the status of top-level unions/interfaces? They feel necessary for returning search results that span multiple node types. I'm eager to migrate away from neo4j-graphql-js, but I don't think I'll be able to until they're supported.
I also wonder if it wouldn’t be more optimal to type the observables (Rxjs) with interfaces and to sort or map the implementation classes/types in parallel or a posteriori rather than issuing as many queries as classes/types corresponding to components (Angular) ?
@darrellwarde any updates on the status of top-level unions/interfaces? They feel necessary for returning search results that span multiple node types. I'm eager to migrate away from neo4j-graphql-js, but I don't think I'll be able to until they're supported.
We're focussing on interface relationship fields first (#525) as this was what was called for by the community. Top-level interface and union queries will follow hopefully not too far after!
I am halfway into my migration to find this bug, its a deal-breaker for us
I am halfway into my migration to find this bug, its a deal-breaker for us
Top-level unions and interfaces won't be trailing too far behind field-level interfaces, which will be released soon.
Field level interfaces have been released in 2.4.0 and are available on npm. Top-level is something we are still planning and shaping.
Hey folks! I was wondering if there's been any update on top-level interfaces? We've got a few use cases that I can't figure out good workarounds to, and it would be super helpful if we had autogenerated queries and mutations for them. For example, given this schema:
interface Content {
id: ID!
name: String @ignore
}
type Image implements Content {
id: ID!
name: String @ignore(dependsOn: ["altText"])
url: String
altText: String
}
type Post implements Content {
id: ID!
name: String @ignore(dependsOn: ["title"])
text: String
title: String
}
Currently, there are no queries or mutations that are generated for them, so we'd have to query or mutate Image
or Post
records separately. This requires quite a bit of application code, especially once you have more than 3 or 4 different types of Content
. I looked into writing my own queries using @cypher
or using OGM
directly, and both have downsides.
-
@cypher
- like others have mentioned, these are hardcoded queries, so we wouldn't be able to handle different selection sets in different requests. We could try and fetch everything, but our data model has many-to-manyContent
relations that might have arbitrary depth, and I'm not sure how to handle that with this approach (plus, even if it's possible, that's a whole lot of data on every request, especially if the actual selection set ends up being small) -
OGM
- we can't callogm.model()
on interfaces (which makes sense, I think), but that means doing a query against every possibleContent
type. Even so, you can pass a request's selection set into an OGM model's.find()
method, until you try to fetch a field with@ignore(dependsOn)
or@cypher
in the schema, which throws an error (I think because OGM doesn't know about all of the stuff@neo4j/graphql
knows about)
@nelsonpecora I saw your comment and looked at it again. I didn't look at it for too long, but the initial work is here: https://github.com/neo4j/graphql/compare/dev...dmoree:feature/top-level-union-interface
Those are minimal changes to get queries working where full text in unions can be used on those that have them, but quickly you'll realize that interfaces are treated differently. They are only relevant if they are used in a @relationship
. Maybe I'm missing something, but you can take a look.
Also, how would you envision mutations working? Queries I think can be readily done if things were reworked, interfaces in particular.
Any updates or workarounds for top-level interface queries?
Any updates for top-level interface queries? I see multiple branches industriously developed by @a-alle and some PRs merged, such as #4061. Would it be released soon? 🥰🥰
As you've seen, feature development is well underway! They are currently hidden under an experimental
flag that we've introduced, so not ready for production use yet, but you can play around with them if you like! Feedback is appreciated.