I am learning/using Apollo Federation. I have a situation where I have my hierarcy set up as follows:
type ObjectA @key(fields: "id") {
id: ID!
fieldA: String!
objectB: ObjectB!
}
type ObjectB @key(fields: "id") {
id: ID!
fieldB: String!
}
In a separate service, I want to extend ObjectB
by adding a field which depends on both fieldA and fieldB. I’m not sure if this is possible. I can definitely make it depend on fieldB, but I’m not sure if there is a directive which indicates that it depends on fieldA as well.
extend type ObjectB @key(fields: "id") {
id: ID! @external
fieldB: String! @external
newField: String @requires(fields: "fieldB")
}
That looks like a valid document to me. You should have no problem extending the ObjectB
type and adding fields in a different service. When the schema is stitched together, the final type def will look like this:
type ObjectB {
id: ID!
fieldB: String!
newField: String
}
One thing I highly recommend is taking a look at the query plan to understand how these fields are resolved.
That all would work! I would recommend using Apollo Workbench and using this to plan out your designs. @kartik_gujarati is right on taking a look at the query plan, workbench can show you that as well. Here is your schema in workbench, fully composing (you can see the supergraph schema if you want also). Hopefully this tool helps you design/plan our your federated schema with a deeper understanding of how the query will execute.
I’m sorry, I’m having trouble seeing how this demonstrates that the new field depends on both ObjectB.fieldB and ObjectA.fieldA. It looks like what I originally had, which is specifying that it requires ObjectB.fieldB, but I don’t see anywhere indicating that it requires ObjectA.fieldA for the newField.
The ObjectB.newField
only “depends on” (requires) ObjectB.fieldB
and ObjectB
does not need ObjectA
to get resolved as per your schema doc. From the screenshot @michael-watson posted above, you can see that for a query that looks something like below:
query TestQuery {
objectA {
fieldA
objectB {
fieldB
newField
}
}
}
you can see that the query planner first resolves ObjectB.fieldB
(#L21 in the MyQuery.queryplan) and then uses that to resolve ObjectB.newField
. The full query plan looks like this:
QueryPlan {
Sequence {
Fetch(service: "subgraph-one") {
{
objectA {
fieldA
objectB {
fieldB
__typename
id
}
}
}
},
Flatten(path: "objectA.objectB") {
Fetch(service: "subgraph-two") {
{
... on ObjectB {
__typename
id
fieldB
}
} =>
{
... on ObjectB {
newField
}
}
},
},
},
}
So, it basically sounds like you can not have a field in ObjectB require a field in ObjectA, correct?
Technically it is possible. You can have a ObjectA type field in the ObjectB and have other fields in ObjectB depend on fields in ObjectA. Something like this:
type ObjectB {
id: ID!
fieldB: String!
objectA: ObjectA
}
And in the other service you extend ObjectB as:
extend type ObjectB @key(fields: "id") {
id: ID! @external
fieldB: String! @external
objectA: ObjectA @external
newField: String @requires(fields: "fieldB")
anotherNewField: String @requires(fields: "objectA { fieldA }")
}
Take a look at Entities in Apollo Federation - Apollo GraphQL Docs
However, be cautious creating a circular dependency between types. A bad actor can make a nested query (like below) to your service causing your service to go down.
query {
objectA {
fieldA
objectB {
fieldB
objectA {
objectB {
objectA {
......
}
}
}
}
}
}
If this nesting is a must for your use-case, take a look at the depth limiting library - https://github.com/stems/graphql-depth-limit
Hope this helps.