GraphQL Federation: What's the right way to use __typename for fragment on Interface?

We are upgrading our GraphQL Federation to the latest “@apollo/gateway”: “2.14.4” and find out that it handles fragment on interface differently than the older version.

In the older version, the query plan will interpret fragment to a flat call to subgraph, including the typename field automatically. But in 2.14.4, the call to subgraph now receives the fragment directly without an interpretation and typename is no longer added. That breaks our feature as our subgraph only responds __typename when __typename is present in the input (and our existing queries didn’t include the __typename field)

So I wonder if this is a bug in the @apollo/gateway engine, or maybe our subgraph is encouraged to respond typename ourselves on Interfaces. (I tested with letting subgraph to respond typename regardless of the input, the federation server worked well with it). What is the best practice to use __interface? Is it encourage to exist on fragment in the input queries?

This part of the code in query-planner-js/src/buildPlan.ts suggests that typename should still be added. But in "@apolloapolloapolloapollo/gateway": “2.14.4”, our query didn’t have typename at all.

    const reuseQueryFragments = this.config.reuseQueryFragments ?? true;
    let fragments = operation.fragments;
    if (fragments && !fragments.isEmpty() && reuseQueryFragments) {
      // For all subgraph fetches we query `__typename` on every abstract types (see `FetchGroup.toPlanNode`) so if we want
      // to have a chance to reuse fragments, we should make sure those fragments also query `__typename` for every abstract type.
      fragments = addTypenameFieldForAbstractTypesInNamedFragments(fragments);
    } else {
      fragments = undefined;
    }

Something have caused this part of the code to be skipped. I wonder if it happened to do with the flag this.config.debug.bypassPlannerForSingleSubgraph before the block. I tried a query that makes requests to 2 subgraphs, and the problem persists. (So it wasn’t bypassPlannerForSingleSubgraph)

Hello :waving_hand:

Can you provide some additional information on what you are encountering and what problems this causes?

Query planner is responsible for generating an “efficient” plan on how to fulfill user request. This involves normalization of a query and then generation of 1+ subgraph fetches to fulfill the user request. Due to the normalization process your incoming queries may not be the same as the ones that gateway sends to the subgraph.

bypassPlannerForSingleSubgraph is an option to skip the planning entirely and pass in the original query to the target subgraph (i.e. skip normalization and query plan generation). This was specifically added for monographs (i.e. single GraphQL subgraph). In this situation, if your original query did not include __typename selection it won’t be include query either.

Referenced section of the code populates __typename defensively (in many cases it may not be needed) and its only used IF you choose a query plan option to reuseQueryFragments (this option attempts to preserve user provided fragments from the original query in the subgraph queries).

If you don’t enable any query optimizations OR use the option to generateQueryFragments (preferred mechanism) then this normalization is not applied at all and __typenames will only be included if needed.

Whether you are using reuseQueryFragments, generateQueryFragments or send uncompressed queries, query planner should generate valid queries. Your queries to the gateway/router should only include what is necessary for the clients. If additional data is necessary (e.g. through @requires or some type condition information) it will be populated by the query planner.

Thanks,
Derek

Hi Derek,

Thanks for the reply. I setup a demo of the bug in GitHub - yuhanz/bug-demo · GitHub

Install the dependencies with npm install --force , and start the app with sh start.sh. The input form is available at: http://localhost:4000/federation/

The app has 1 subgraph, which is based on schemas/subgraph.graphqls converted to schemas/supergraph2.sdl . I used src/MockGraphqlDataSource.js to emulate a subgraph backend where it responds json without typename when it is not in the query and add typename in the response when it appears in the query.

This query will demonstrate the bug:

fragment optionFields on IOption {
  id
   ... on ColorOption {
    primaryRGB
  }
}
{
      style(styleId: "401822600") {
        options {
      colors { ...optionFields }
      }
    }
}

The result isn’t rendered correctly with the RGB related field:

{
  "data": {
    "style": {
      "options": {
        "colors": [
          {
            "id": "1"
          },
          {
            "id": "2"
          }
        ]
      }
    }
  }
}

The query has to include __typename in fragment to become a working query:

fragment optionFields on IOption {
  id
   ... on ColorOption {
    primaryRGB
  }
}
{
      style(styleId: "401822600") {
        options {
      colors { 
        __typename
        ...optionFields }
      }
    }
}

That gives the expected result:

{
  "data": {
    "style": {
      "options": {
        "colors": [
          {
            "__typename": "ColorOption",
            "id": "1",
            "primaryRGB": [
              255,
              255,
              255
            ]
          },
          {
            "__typename": "ColorOption",
            "id": "2",
            "primaryRGB": [
              9,
              9,
              9
            ]
          }
        ]
      }
    }
  }
}

Meanwhile, inline fragment always works, as it knows to include __typename automatically:


{
      style(styleId: "401822600") {
        options {
      colors { 
        ... on ColorOption {
          primaryRGB
        } }
      }
    }
}

Quite unfortunately, I wasn’t able to identify at which combination of earlier versions of dependencies, fragment still worked. (It could be a combination of “@apollo/gateway” and “graphql”). But it used to be smart enough to know when to include __typename automatically for fragment queries.

It seems a bug. Or maybe it has to do with my project setup / config parameters / dependency version? Could you guide, please?

Thank you.

Yuhan Zhang

Hi,

I prepared a pull request, which added the __typename on fragment query: fixing the missing __typename when fragment appears in the query by yuhanz · Pull Request #3475 · apollographql/federation · GitHub

While the current main branch failed 1 test case for query-planner-js (which is the test of “only uses an @interfaceObject if it can”), this change has passed the particular test:

npm ci
npm test -- --runInBand query-planner-js/src/__tests__/buildPlan.interfaceObject.test.ts

 PASS   @apollo/query-planner  query-planner-js/src/__tests__/buildPlan.interfaceObject.test.ts
  ✓ avoids buffering @interfaceObject results that may have to filtered with lists (18 ms)
  ✓ handles @requires on concrete type of field provided by interface object (22 ms)
  ✓ handles @interfaceObject in nested entity (15 ms)
  ✓ handles @interfaceObject input rewrites when cloning dependency graph (22 ms)
  basic @key on interface/@interfaceObject handling
    ✓ can use a @key on an @interfaceObject type (8 ms)
    ✓ can use a @key on an interface "from" an @interfaceObject type (2 ms)
    ✓ only uses an @interfaceObject if it can (1 ms)
    ✓ does not rely on an @interfaceObject directly for `__typename` (2 ms)
    ✓ does not rely on an @interfaceObject directly if a specific implementation is requested (2 ms)
    ✓ can use a @key on an @interfaceObject type even for a concrete implementation (2 ms)
    ✓ handles query of an interface field (that is not on the `@interfaceObject`) for a specific implementation when query starts on the @interfaceObject (1 ms)

Test Suites: 1 passed, 1 total
Tests:       11 passed, 11 total
Snapshots:   11 passed, 11 total
Time:        1.03 s, estimated 2 s

Meanwhile, there are many other tests failing in the main branch. The CI test cases in the Github pull request doesn’t look clean. But I think the error is probably unrelated to the change? Could you guide on if this pull request can be considered?

Thank you.

The reproduction makes this look more like a query planning issue than something the subgraph should work around.

What’s interesting is that the inline fragment works, while the named fragment loses primaryRGB unless __typename is explicitly selected.

I’d compare the actual subgraph fetch operations generated with reuseQueryFragments enabled and disabled, using the same schema and input query. That might help isolate whether fragment reuse is dropping the type information needed to evaluate the concrete type condition.

I’d also check whether the gateway receives __typename internally but removes it from the final response, or whether it never requests it from the subgraph in the first place. Those would point to different stages of the problem.

I wouldn’t change the subgraph to return extra fields unconditionally just to work around this. The query planner should be responsible for requesting the information it needs.