apollographql / router

A configurable, high-performance routing runtime for Apollo Federation 🚀
https://www.apollographql.com/docs/router/
Other
808 stars 272 forks source link

Returns null for interface entity when it is resolved before a nested entity #5844

Open aliosmanisikk opened 2 months ago

aliosmanisikk commented 2 months ago

Describe the bug

It is hard to describe this issue since we don't know the exact issue but I'll try to explain our use case where I created a showcase here https://github.com/aliosmanisikk/apollo-router-entity-error-showcase

In a nutshell, there is an issue with resolving interface entity in this specific scenario which I'm about to show. I'm sure there are other cases but this is how we figured it out.

To Reproduce

Setup 2 subgraphs

SubgraphA schema

  extend schema @link(url: "https://specs.apollo.dev/federation/v2.8", import: ["@key"])

  interface Product @key(fields: "resolveInMs") {
    resolveInMs: Int!
    brand: String!
  }

  type SunglassProduct implements Product @key(fields: "resolveInMs") {
    resolveInMs: Int!
    brand: String!
  }

  type SunglassVariant @key(fields: "sku") {
    sku: String!
    color: String!
    inventory: Inventory!
  }

  type Inventory @key(fields: "sku", resolvable: false) {
    sku: String!
  }

Subgraph B schema

  extend schema @link(url: "https://specs.apollo.dev/federation/v2.8", import: ["@key", "@shareable", "@interfaceObject"])

  type ShoppingListItem {
    id: ID!
    product(resolveInMs: Int!): Product
    variant: SunglassVariant
  }

  type Product @key(fields: "resolveInMs", resolvable: false) @interfaceObject {
    resolveInMs: Int!
  }

  type SunglassVariant @key(fields: "sku", resolvable: false) {
    sku: String!
  }

  type Inventory @key(fields: "sku") {
    sku: String!
    stock: Int!
  }

  extend type Query {
    myShoppingList: [ShoppingListItem!]!
  }

In order to make this showcase a bit more interesting, I added the Product key as resolveInMs which resolves the Product in variable amount of time. Please note that all data resolved are static. Variant entity is resolved in 3 milliseconds, Inventory is resolved in 5 milliseconds and Product is resolved with the resolveInMs argument time. Here are some results from some queries. With all data being static, one would expect the same result for queries independent of the resolve time. But here are results for some queries:

query MyShoppingList {
  myShoppingList {
    variant {
      inventory {
        sku
        stock
      }
    }
    product(resolveInMs: 0) {
      __typename
      brand
    }
  }
}

returns

{
  "data": {
    "myShoppingList": [
      {
        "variant": {
          "inventory": {
            "sku": "123456",
            "stock": 5
          }
        },
        "product": null
      }
    ]
  }
}
query MyShoppingList {
  myShoppingList {
    variant {
      inventory {
        sku
        stock
      }
    }
    product(resolveInMs: 10) {
      __typename
      brand
    }
  }
}

returns

{
  "data": {
    "myShoppingList": [
      {
        "variant": {
          "inventory": {
            "sku": "123456",
            "stock": 5
          }
        },
        "product": {
          "__typename": "SunglassProduct",
          "brand": "RayBan"
        }
      }
    ]
  }
}
query MyShoppingList {
  myShoppingList {
    variant {
      inventory {
        sku
        stock
      }
    }
    product(resolveInMs: 0) {
      brand
    }
  }
}

returns 

{
  "data": {
    "myShoppingList": [
      {
        "variant": {
          "inventory": {
            "sku": "123456",
            "stock": 5
          }
        },
        "product": {
          "brand": "RayBan"
        }
      }
    ]
  }
}
query MyShoppingList {
  myShoppingList {
    variant {
      color
    }
    product(resolveInMs: 0) {
      __typename
      brand
    }
  }
}

returns

{
  "data": {
    "myShoppingList": [
      {
        "variant": {
          "color": "green"
        },
        "product": {
          "__typename": "SunglassProduct",
          "brand": "RayBan"
        }
      }
    ]
  }
}

Summary

I tested Product as regular entity as well and there is no issue with that. So, the issue happens with only interface entities and requesting __typename while a nested entity is resolved next to it.

Additional Context

tlorentgv commented 2 months ago

Thanks for the detailed write up @aliosmanisikk , quite helpful. It now requires our project to write quite a cumbersome, far from ideal workaround. Any help from Apollo? 🙏