Apollo-server: Support local and remote services in ApolloGateway

Created on 4 Sep 2019  ·  8Comments  ·  Source: apollographql/apollo-server

Currently ApolloGateway supports the serviceList option for remote GraphQL services and localServiceList for local GraphQL schemas, but they cannot be used together. It would be great if ApolloGateway allowed both remote and local schemas.

⛲️ feature 👩‍🚀 federation

Most helpful comment

Sorry I should have mentioned that I had tried returning a LocalGraphQLDataSource inside of buildService but ran into some issues:

  1. This check requires a url in the service definition.
  2. This object eventually gets passed to buildService (a second time for the service), but does not include anything originally in the definition (like schema) so I have to do a check on name and then reference schema from a higher scope. Is this expected? I didn't originally notice this so now I have it working.

Here is what I have now for reference. Is this the "correct" way to use LocalGraphQLDataSource?

// local schema
const schema = buildFederatedSchema([Foo, Bar, Baz]);

const serviceList = [
  // can't add `schema` to this object because `buildService()` will eventually be
  // called a second time without it (only name, url and typeDefs)
  { name: 'local', url: 'local' },
  { name: 'remote', url: 'http://remote.com/graphql' }
];

const gateway = new ApolloGateway({
  serviceList,
  buildService({ name, url }) {
    if (name === 'local') {
      return new LocalGraphQLDataSource(schema);
    }

    return new RemoteGraphQLDataSource({ url });
  }
});

All 8 comments

@tjwallace this is currently possible today (though lacking in documentation as we plan on making this more "native" in the next major version of Apollo Server). This can be done by returning a LocalGraphQLDataSource instead of a remote one when using buildService

Sorry I should have mentioned that I had tried returning a LocalGraphQLDataSource inside of buildService but ran into some issues:

  1. This check requires a url in the service definition.
  2. This object eventually gets passed to buildService (a second time for the service), but does not include anything originally in the definition (like schema) so I have to do a check on name and then reference schema from a higher scope. Is this expected? I didn't originally notice this so now I have it working.

Here is what I have now for reference. Is this the "correct" way to use LocalGraphQLDataSource?

// local schema
const schema = buildFederatedSchema([Foo, Bar, Baz]);

const serviceList = [
  // can't add `schema` to this object because `buildService()` will eventually be
  // called a second time without it (only name, url and typeDefs)
  { name: 'local', url: 'local' },
  { name: 'remote', url: 'http://remote.com/graphql' }
];

const gateway = new ApolloGateway({
  serviceList,
  buildService({ name, url }) {
    if (name === 'local') {
      return new LocalGraphQLDataSource(schema);
    }

    return new RemoteGraphQLDataSource({ url });
  }
});

But buildFederatedSchema() would fail on User.id if one schema references the other. So I don't see how using LocalGraphQLDataSource would work.

GraphQLSchemaValidationError: Field "User.id" can only be defined once

`` const server = new ApolloServer({ schema: buildFederatedSchema([{ typeDefs: gql
extend type Query {
me: User
}

  type User @key(fields: "id") {
    id: ID!
    username: String!
  }
`,
resolvers: [],

}, {
typeDefs: gql`
extend type Query {
topProducts(first: Int = 5): [Product]
}

  type Product @key(fields: "upc") {
    upc: String!
    name: String!
    price: Int
  }
`,
resolvers: [],

}, {
typeDefs: gql`
type Review {
body: String
author: User @provides(fields: "username")
product: Product
}

  extend type User @key(fields: "id") {
    id: ID! @external
    reviews: [Review]
  }

  extend type Product @key(fields: "upc") {
    upc: String! @external
    reviews: [Review]
  }
`,
resolvers: [],

}]),
context: ({ req }) => ({ user: req.user }),
})```

Where does this stand ? is it possible to do or not? Just wanted to be clear.

@5amfung Seems like buildFederatedSchema still just concatenates, and you should be splitting them into local services for each module instead of just an array of modules into buildFederatedSchema.

With help of graphql-transform-federation I'm going to try connect to a gateway not federated service

So at the very end it will be something like:

proxied

Wondering if instead of having dedicated proxy running for each service I can put it inside gateway itself, e.g.:

inlined

Considerations here are following:

  • side by side with servicesList in gateway config i will just add federated remote executable schema
  • code for both gateway and federation proxy is in js so will be nice to have them close enough
  • when dotnet will support federation i will need just to remove that proxy from gateway
  • no need to support one more deployment

From a conversation above I did not understand whether this feature is implemented or not or is it in roadmap, at moment I do see that GatewayConfig is a union of RemoteGatewayConfig and LocalGatewayConfig so it should be possible to pass both servicesList and localServiceList as a consturctor options but from the gateway code it seems that it is expecting typeDefs and wont receive ready to use schema

After having a closer look at @tjwallace answer I also were able to setup this by just adding ternary into buildService which now returns LocalGraphQLDataSource(federatedSchem) or RemoteGraphQLDataSource based on config

@mac2000 :- how will the queries execute for schemas which you have passed into constructor of LocalGraphQLDataSource, since basically you have mocked the schemas?

@rahul22048 oh it is very old at very end we did make following:

right inside apollo federation gateway wrap all services with code pieces from graphql federation transformation

in at very end our gateway was something like:

var gateway = new Gateway({ services: process([
    {name: 'federated-service-1', url: 'http://localhost:3000'},
    {name: 'nonfederated-service-2', url: 'http://localhost:5000', config: require('./nonfederated-service-2.js')},
]) })

where process function inside returned records as is if there were no config property, other wise do transformation with applied configs

and it was working for a while, but later we did manage how to make federation support in dotnet so no need for this, at moment everything is working without any proxies

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jpcbarros picture jpcbarros  ·  3Comments

deathg0d picture deathg0d  ·  3Comments

nevyn-lookback picture nevyn-lookback  ·  3Comments

stevezau picture stevezau  ·  3Comments

dobesv picture dobesv  ·  3Comments