Apollo Federation: Is it possible, and how, to extend a type and add a field which depends on fields from two different types?

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 :100: 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.