apollographql / federation

🌐  Build and scale a single data graph across multiple services with Apollo's federation gateway.
https://apollographql.com/docs/federation/
Other
658 stars 244 forks source link

Node query support in federated apollo server #1067

Open tot-ra opened 2 years ago

tot-ra commented 2 years ago

Hey. We would like to have node query & node ID support.

Our use case is that our frontend uses relay-specific features and wants generic entity loading by ID. There is a possibility with regular apollo server to define node interface on an entity & use node resolver: https://stackoverflow.com/questions/53117834/how-to-implement-a-node-query-resolver-with-apollo-graphql

But we would like to reference node interface on federated entities and use global node resolver (on a federated apollo gateway).

Example service A

  type Foo implements Node {
    id: ID!
    foo: String
  }
  interface Node {
    id: ID!
  }

service B:

  type Bar implements Node {
    id: ID!
    bar: String
  }
  interface Node {
    id: ID!
  }

Now what we want is to inject extra schema & extra resolver into the gateway (in addition to URLs of services where we fetch the schema

const schema = gql`
  interface Node {
    id: ID!
  }

  type Query {
    node(id: ID!): Node
  }
`;
const resolvers = {
  Query: {
    node: async (root, args, context) => {
      const { __typename, id } = decode(args.id)

      return {
        id,
        __typename, // <--- I assume this will help to resolve actual value by reference, eg.  with __resolveReference() resolvers
      }; }}};

Then if we could somehow inject them & hope that server will resolve them before hitting actual services.. that would be great

const server = new ApolloServer({
    schema, // <---- initialSchema ?
    resolvers, // <---- initial Resolvers ?
  gateway: new ApolloGateway({
    serviceList: [
    { name: 'service_a', url: 'https://a.dev/graphql' },
    { name: 'service_b', url: 'https://b.dev/graphql' }
    ]
  }),
});

If this support already exists is some form, maybe add it to documentation?

Right now I'm getting

[graphql-service] Error: Cannot define both `gateway` and any of: `modules`, `schema`, `typeDefs`, or `resolvers`
[graphql-service]     at new ApolloServerBase (/app/node_modules/apollo-server-core/dist/ApolloServer.js:67:19)

Based on the code, if server is initialized with gateway mode, then it initializes internal schemaManager and schema is overwritten with its getSchemaDerivedData() value.

So my guess is that this hybrid schema/resolver would require changes in both SchemaManager class and ApolloServer

tot-ra commented 2 years ago

I tried one workaround - to define Query.node resolver only in service A and in that case type Foo resolving worked, but when I tried returning reference to Bar, I got "Fragment cannot be spread here as object of type Node can never be of type Bar". Thats logical, as service A doesn't know anything about Bar, only whats defined in it

glasser commented 2 years ago

I'm going to move this over to the Federation repo which is where this sort of thing is discussed.

tot-ra commented 2 years ago

thanks. Most closest issue is https://github.com/apollographql/federation/issues/377 but its also a workaround with a dedicated service providing all types for node query like I tried ⬆️

lukas-krecan commented 2 years ago

We are trying to solve the same issue. In my opinion current behavior is not correct. If we create node query resolver service, Apollo sends us query like this

{node(id:"dHlwZUZyb21BU2VydmljZTox"){__typename ...on TypeFromAnotherService{id __typename}}}

where TypeFromAnotherService is a type defined in another service. This is something that should not happen in my opinion.

tot-ra commented 2 years ago

I agree. This has 2 major disadvantages

ssukienn commented 2 years ago

Adding some kind of library support for this or documenting alternatives/approaches would be really beneficial.

This is popular pattern. Maybe not as much as relay pagination but still.

theJC commented 1 year ago

We had implemented this as well for federation 1, and come to find out the way we got it working was counting on a bug in the federation 1 query planner, and so we are having to re-work our solution in order to migrate to federation 2.

By having a single common implementation in the gateway/router, this will reduce the cost to all of Apollo federation customers who are/wish to leveraging Node query support in their federated graphs, and assure that future updates/upgrades to the gateway are much less likely to inadvertently break this single/common implementation, as well as hopefully allowing Apollo and its customers to ensure an extremely performant implementation.

Global object identification / Node interface is a very popular and useful capability in the GraphQL community, and not having this available out of the box with Apollo's federation offerings seems like a huge miss.

mcohen75 commented 1 year ago

At Indeed we created a subgraph that implemented the node and nodes queries. That service dynamically loaded schema using Netflix DGS's Hot Reloading (https://netflix.github.io/dgs/advanced/schema-reloading/).

This worked in Fed1 because the query planner had no problem sending requests to this nodes subgraph that included types it didn't define. This seemed sensible because the queries returned the Node interface and relied on other subgraphs to provide concrete types. Though admittedly the subgraph was not able to stand on its own.

The workarounds I'm aware of that work with Fed 2 are kinda painful:

  1. Whenever any subgraph publishes its schema, figure out if a type that implements node was added or removed. If one or more changes exist, publish a new schema for the nodes subgraph. The main problem here is toil around creating and maintaining this solution.
  2. Periodically generate and republish the nodes subgraph schema. This is also toil and adds the disadvantage that we have to wait until the next run before the new schema is available.
pcmanus commented 1 year ago

This worked in Fed1 because the query planner had no problem sending requests to this nodes subgraph that included types it didn't define. This seemed sensible because the queries returned the Node interface and relied on other subgraphs to provide concrete types. Though admittedly the subgraph was not able to stand on its own.

I do want to say: the fact it worked in federation 1 was never intended, and was not part of some "intelligent" design. Essentially, fed 1 interface handling is rough and this lead to generating query plans with subgraph queries that are invalid for the subgraph schema. Or at least, invalid for the subgraph schema presented to composition.

And I hope this won't be taken as me blaming anyone for creatively relying on that federation 1 "bug". I can definitively see how, when it helped with doing something genuinely useful, it could have even felt like an intentional design. And I'll admit that when we fixed this bug with fed2, we were focused on fixing the cases where it's genuinely harmful (generating plans that fail at runtime when it could and should not), and didn't realise that this same problem created opportunities that were relied on by some users. I wish we had realised that sooner, so we could have started thinking of how to bring back the "good" parts while keeping the "bad" parts fixed.

But I'm trying to justify why I genuinely believe the "bad" parts needed to be fixed, and this mean that "something" else was necessary to bring back those "good" parts.

The workarounds I'm aware of that work with Fed 2 are kinda painful

I agree that it's not ideal to do in fed2 today, and I agree that it's worth making better. And fwiw, I've laid out how I'd like to support this in #2277, more specifically in the section called "Dynamic dispatch". I'd definitively value feedback on this btw.

Of course, I understand that being pointed to some "future work" when you have something that work for you in fed1 is frustrating, and I'm again sorry I didn't realise sooner that users were relying on this behaviour of fed1 sooner.

and not having this available out of the box with Apollo's federation offerings seems like a huge miss

Agreed with this as well. My personal preferred end state would be to both:

  1. have enough flexibility in federation so the Node API can be "manually" implemented both conveniently and safely: which is the "dynamic dispatch" idea I've mentioning above.
  2. but also have an out-of-box implementation of said Node API, because it's a common API and there is no reason for everyone to re-implement it manually.
ziahamza commented 3 months ago

@pcmanus has there been any updates on point number 2 earlier to implement Relay compliant services with Apollo Federation v2? Or if you can point to any way of doing it in a complaint way than it will help a lot!