Problem Statement
We had identical JSON format for facets in mutation requests and query responses upto v1.1.0. But we didn’t have the ability to fetch facets on scalar list predicates(see this issue).
To support fetching facets on value list predicates, we changed the query response format as it is in versions after v1.1.0. Now response format after fetching facets on all predicate types is uniform.
We brought facets for a predicate outside of predicate in responses for all types(compare facets responses for versions upto v1.1.0 and after v1.1.0 as shown below).
NOTE: In this document, request refers to JSON mutation requests and response refers to JSON query responses.
Request/Response format comparisons upto and after v1.1.0
Scalar predicate
- RDF mutation request:
_:1 <name> "Alice" (since="birth") .
- Equivalent JSON mutation request:
{
"set": {
"uid": "_:1",
"name": "Alice",
"name|since": "birth"
}
}
- Facet query:
{
q(func: has(name)) {
name @facets
}
}
- Response upto v1.1.0:
{
"data": {
"q": [
{
"name|since": "birth",
"name": "Alice"
}
]
}
}
- Response after v1.1.0:
{
"data": {
"q": [
{
"name|since": "birth",
"name": "Alice"
}
]
}
}
Scalar list predicate
- RDF mutation request:
_:1 <nickname> "Joshua" (kind="official") .
_:1 <nickname> "David" .
_:1 <nickname> "Josh" (kind="friends") .
- Equivalent JSON mutation request:
{
"set": [
{
"uid": "_:1",
"nickname": "Joshua",
"nickname|kind": "official"
},
{
"uid": "_:1",
"nickname": "David"
},
{
"uid": "_:1",
"nickname": "Josh",
"nickname|kind": "friends"
}
]
}
- Facet query:
{
q(func: has(nickname)) {
nickname @facets
}
}
- Response upto v1.1.0:
We did not support fetching predicates on scalar list predicates upto v1.1.0. Hence below repsone doesn't have facets.
{
"data": {
"q": [
{
"nickname": [
"David",
"Josh",
"Joshua"
]
}
]
}
}
- Response after v1.1.0:
{
"data": {
"q": [
{
"nickname|kind": {
"1": "friends",
"2": "official"
},
"nickname": [
"David",
"Josh",
"Joshua"
]
}
]
}
}
UID predicate:
- RDF mutation request:
_:1 <name> "San Francisco" .
_:1 <state> _:2 (capital=false) .
_:2 <name> "California" .
- Equivalent JSON mutation request:
{
"set": {
"uid": "_:1",
"name": "San Francisco",
"state": {
"uid": "_:2",
"name": "California",
"state|capital": false
}
}
}
- Facet query:
{
q(func: has(state)) {
name
state @facets {
name
}
}
}
- Response upto v1.1.0:
{
"data": {
"q": [
{
"name": "San Francisco",
"state": {
"name": "California",
"state|capital": false
}
}
]
}
}
- Response after v1.1.0:
{
"data": {
"q": [
{
"name": "San Francisco",
"state": {
"name": "California"
},
"state|capital": false
}
]
}
}
UID list predicate:
- RDF mutation request:
_:1 <name> "Alice" .
_:1 <speaks> _:2 (fluent=true) .
_:1 <speaks> _:3 (fluent=false) .
_:2 <name> "Spanish" .
_:3 <name> "Chinese" .
- Equivalent JSON mutation request:
{
"set": {
"uid": "_:1",
"name": "Alice",
"speaks": [
{
"uid": "_:2",
"name": "Spanish",
"speaks|fluent": true
},
{
"uid": "_:3",
"name": "Chinese",
"speaks|fluent": false
}
]
}
}
- Facet query:
{
q(func: uid(0x1)) {
name
speaks @facets {
name
}
}
}
- Response upto v1.1.0:
{
"data": {
"q": [
{
"name": "Alice",
"speaks": [
{
"name": "Spanish",
"speaks|fluent": true
},
{
"name": "Chinese",
"speaks|fluent": false
}
]
}
]
}
}
- Response after v1.1.0:
{
"data": {
"q": [
{
"name": "Alice",
"speaks": [
{
"name": "Spanish"
},
{
"name": "Chinese"
}
],
"speaks|fluent": {
"0": true,
"1": false
}
}
]
}
}
Issue raised because of above changes
Above changes in response formats has created other issues. Request and response formats are not compatible. This means users have to maintain two Go structs at client side. This has affected our user experience. Currently we have 3 github issues listed to address this. I have tried to quote users here:
Issue: #4798
I realize the facets response format has recently changed, but I don’t understand how I can now unserialize it into my data structures : facets are now independent objects attached to the parent node while my Go facet properties are defined into the child node.
Issue: #4581
The JSON input and output are not permutable
Issue: #4907
Thank you for helping me raising the issue upon.
I don’t really see the issue and reason why we change the facets response to the way it is now. But at a dgraph’s user perspective, I think it would make more sense to have same data struct for creating or querying facets value.
And putting facets value inside the object would give a better understandable view to anyone even who new to dgraph just like me. Despite the fact that how it’s physically stored behind.
From above github issues, our users’ expectations are:
- Have same request/response format for facets.
- Have backward compatibility with previous versions for facets responses(if possible).
Probable solutions
Solution #1 - Have facets requests and responses format as per new response format (after v1.1.0)
Hence request format for all types will look like as follows:
- Scalar predicate
Current request format:
{
"set": {
"uid": "_:1",
"name": "Alice",
"name|since": "birth"
}
}
New request format:
{
"set": {
"uid": "_:1",
"name": "Alice",
"name|since": "birth"
}
}
- Scalar list predicate
Current request format:
{
"set": [
{
"uid": "_:1",
"nickname": "Joshua",
"nickname|kind": "official"
},
{
"uid": "_:1",
"nickname": "David"
},
{
"uid": "_:1",
"nickname": "Josh",
"nickname|kind": "friends"
}
]
}
New request format:
{
"set": {
"uid": "_:1",
"nickname": ["Joshua", "David", "Josh"],
"nickname|kind": {
"0": "official",
"2": "friends"
}
}
}
- UID predicate
Current request format:
{
"set": {
"uid": "_:1",
"name": "San Francisco",
"state": {
"uid": "_:2",
"name": "California",
"state|capital": false
}
}
}
New request format:
{
"set": {
"uid": "_:1",
"name": "San Francisco",
"state": {
"uid": "_:2",
"name": "California",
},
"state|capital": false
}
}
- UID list predicate
Current request format:
{
"set": {
"uid": "_:1",
"name": "Alice",
"speaks": [
{
"uid": "_:2",
"name": "Spanish",
"speaks|fluent": true
},
{
"uid": "_:3",
"name": "Chinese",
"speaks|fluent": false
}
]
}
}
New request format:
{
"set": {
"uid": "_:1",
"name": "Alice",
"speaks": [
{
"uid": "_:2",
"name": "Spanish"
},
{
"uid": "_:3",
"name": "Chinese"
}
],
"speaks|fluent": {
"0": true,
"1": true
}
}
Pros:
- This will have same request and response format.
- This supports fetching facets on all types of predicates.
Cons:
- This changes the request format for facets, hence becomes a breaking change. However we can think about making this as backward compatible by supporting old request format also.
- Response format will not be backward compatible with version before v1.2.0 which is also the current case.
- This is not the most preferred solution. Users want facets to be present inside node.
Solution #2 - Have facets requests and response format as old format (up to v1.1.0)
This is most preferred solution(Manish, Michel and our users are in favour of it).
response format for all types will look like as follows:
- Scalar predicate
Current response format:
{
"data": {
"q": [
{
"name|since": "birth",
"name": "Alice"
}
]
}
}
New response Format:
{
"data": {
"q": [
{
"name|since": "birth",
"name": "Alice"
}
]
}
}
- Scalar list predicate
Current response format:
{
"data": {
"q": [
{
"nickname|kind": {
"1": "friends",
"2": "official"
},
"nickname": [
"David",
"Josh",
"Joshua"
]
}
]
}
}
New response Format:
Need to decide one format here.
- UID predicate
Current response format:
{
"data": {
"q": [
{
"name": "San Francisco",
"state": {
"name": "California"
},
"state|capital": false
}
]
}
}
New response Format:
{
"data": {
"q": [
{
"name": "San Francisco",
"state": {
"name": "California",
"state|capital": false
},
}
]
}
}
- UID list predicate
Current response format:
{
"data": {
"q": [
{
"name": "Alice",
"speaks": [
{
"name": "Spanish"
},
{
"name": "Chinese"
}
],
"speaks|fluent": {
"0": true,
"1": false
}
}
]
}
}
New response Format:
{
"data": {
"q": [
{
"name": "Alice",
"speaks": [
{
"name": "Spanish",
"speaks|fluent": true
},
{
"name": "Chinese",
"speaks|fluent": true
}
]
}
]
}
}
Pros:
- Again same request and response format here.
- Response format will be same for all version expect versions from v1.2.0 to now.
Cons:
- This breaks our current response format. Hence becomes a breaking change.
- This doesn’t have any way as of now to support scalar list response. Any alternate way can be thought here and this can be treated as exceptional case. But again response and request structures won’t be same, which is the whole purpose of this exercise.
Solution #3 - Have facets requests and response format as old format (up to v1.1.0) except for scalar list (Hybrid approach)
We have already seen in solution #2, request and response format are same for all predicates types except for scalar list. Solution #2 doesn’t have any way to represent request/response format for scalar list type, such that both are compatible with each other. Hence we can take some middle way. We can represent request/response format for scalar list type in same format as proposed in solution #1.
Hence there will not be any change in response format for scalar list type.
Current request Format:
{
"set": [
{
"uid": "_:1",
"nickname": "Joshua",
"nickname|kind": "official"
},
{
"uid": "_:1",
"nickname": "David"
},
{
"uid": "_:1",
"nickname": "Josh",
"nickname|kind": "friends"
}
]
}
New request Format:
{
"set": {
"uid": "_:1",
"nickname": ["Joshua", "David", "Josh"],
"nickname|kind": {
"0": "official",
"2": "friends"
}
}
}
Pros:
- Same request and response format.
- Has all properties from solution #2 and fixes issues with solution #2.
Cons:
- Breaking as both request and response format. Breaking request format for only scalar list type but breaking response format for all type except scalar list.
Solution #4 - Leave this in current state, which is having two different format for requests and responses.
Pros:
- This does not changes anything and hence no breaking change.
Cons:
- If we leave request/response in current format, users has two maintain two different Go structs at client side, which is not a good user experience.
Summary
Breaking request format | Breaking response format | Facets inside node | Way to represent Scalar list response | Same request/request format | |
---|---|---|---|---|---|
Solution #1 | Scalar list, UID, UID list | x | x | ✓ | ✓ |
Solution #2 | x | Scalar list, UID, UID list | ✓ | x | ✓ |
Solution #3 | Scalar list | UID, UID list | ✓ | ✓ | ✓ |
Solution #4 | x | x | x | ✓ | x |