Search code examples
graphqlapollo-serverapollo-federation

Apollo Gateway: duplicate / identical types across subgraphs returns null results?


My team and I are trying to move away from our monolithic Apollo Server API pattern toward GQL micro services with Apollo Federation.

Before we get into any code, some preface: my team has used GraphQL Codegen for our monolith to make a single master schema by finding and combining all the various type defs scattered across any number of .graphql files. The resulting output is a src/generated/types.ts file, which several of our resolvers and utility functions import the generated types from. This of course won't work if we're looking to deploy our micro services in isolation.

So, moving towards using a gateway, and with the current goal of being able to continue using GraphQL Codegen to generate and import types, we've simply got some type defs duplicated for now in order to do local type generation. Any optimizations and deduping will occur when we get the time, or by necessity if that isn't something we can have as tech debt. 😬

Minus some redacted information for security purposes, this same file is duplicated across all subgraphs which the gateway consumes.

users.graphql

extend type Query {
  self: User
}

type User implements IDocument & ICreated & IUpdated & IDisplayName {
  "Unique identifier for the resource across all collections"
  _id: ID
  "Unique identifier for the resource within its collection"
  _key: ID
  "Unique identifier for revision"
  _rev: String
  "ISO date time string for the time this resource was created"
  createdAt: String
  "Unique identifier for users that created this resource"
  createdBy: ID
  "ISO date time string for the time this resource was created"
  updatedAt: String
  "Unique identifier for users that created this resource"
  updatedBy: ID
  "A preformatted name safe to display in any HTML context"
  displayName: String
  "Email addresses"
  email: String
  "Determines if a users is a service account supporting applications"
  isServiceAccount: Boolean
}

extend type Mutation {
  user: UserMutation
}

type UserMutation {
  save(user: UserInput): User
}

input UserInput {
  "Unique identifier for the resource across all collections"
  _id: ID
  "Unique identifier for the resource within its collection"
  _key: ID
  "Unique identifier for revision"
  _rev: String
  "A preformatted name safe to display in any HTML context"
  displayName: String
  "Email addresses"
  email: String
}

GraphQL Codegen generates types as expected, and the service compiles just fine. What's more is that the Gateway also seems to have no problems (i.e. compiles and runs) in stitching together several subgraphs containing the duplicate types.

However, when I attempt to execute the following query on GraphQL Playground,

query Self {
  self {
    _id
    _key
    displayName
    email
  }
}

It just returns

{
  "data": {
    "self": null
  }
}

If I change the Gateway's supergraphSdl to only grab just one micro service, thus avoiding type duplication, I get results as expected:

{
  "data": {
    "self": {
      "_id": "users/c3a6062f-b070-4e39-8b2a-9d1354e9dccb",
      "_key": "c3a6062f-b070-4e39-8b2a-9d1354e9dccb",
      "displayName": "redacted",
      "email": "redacted"
    }
  }
}

(Here's the resolver if it matters. I've debugged the dickens out of it and have come to the conclusion that it works fine.)

const query: QueryResolvers = {
  self: (_, __, context) => context.user,
};

I'm still pretty new to Federation, so I apologize if there's an obvious answer. But given what I've related, is there any way to

  • allow type duplication across the several subgraphs to be stitched together, and is there a way to
  • still keep isolated, generated types for each service?

I've looked at various possible ways to resolve this issue , exploring the extends keyword and wondering if extending the User type def across all but one of the services would leave just one "master" User type def: that either didn't work or I did something wrong. I've only got the vaguest idea of what's going on, and I'm guessing the Gateway is confused about which type and from which service it's supposed to use in order to return a response.

Here are various relevant packages and versions which might help solve the issue:

Gateway

"@apollo/gateway": "^2.0.1",
"graphql": "^16.4.0"

GQL Micro services

"graphql": "^16.4.0"
"@graphql-codegen/cli": "^2.6.2",
"@apollo/federation": "^0.36.1",

Any help is immensely appreciated, even if it means what I want isn't possible! If more information and / or code is required I will be happy to give it.


Solution

  • I didn't solve the quest for allowing duplication, but ultimately, I didn't want to have to do it anyway, and since I don't think I even can now, that's fine.

    Instead, I was able to find a much more elegant solution!

    We just have to point GraphQL Codegen's schema field in the various codegen.yml files to whichever .graphql sources are required. That way, we get the types we need to use, and we prevent usurpation of single sources of truth by not redeclaring type defs. So, happy ending. 🥳

    Great discussion y'all