We have two subgraphs: ‘resources’ and ‘learning’
‘learning’ defines the following interfaces.
- “UserQuestion” interface
- “Question” interface, which implements “UserQuestion”
- “ShortQuestion”, “MultiQuestion” types, which implement both the “UserQuestion” and “Question”
interface UserQuestion @key(fields: "id", resolvable:false) @signedIn {
id: ID! @public
quillDocument: [QuillDelta!]!
figure: String
}
interface Question implements UserQuestion @key(fields: "id") @signedIn {
id: ID! @public
content: String! @public
figure: String @public
quillDocument: [QuillDelta!]!
}
type ShortQuestion implements Question & UserQuestion @key(fields: "id") {
id: ID! @public
content: String! @public
figure: String @public
quillDocument: [QuillDelta!]!
answerFormat: String!
}
type MultiQuestion implements Question & UserQuestion @key(fields: "id") {
id: ID! @public
content: String! @public
figure: String @public
quillDocument: [QuillDelta!]!
}
‘resources’ extends the interface above in the following ways
- It extends the “UserQuestion” interface, setting resolvable to false.
- It declares the “Question” as
interfaceObject
implementing UserQuestion - It implements a “LessonPlanUserQuestion”, a type that implements “UserQuestion”.
- Since “Question” is declared as
interfaceObject
, “Short/Multi Question” types aren’t reimplemented in this subgraph (I’m guessing this is triggering the issue)
extend interface UserQuestion @key(fields: "id", resolvable: false) @signedIn {
id: ID! @public
quillDocument: [QuillDelta!]!
figure: String
}
type Question implements UserQuestion @key(fields: "id") @interfaceObject {
id: ID!
lessonPlanDifficulty: QuestionDifficulty
quillDocument: [QuillDelta!]! @external
figure: String @external
}
type LessonPlanUserQuestion implements UserQuestion @key(fields: "id") {
id: ID! @public
quillDocument: [QuillDelta!]!
figure: String
}
The Problem:
- The query plan takes the stub “Question” from resources and tries to resolve it in learning (which is correct).
- It, however, does not seem to recognise that “learning” has “Short/Multi Question” type implementations and constructs a query plan to only resolve up to Question.
Query
query GetWorksheetPlanFedLite($worksheetPlanId: ID!) {
worksheetPlan(id: $worksheetPlanId) {
...WorksheetPlan
}
}
fragment WorksheetPlan on WorksheetPlan {
lessons {
sections {
nodes {
... on LessonPlanStudentQuestionsNode {
skillUserQuestions: questions {
userQuestion {
... on Question {
__typename
... on MultiQuestion {
id
content
__typename
}
}
}
}
}
}
}
}
}
Query Plan
QueryPlan {
Sequence {
Fetch(service: "resources") {
{
worksheetPlan(id: $worksheetPlanId) {
lessons {
sections {
__typename
nodes {
__typename
... on LessonPlanStudentQuestionsNode {
skillUserQuestions: questions2 {
userQuestion {
__typename
... on Question {
__typename
id
}
}
}
}
}
}
}
}
}
},
Flatten(path: "worksheetPlan.lessons.@.sections.@.nodes.@.skillUserQuestions.@.userQuestion") {
Fetch(service: "learning") {
{
... on Question {
__typename
id
}
} =>
{
... on Question {
__typename
}
}
},
},
},
}
- As you can see in the ‘learning’ Fetch section, there is no type-specific call planned.
Result
{
"data": null,
"extensions": {
"valueCompletion": [
{
"message": "Cannot return null for non-nullable field MultiQuestion.content",
"path": [
"worksheetPlan",
"lessons",
0,
"sections",
2,
"nodes",
0,
"skillUserQuestions",
0,
"userQuestion"
]
},
{
"message": "Cannot return null for non-nullable field LessonPlanSkillQuestionNode.userQuestion",
"path": [
"worksheetPlan",
"lessons",
0,
"sections",
2,
"nodes",
0,
"skillUserQuestions",
0,
"userQuestion"
]
},
{
"message": "Cannot return null for non-nullable array element of type LessonPlanSkillQuestionNode at index 0",
"path": [
"worksheetPlan",
"lessons",
0,
"sections",
2,
"nodes",
0,
"skillUserQuestions",
0
]
},
{
"message": "Cannot return null for non-nullable field LessonPlanStudentQuestionsNode.skillUserQuestions",
"path": [
"worksheetPlan",
"lessons",
0,
"sections",
2,
"nodes",
0,
"skillUserQuestions"
]
},
{
"message": "Cannot return null for non-nullable array element of type LessonPlanNode at index 0",
"path": [
"worksheetPlan",
"lessons",
0,
"sections",
2,
"nodes",
0
]
},
{
"message": "Cannot return null for non-nullable field SkillSection.nodes",
"path": [
"worksheetPlan",
"lessons",
0,
"sections",
2,
"nodes"
]
},
{
"message": "Cannot return null for non-nullable array element of type LessonSection at index 2",
"path": [
"worksheetPlan",
"lessons",
0,
"sections",
2
]
},
{
"message": "Cannot return null for non-nullable field Lesson.sections",
"path": [
"worksheetPlan",
"lessons",
0,
"sections"
]
},
{
"message": "Cannot return null for non-nullable array element of type Lesson at index 0",
"path": [
"worksheetPlan",
"lessons",
0
]
},
{
"message": "Cannot return null for non-nullable field WorksheetPlan.lessons",
"path": [
"worksheetPlan",
"lessons"
]
},
{
"message": "Cannot return null for non-nullable field WorksheetPlan!.worksheetPlan",
"path": [
"worksheetPlan"
]
}
]
}
}
Note, however, modifying the query such that Question shares the same field as MultiQuestion works
... on Question {
__typename
content
... on MultiQuestion {
id
content
__typename
}
}
Relevant query plan section
- As you can see, it’s only querying up to the Questions without doing a subtype-specific query.
Flatten(path: "worksheetPlan.lessons.@.sections.@.nodes.@.skillUserQuestions.@.userQuestion") {
Fetch(service: "learning") {
{
... on Question {
__typename
id
}
} =>
{
... on Question {
__typename
content
}
}
},
{
"data": {
"worksheetPlan": {
"lessons": [
{
"sections": [
{
"nodes": [
{
"skillUserQuestions": [
{
"userQuestion": {
"__typename": "MultiQuestion",
"content": "Which of the following cities has the largest amount of rainfall?",
"id": "mqn_01J9NDVDP5GDEMQV0SQMB64BWT"
}
},
{
"userQuestion": {
"__typename": "MultiQuestion",
"content": "Which of the following cities has the smallest amount of rainfall?",
"id": "jpuJG3mGylIx0yI91kjJ"
}
},
{
"userQuestion": {
"__typename": "ShortQuestion",
"content": "The column graph shows the number of days it hailed over six years.\n\nWhat is the total number of days it hailed over six years?"
}
}
]
]
]
]
}
Notes:
- Directly specifying
... on MultiQuestion
without nesting it under... on Question
makes the router’s query plan completely ignore learning and only fetches from ‘resources’, resulting in a similar Apollo error. - Not making the
Question
aninterfaceObject
and making it an interface in resources + short/multi does fix this issue, but this is not possible for us to do.- We don’t have subtype (short/multi) info accessible in the ‘resources’ db (it is only available on the learning db), thus the need to make Question a stub.
- We cannot declare short and multi question as stub on resources because Question is an interfaceObject and not implementable, implementing only UserQuestion does not work either
INTERFACE_OBJECT_USAGE_ERROR: [resources] Interface type "Question" is defined as an @interfaceObject in subgraph "resources" so that subgraph should not define any of the implementation types of "Question", but it defines types "ShortQuestion" and "MultiQuestion"
- The error is occurring after the addition of the
UserQuestion
abstraction. Previously, we had aQuestion
interface and its implementations (Short/Multi) in ‘learning’ and ‘resources’ hadQuestion
as an ‘interfaceObject’, and the subtypes did resolve correctly then.