The more I try to exploit the GraphQL native support of Dgraph, the more it seems to be difficult to combine GraphQL± and GraphQL.
Sometimes it feels like Dgraph’s GrapQL is capable to properly handle any kind of external source but the underlying Dgraph itself.
Don’t get me wrong, I’m really happy about the Database and it’s potential, but I cannot wrap my head around the trouble one has to face and workaround just trying to speak to Dgraph thought it’s own GraphQL interface.
Context: Dgraph generates its own query and mutation set starting from the schema. That’s quite a big help for the scaffold of an application but many times it’s just not expressive enough (you might want to have a more specific filter? custom query? custom mutation?).
Today I spent quite a few time playing around trying to embed some GraphQL± logic (which is waaaaay more expressive) in the GraphQL schema also following suggestions and indications in the this thread:
Schema:
type Name {
name: String! @id @search(by: [hash])
}
type Person {
name: Name!
surname: Name!
}
type Test @remote {
data: Q
}
type Q @remote {
q: [P]
}
type P @remote {
Per: [Person]
}
type Query{
peopleByName(name: String!, query: String!): Test @custom(http: {
url: "http://localhost:8080/query"
forwardHeaders:["Content-Type"]
method: "POST"
body: "{ query: $query, variables: {$name: $name }}"
})
}
The goal was pretty simple: Being able to create a custom query peopleByName
which would exploit the reverse directive to get all person related to a specific name. I know I could’ve created an hasReverse field in the Name type to get the full inverse relation, but right know I’m just trying to reduce the complexity at the minimum while still trying to integrate GraphQL± in GraphQL. I’ll also close my eyes aboutthe fact that it’s not possible to make a GraphQL± query in custom Field resolver at the moment.
After the push of the Schema I set the Person.name relation as inverse to being able to traverse it starting from the name to the person
<Person.name >: uid @reverse .
The proceeded to populate some data in the Database thought GraphQL mutations
mutation {
addPerson(input: [{name: {name: "Mario"}, surname:{name:"Rossi"}},
{name: {name: "Mario"}, surname:{name:"Verdi"}},
{name: {name: "Carla"}, surname:{name:"Rossi"}}]) {
person {
name {
name
}
surname {
name
}
}
}
}
and finally got to the point of testing some query.
What I found are quite odd behaviour:
query {
peopleByName(name:"Mario", query:"query a($name: string): { q(func:eq(Name.name, $name)) { Per: ~Person.name { expand(_all_) } }}") {
__typename
data {
__typename
q {
__typename
Per {
__typename
name {
name
}
}
}
}
}
}
Running the query above I thought that once dgraph returned a Person
node to the GraphQL layer, it would’ve used it’s internal resolvers to continue the resolution of the inner fields (I’m still not an expert of GraphQL, but it was fair logic to me that once you have a Person
node, GraphQL knew how to resolve the name
field) but the response was not as expected:
{
"data": {
"peopleByName": {
"__typename": "Test",
"data": {
"__typename": "Q",
"q": []
}
}
},
"extensions": {
"tracing": {
"version": 1,
"startTime": "2020-07-22T11:46:32.424152949Z",
"endTime": "2020-07-22T11:46:32.429471649Z",
"duration": 5318800
}
}
}
I then tried to expend a level deeper, to give GraphQL all the information about the person and about the inner Name
nodes:
query {
peopleByName(name:"Mario", query:"query a($name: string): { q(func:eq(Name.name, $name)) { Per: ~Person.name { expand(_all_) {expand(_all_)} } }}") {
__typename
data {
__typename
q {
__typename
Per {
__typename
name {
name
}
}
}
}
}
}
Response:
{
"errors": [
{
"message": "Non-nullable field 'name' (type Name!) was not present in result from Dgraph. GraphQL error propagation triggered.",
"locations": [
{
"line": 49,
"column": 11
}
],
"path": [
"peopleByName",
"data",
"q",
0,
"Per",
0,
"name"
]
},
{
"message": "Non-nullable field 'name' (type Name!) was not present in result from Dgraph. GraphQL error propagation triggered.",
"locations": [
{
"line": 49,
"column": 11
}
],
"path": [
"peopleByName",
"data",
"q",
0,
"Per",
1,
"name"
]
}
],
"data": {
"peopleByName": {
"__typename": "Test",
"data": {
"__typename": "Q",
"q": [
{
"__typename": "P",
"Per": [
null,
null
]
}
]
}
}
},
"extensions": {
"tracing": {
"version": 1,
"startTime": "2020-07-22T11:51:34.651053286Z",
"endTime": "2020-07-22T11:51:34.657374286Z",
"duration": 6321000
}
}
}
Better but not good, Graph returns the data with its internal schema, and GraphQL is not able to map back the fields with its own types.
Finally running a very long and stupid query where i manually maped back the fields, I managed to get the data:
query {
peopleByName(name:"Mario", query:"query a($name: string): { q(func:eq(Name.name, $name)) { Per: ~Person.name { name: Person.name{name:Name.name} surname: Person.surname{name:Name.name} } }}") {
__typename
data {
__typename
q {
__typename
Per {
__typename
name {
name
}
}
}
}
}
}
{
"data": {
"personsByName": {
"__typename": "Test",
"data": {
"__typename": "Q",
"q": [
{
"__typename": "P",
"Per": [
{
"__typename": "Person",
"name": {
"name": "Mario"
}
},
{
"__typename": "Person",
"name": {
"name": "Mario"
}
}
]
}
]
}
}
},
"extensions": {
"tracing": {
"version": 1,
"startTime": "2020-07-22T11:55:46.318815768Z",
"endTime": "2020-07-22T11:55:46.324802667Z",
"duration": 5986899
}
}
}
I know that there some points the team is planning to cover in the current roadmap to improve the integration between GraphQL± and GraphQL, but I think that being able to use the native expressiveness of GraphQL± in the GraphQL schema should be a major priority.
For this particular example, the problem could be workarounded transforming of results of the Dgraph query trimming the {namespace}. part of the field’s name (which is added during the schema generation). A more complete solution, if possible, should be to delegate entirely to GraphQL logic the resolution of fields once Dgraph returns a “known” type (a not @remote type)