Giter Site home page Giter Site logo

apollographql / federation Goto Github PK

View Code? Open in Web Editor NEW
647.0 43.0 239.0 94.13 MB

🌐  Build and scale a single data graph across multiple services with Apollo's federation gateway.

Home Page: https://apollographql.com/docs/federation/

License: Other

Gherkin 5.15% JavaScript 0.16% TypeScript 94.56% Shell 0.13%
apollo graphql federation gateway apollo-federation

federation's Introduction

CircleCI Netlify Status

Apollo Federation

Apollo Federation is an architecture for declaratively composing APIs into a unified graph. Each team can own their slice of the graph independently, empowering them to deliver autonomously and incrementally.

Federation 2 is an evolution of the original Apollo Federation with an improved shared ownership model, enhanced type merging, and cleaner syntax for a smoother developer experience. It’s backwards compatible, requiring no major changes to your subgraphs.

Checkout the Federation 2 docs and demo repo to take it for a spin and let us know what you think!

For Federation 1, see the docs and code.

Checkout the project roadmap to see what's coming next!

Contributing

If this project seems like something to which you want to contribute, first off thank you. We are so excited that you are excited about this project and we want to make sure contributing is a safe, fun, and fruitful experience for you. Please read our code of conduct and then head on over to the contributing guide to learn how to work on this project.

If you ever have any problems, questions, or ideas, the maintainers of this project are available to help. Please open an issue for assistance!

Current branches

  1. Federation 2 is the current main branch. Installing latest from npm is the most recent published code from main.
  2. Prior releases are located under the version-0.x branch. This comprises all 0.x packages previously released. Installing latest-1 from npm is the most recent published code from version-0.x.

Who is Apollo?

Apollo builds open-source software and a graph platform to unify GraphQL across your apps and services. We help you ship faster with:

  • GraphOS – A free, end-to-end platform for managing your GraphQL lifecycle. Track your GraphQL schemas in a hosted registry to create a source of truth for everything in your graph. GraphOS provides an IDE (Apollo Explorer) so you can explore data, collaborate on queries, observe usage, and safely make schema changes.
  • Apollo Federation – The industry-standard open architecture for building a distributed graph. Use Apollo’s to compose a unified graph from multiple subgraphs, determine a query plan, and route requests across your services.
  • Apollo Client – The most popular GraphQL client for the web. Apollo also builds and maintains Apollo iOS and Apollo Android.
  • Apollo Server – A production-ready JavaScript GraphQL server that connects to any microservice, API, or database. Compatible with all popular JavaScript frameworks and deployable in serverless environments.

Learn how to build with Apollo

Check out the Odyssey learning platform, the perfect place to start your GraphQL journey with videos and interactive code challenges. Join the Apollo Community to interact with and get technical help from the GraphQL community.

Licensing

Source code in this repository is covered by (i) the Elastic License 2.0 or (ii) an MIT compatible license, in each case, as designated by a licensing file in a subdirectory or file header. The default throughout the repository is a license under the Elastic License 2.0, unless a file header or a licensing file in a subdirectory specifies another license.

federation's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

federation's Issues

[Federation] Partial Queries - Discussion

Something I'd really like to see for federation is to support partial queries/results. As an example:

federated service 1's SDL might be:

extend union ConfigTypes = OrgCommunicatonsConfig

type OrgCommunicatonsConfig {
    canSendMessages: Boolean!,
    canSendSMS: Boolean!
}

extend type Query {
    getOrgConfig(): [ConfigTypes!] @partial
}

federated service 2's SDL might be:

extend union ConfigTypes = OrgSecurityConfig

type OrgSecurityConfig {
    setting1: String!,
    setting2: String!
}

extend type Query {
    getOrgConfig(): [ConfigTypes!] @partial
}

The resulting schema would be:

union ConfigTypes = OrgSecurityConfig | OrgCommunicatonsConfig

type OrgSecurityConfig {
    setting1: String!,
    setting2: String!
}

type OrgCommunicatonsConfig {
    canSendMessages: Boolean!,
    canSendSMS: Boolean!
}

type Query {
    getOrgConfig(): [ConfigTypes!]
}

The query executor would then forward any query to getOrgConfig to each service and then amalgamate the results so you can then do something like:

query {
    getOrgConfig() {
        ... on OrgSecurityConfig {
         /// query fields
       }
       ... on OrgCommunicatonsConfig {
         /// query fields
       }
    }
} 

Federation: Middlewares have no effect?

I am trying to use graphql-shield on my gateway server. It seems like while the shield is called, the error from the shield does not make it to the user. My guess is that the executor is somehow overriding the result from the middleware.

Is there a documented way of using middlewares with apollo-gateway?

If it is currently not possible to use middlewares with apollo-gateway, this is a feature request 😁

const { schema: federatedSchema, executor } = await gateway.load();

const server = new ApolloServer({
  schema: applyMiddleware(
    federatedSchema,
    shield(
      {
        Query: deny,
        Mutation: deny
      },
      {
        debug: true
      }
    )
  ),
  executor
});

Support custom links for Apollo Gateway

I'm currently stitching all of our micro-services using a custom Apollo link to make Lambda invocations instead of a normal HTTP request. The reason is that our services don't have a public HTTP endpoint since there's no need for that.

Apollo Gateway expects the federated schemas to be available via a HTTP endpoint and I was wondering if it's possible to just set a link instead.

Also, a bit unrelated, but we also avoid the introspection step by referencing to the typedefs directly (each service exposes a SDK with those typedefs). This is to avoid doing that request because in a Lambda architecture there are no long running servers, so that takes a lot of calls. Would it be possible to also allow this?

High availability support for Federation

First let me say we love federation on our team. We split our monolith into approx. 11 microservices and have been very pleased with the ease of the refactor and the results.

There were some issues around availability that we had to roll ourselves, and it led me to think maybe these would be better included in the federation/gateway package.

First of all, the gateway attempts to connect to the federated services, and if it is unable it still get's initialized, only without the services it failed to connect too. This is troublesome for an application that is deployed automatically as it requires human intervention to monitor if the gateway initialization was successful.

Our workaround was to create a readinessProbe that performs the same query the gateway makes to the federated services, with the goal being to make sure the services are available and ready to receive traffic. This probe has a configurable number of retries and timeout before throwing. If the probe is unsuccessful we let the application to crash, as it doesn't make sense for us to allow it to initialize without all services being available.

Perhaps this feature could be built into the gateway, along with a configurable number of retries? If it is unsuccessful it crashes and does not init. Perhaps that too could be a configuration mustConnect: true/false (I'm sure there's a better name for the property).

Other than that we let kubernetes manage the availability of the other services via liveness probes, but for teams not using a deployment orchestration manager like a kubernetes perhaps there is a way by which the gateway could check the availability of its downstream services?

Also, a periodic reloading of the schema would be helpful. Let's take the case where we update a schema in one of our services and redeploy it. The gateway wouldn't know the service's schema had changed so we must restart the gateway for it to get the changes. This hurts availably by causing downtime while the gateway is redeployed. If the gateway would refresh its schema on a configurable polling interval that issue could be avoided. We've developed a hack using helm charts to tell the gateway to redeploy when one of the downstream services have been updated, but that still causes downtime.

If any of these ideas seems like it would be a good addition to the project I would be more than happy to make a PR. I am truly appreciate of the work the apollo team is doing and I'd love to contribute.

File upload object is empty in federated gateway but not if called directly on the service

After federating a service that implements the upload scalar, for some reason, if I hit that resolver from the federated gateway, the file object is empty.

(base) cL:gateway chris$ npm run start-apollos

> [email protected] start-apollos /Users/chris/projects/bx/gateway
> concurrently "npm:start-apollo-*"

[start-apollo-data]
[start-apollo-data] > [email protected] start-apollo-data /Users/chris/projects/bx/gateway
[start-apollo-data] > NODE_ENV=development node -r ts-node/register ./src/apollos/DataLibrary/index.ts
[start-apollo-data]
[start-apollo-accounts]
[start-apollo-accounts] > [email protected] start-apollo-accounts /Users/chris/projects/bx/gateway
[start-apollo-accounts] > node -r ts-node/register ./src/apollos/Accounts/index.ts
[start-apollo-accounts]
[start-apollo-bioref]
[start-apollo-bioref] > [email protected] start-apollo-bioref /Users/chris/projects/bx/gateway
[start-apollo-bioref] > node -r ts-node/register ./src/apollos/BioRef/index.ts
[start-apollo-bioref]
[start-apollo-accounts] Account apollo running at http://localhost:9001/
[start-apollo-bioref] BioRef Apollo running at http://localhost:9003/
[start-apollo-data] πŸš€ Server ready at http://localhost:9002/graphql
[start-apollo-data] { file: {}, something: 'hello' }
[start-apollo-data] { file: {}, something: 'hello' }

But then if I hit the service directly, everything is fine.

[start-apollo-data] πŸš€ Server ready at http://localhost:9002/graphql
[start-apollo-data] Promise {
[start-apollo-data]   { filename: 'test.csv',
[start-apollo-data]     mimetype: 'text/csv',
[start-apollo-data]     encoding: '7bit',
[start-apollo-data]     createReadStream: [Function: createReadStream] } }

Is there something I am doing wrong with the apollo-gateway?

For reference,

// TypeDefs
export const typeDefs = gql`
  scalar Upload

  extend type Mutation {
    testUpload(file: Upload, something: String): String
  }
`;
// *Resolvers

import { GraphQLUpload } from 'graphql-upload';

const testUpload = async (obj, { file }, ctx, info) => {
  const { createReadStream } = await file;
  console.log(file);
  return '';
};
export const resolvers = {
  Upload: GraphQLUpload,
  Mutation: {
     testUpload(obj, args, ctx, info) {
        return testUpload(obj, args, ctx, info)
     }
  },
};

For now, I've worked around it by registering another client that directly hits the service instead of the gateway just to do file uploads, but I feel like this defeats the point of federating in the first place.

Any suggestions/guidance would be greatly appreciated!

[Federation] Identical Interfaces not allowed

I'm using the latest verion of apollo server and apollo gateway. I've implemented two federated endpoints (in PHP) here:

Both of these endpoints use the same code base, but different databases. I've ensured that all of the interfaces are identical, and the objects are namespaced. For instance MediaWikiRevision is an interface of a revision that is identical. There is a TatooineRevision and a JakkuRevision that both implement the identical interface. I've also ensured that the fields on the interface only return other interfaces (not objects).

However, when I try to use these in Apollo Gateway I get the following error:

GraphQLSchemaValidationError: Field "MediaWikiRevision.revid" can only be defined once.
Field "MediaWikiRevision.parent" can only be defined once.
Field "MediaWikiRevision.user" can only be defined once.
Field "MediaWikiRevision.anon" can only be defined once.
Field "MediaWikiRevision.timestamp" can only be defined once.
Field "MediaWikiRevision.size" can only be defined once.
Field "MediaWikiRevision.sha1" can only be defined once.
Field "MediaWikiRevision.comment" can only be defined once.
Field "MediaWikiRevision.parsedcomment" can only be defined once.
Field "MediaWikiRevision.tags" can only be defined once.
Field "MediaWikiRevision.roles" can only be defined once.
Field "MediaWikiRevision.slot" can only be defined once.
Field "MediaWikiRevision.slots" can only be defined once.
There can be only one type named "MediaWikiRevision".

According to the docs it's totally accaptable to have overlapping types, as long as they are identical. Doing a google search for the error reveals this unanswered stack overflow question.

I'm not sure what the problem is, but according to the documentation, this error shouldn't be thrown... am I missing something?

Federation - Suggestion to move from _service as SDL to actual types

Had a chance to look through the Apollo Federation solution and spec. I love the idea of being able to compose services in a declarative way.

However I feel like the current specification of the Federation approach is a little clunky. In particular the new _service field which is basically a printed SDL with the proper directives still attached. This feels like it heavily favors implementations that use an SDL first approach (as code-first approaches would require additional tools to re-construct the schema in SDL form with all these directives attached).

I was wondering what the stance on this is going forward? I'm wondering if the spec of the _service field could become more standardized and use the native graphql type system to expose this sorts of metadata (and not via a special schema print).

At first glance, this is what I came up with looking over the spec (I most likely missed things):

type _Service {
  _types: [_FederatedTypeMetaData!]!
}

type _FederatedTypeMetaData {
  _type: String! # 'User'
  _key: _FieldSet! # ['id']
  _provides: [_FederatedTypeProvidesMetaData!]! # { _field: 'product', _provides: ['name']  }
  _requires: [_FederatedTypeRequiresMetaData!]! # { _field: 'reviews', _requires: ['email']  }
  _externals: [_FederatedTypeExternalMetaData!]! # { _field: 'email' }
  _extends: Boolean! # true
}

type _FederatedTypeProvidesMetaData {
  _field: String!
  _provides: _FieldSet!
}

type _FederatedTypeRequiresMetaData {
  _field: String!
  _requires: _FieldSet!
}

type _FederatedTypeExternalMetaData {
  _field: String!
}

This would be a more approachable solution for other languages and more importantly a lot more code-first approaches (such as nexus) as this is just expanding the schema by using built-in type systems (and not needing for an enhanced print behavior).

federation: queries for interface types require knowledge of concrete implementations

Version: @apollo/gateway@0.6.6
Reproduction: https://github.com/apollographql/federation-demo/compare/master...lennyburdette:interfaces?expand=1
Related issues: apollographql/apollo-server#2848

I changed the federation-demo to use an interface for the Product type. I could get it to work, but not without some hacks that won't work in the real world.

The Reviews service has to know about Product implementations (Book and Furniture), even though it only knows about Product.id.

For this query:

{
  me {
    reviews {
      product {
        id # doesn't work without https://github.com/apollographql/apollo-server/pull/2848
	name
        price
        inStock
        ... on Furniture {
          shippingEstimate
        }
      }
    }
  }
}

I see this in the query plan:

Fetch(service: "reviews") {
  {
    ... on User {
      __typename
      id
    }
  } =>
  {
    ... on User {
      reviews {
        product {
          __typename
          id
          ... on Book {
            __typename
            id
          }
          ... on Furniture {
            __typename
            id
          }
        }
      }
    }
  }
},

Because the Gateway fetches __typename, I have to implement Product.__resolveType (in a gross way.)

This is often very hard and sometimes impossible, depending on how much information the service stores. Reviews doesn't know anything about Products other than the id, so there's no way for it to return the correct __typename.

Ideally, the query to Reviews would be:

{
  ... on User {
    reviews {
      body
      product {
        # ideally we wouldn't subselect __typename here so that the Reviews service
        # doesn't have to care about interface implementations
        id
      }
    }
  }
}

This is less of a problem for the Inventory service because:

  • We use the ids from Reviews to call the Products service, which can return __typenames.
  • We fetch Inventory fields using references ({ __typename: "Furniture", id: "1" }), so Inventory can just return the given __typename.

(There's also some unfortunate redundancy in Reviews and Inventoryβ€”we have to repeat __resolveReference functions for each interface implementation, but I can solve that on my side.)

Federation + Dataloaders/batching

Feature request

Federation looks really nice, but according to the docs there doesn't seem to be a way to incorporate dataloaders into it and there is no mention of federation implementing any kind of batching and how it would do that. Currently we have special batch queries used in schema stitching to efficiently load a long list of items, so how would this be done in federation?

Federated metrics doesn't report parse or validation errors from the backend

The extensionStack.format() call is not invoked when there are parse or validation errors, so no federated trace is provided to gateway.

The errors are still returned to gateway outside of the trace, but are treated as downstream errors so they are only sent to the end user, not in the reported trace.

We could fix this by calling format() more often, or by not skipping downstream parse/validation errors (if we can detect them).

[Federation] invalid query is sent to federated service when original query contains aliases with fragments

When sending a query like

query Test {
  node(
    nodeId: "WyJ0ZXN0IiwiMSJd"
  ) {
    ... on Device {
      name
    }
    ... on Catalog {
      name: title
    }
  }
}

the following invalid query is forwarded by the gateway to the federated service:

{ node(nodeId: "WyJ0ZXN0IiwiMSJdOGE0Il0=") { __typename ... on Device { name } ... on Catalog { name } } }

This should be

{ node(nodeId: "WyJ0ZXN0IiwiMSJdOGE0Il0=") { __typename ... on Device { name } ... on Catalog { name: title } } }

So the information that a field title is being queried that should be renamed to name is lost. Instead, the field name is being queried directly (which leads to an error in my case, since my type Catalog does not have a field name).

This seems to only happen if there is a second interface there (in this case: Device) that reads a field that really has the aliased name.

Query planner duplicate calls to services on interface types

Hello,

Packages: apollo-gateway v0.11.6

I'm running a microservice architecture based on the federation specs with apollo-gateway at the helm. One of the microservices defines a BasePost interface type, that is implemented by RegularPost and FeaturedPost, while another microservices defines a User type.

When I run a query to fetch blogs, their posts and authors, I get duplicated calls to the users microservices due to the BasePost interface. See the query planner below:

QueryPlan {
    Sequence {
      Fetch(service: "blogs") {
        {
          blogs {
            id
            posts {
              __typename
              ... on RegularPost {
                id
                author {
                  id
                  __typename
                }
              }
              ... on FeaturedPost {
                id
                author {
                  id
                  __typename
                }
              }
            }
          }
        }
      },
      Parallel {
        Flatten(path: "[email protected][email protected]") {
          Fetch(service: "users") {
            {
              ... on User {
                __typename
                id
              }
            } =>
            {
              ... on User {
                givenName
              }
            }
          },
        },
        Flatten(path: "[email protected][email protected]") {
          Fetch(service: "users") {
            {
              ... on User {
                __typename
                id
              }
            } =>
            {
              ... on User {
                givenName
              }
            }
          },
        },
      },
    },
  }

Given the author has the same fields between the implementing types, I would expect a single Fetch to the users microservice. Is this behaviour normal?

Thanks!

Inherit interface resolvers in Apollo Federation

Is there a way to use interface resolver inheritance with Apollo Federation?
I've found inheritResolversFromInterfaces: true which is an option for makeExecutableSchema. I couldn't find a way though to apply this option to a federated schema.

[Federation] Authorization with Database per service pattern

Hello!

I have:

  1. Gateway
  2. Customer microservice(sign in, sign up, and so on)
  3. Some other microservices

After signing in user have a token and starts making requests to other microservices.
The main problem is to get context with user role and RBAC rules - access to customer database have only Customer microservice.
So I need to send a request to Customer microservice to get context (how could I do it?)
After that, I have two variants of how to validate access to queries and mutations:

  1. On service level by sending context to microservices
  2. On Gateway level before sending requests

How would you recommend to organize this scheme?

Federation: how to forward request headers to implementing services

According to https://www.apollographql.com/docs/apollo-server/api/apollo-gateway/#willsendrequest-requestcontext-request-graphqlrequest-context-recordstring-any--promisevoid, I wrote the following code

class DataSource extends RemoteGraphQLDataSource {
    async willSendRequest({ request, context }) {
        console.log('will', request.http.headers);
    }
}

However, the only headers that's printed out are

'Content-Type': [ 'application/json' ]

Other headers passed by clients are not there.

I tried to search in the forum but didn't find a meaningful discussion of this issue.

buildFederatedSchema throws when a directive uses an input type

I've tried replacing the input type with a scalar and an enum, they both work fine, it's only inputs that are crashing.

Reproducible example:

const {gql} = require('apollo-server');
const {buildFederatedSchema} = require('@apollo/federation');

const typeDefs = gql`
    directive @someDirective(input: SomeInput) on FIELD_DEFINITION

    input SomeInput {
        key: String
    }
`;

buildFederatedSchema(typeDefs);

Package versions:
0.11.2 - @apollo/federation
2.9.15 - apollo-server
14.5.8 - graphql

Invalid query plan with nested @requires crossing service boundaries

Consider the schema from the federation demo. Suppose we want to hide (or otherwise modify) the reviews of any product that's out of stock.

What we want to do, ideally, is change the reviews field to @requires the inStock field:

extend type Product @key(fields: "upc") {
  ...
  reviews: [Review] @requires(fields: "inStock")
}

We can't do this: you can't @requires a field that's not defined in the service that owns the type, and inStock is an extension from inventory, whereas products owns the Product type. (Ideally, we could! But that's another topic for another thread.)

We might try to work around this limitation by wrapping up the stock data in a separate type, owned by inventory. So instead of the inStock field, we'd have in inventory:

type StockData @key(fields: "productUpc") {
  productUpc: String!
  inStock: Boolean
}

The products service has to provide some glue:

type Product @key(fields: "upc") {
  ...
  stockData: StockData
}

Now, we can add our @requires:

extend type Product @key(fields: "upc") {
  ...
  reviews: [Review] @requires(fields: "stockData { inStock }")
}

(I've omitted some @external declarations we'd have to add, for brevity.)

This schema validates successfully. But the query we want to write fails at runtime:

{
  topProducts {
    reviews {
      body
    }
  }
}

If we look at the query plan, we can see why:

Query Plan
QueryPlan {
  Sequence {
    Fetch(service: "products") {
      { 
        topProducts {
          __typename
          upc
          stockData {
            inStock
          }
        }
      }
    },
    Flatten(path: "topProducts.@") {
      Fetch(service: "reviews") {
        { 
          ... on Product {
            __typename
            upc
            stockData {
              inStock
            }
          }
        } =>
        { 
          ... on Product {
            reviews {
              body
            }
          }
        }
      },
    },
  },
}

Apollo ends up asking the products service for inStock:

{ 
  topProducts {
    __typename
    upc
    stockData {
      inStock
    }
  }
}

Which, of course, it doesn't have.

If this sort of @requires is not allowed, apollo should validate for it and document it! (I'm guessing the rule would be to extend "you can only require fields that live on the original type defijnition" to add "and nested fields within the same service".) Or -- much better for us -- it should work.

I've posted a full running example: Send the above query and you'll see the error.

See also my thread on Spectrum with more about the limitation I'm trying to sneak around.

federation: resolveType of extended interface without knowledge of concrete type

Our existing services and clients utilize the node interface for object resolution and I haven't been able to figure out a way to implement this with federation

Given the following services:

Product Service

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

  type Product implements Node @key(fields: "id") {
    id: ID!
    status: String
  }
`

const resolvers = {
  Product: {
    __resolveReference({ id }) {
      return { id, status: 'in_stock' }
    },
  },
}

Node Resolution Service

const typeDefs = gql`
  interface Node @key(fields: "id") @extends {
    id: ID! @external
  }

  extend type Query {
    node(id: ID!): Node
  }
`

const resolvers = {
  Node: {
    __resolveType(object) {
      return object.__typename
    },
  },
  Query: {
    node(_, args) {
      const [__typename, decodedId] = Base64.decode(args.id).split('-')
      return { __typename, id: decodedId }
    },
  },
}

I am unable to lookup a product via the node field with its encoded id. Intuitively, this makes sense because the node resolution service is unaware of any types returned from __resolveType. How feasible would it be for __resolveType to allow for arbitrary types if it's extended?

Or am I thinking about this problem in the wrong way - have there been any other ideas for implementing the node interface with federation and gateway?

Mock with Federations - Syntax Error: Unexpected Name "Hello"

When using the '@apollo/federation' and ''@apollo/gateway' with mocked types and with micro server ('apollo-server-micro'). An error occurs on the gateway service:

Encountered error when loading images at http://localhost:4003/graphql: Syntax Error: Unexpected Name "Hello"

Expected behaviour is to properly build the graph and use mocked types.
Actual behaviour, gateway is failing to load the types.

A workaround is to set the option: mockEntireSchema: false,

ApolloServer + geteway disable initial request, or handle auth error

it appears that ApolloServer + ApolloGeteway will make an initial request to each endpoint.

in a scenario where we need to get authorization token from a user request, this initial call will fail.

in this case, the first call fails, and ApolloServer no longer sends any requests to that url.

would it be possible to either avoid the first request, or send it somewhere else (when auth token is not available) ? alternatively maybe we can handle the error so that future request still go out

const gateway = new ApolloGateway({
    serviceList: [
        { name: 'svc1', url: svc1url }, {...}
    ],
    buildService({ name, url }) {
       return new AuthenticatedDataSource({ url });
    },
  });

const server = new ApolloServer({ 
    gateway, 
    subscriptions: false, 
    context: (ctx:ExpressContext) => {
        return {auth: ctx.req.header('authorization')}
    }
 });

Opentracing support for @apollo/gateway

HI. Is there any support opentracing in apollo federation? I know about apollo federated traces, but It will be nice to see traces (in opentracing standard) between apollo gateway and underlying graphlq services.

Multi-tenancy for Apollo Federation

I got very excited when I learned about Apollo Federation.

One huge use case that I see for Apollo Federation is to allow multi-tenancy for the Apollo Gateway. This would mean that external stakeholders have the ability to dynamically register their federated service and make customizations to an existing schema, but only within their own realm.

Think of a cloud enterprise service software company that wants to allow its customers and partners to extend its "common data model" dynamically, without disrupting other tenants.

I just got started with Apollo Federation, so please let me know if this is already supported or does not make sense in any way.

From my understanding of Apollo Federation, this could be possible deploying one gateway per tenant that treats a central gateway server as a federated service, plus all the custom federated services the customer wants to register. However, wouldn't be true multi-tenancy.

Screen Shot 2019-08-30 at 2 57 33 PM

To make operations and life cycle management easier, it would be nice if this could be achieved within one single Apollo Gateway and so-called "Realms". Conceptually (with static configuration), this could look like this:

const gateway = new ApolloGateway({
  globalServiceList: [
    { name: 'hr', url: 'http://...' },
    { name: 'finance', url: 'http://...' },
  ],
  realms: [{
    name: 'tenant1',
    serviceList: [{
      name: 'hr-extension', url: 'http://...'
    }, {
      name: 'finance-extension', url: 'http://...'
    }]
  },
  {
    name: 'tenant1',
    serviceList: [{
      name: 'hr-extension', url: 'http://...'
    }, {
      name: 'finance-extension', url: 'http://...'
    }]
  }]
});

Together with dynamic realm registration at runtime:

Screen Shot 2019-08-30 at 2 57 37 PM

Have there been thoughts in this direction already?

[Federation Feature Proposal]: Pass data to entity resolvers without exposing the data publicly.

At Indeed we've had good success using Apollo Federation across a few teams. We're excited about the benefits here and expect to broaden adoption in the coming months.

As we've on-boarded new teams we've consistently encountered a problem providing the data necessary to federated services via entity loaders. In order to retrieve entities, sometimes data that we would not otherwise expose externally is required. If we consider the set of services that a federated GraphQL API replaces this is not a surprise. An RPC call to a backend service may include data that is not exposed to external API consumers.

The @required directive is sufficient to solve this problem in some situations. However, this data sometimes does not belong on the extended type. In some cases this data is private and should not be exposed externally.

One workaround here is to forgo creating some Federated Services and communicate with backend services directly. The data would then be exposed as a type by one or more Federated Services. This path has the disadvantage that we are less able to create small focused services and instead have more monolithic GraphQL servers.

Another workaround we've employed here is to encode data into the key field of the entity. The Federated Service that owns the extended type creates the encoded key value. The federated service that owns the data and implements the entity loader decodes the key value in order to load entities. The two services agree on the format of the key. Though this workaround gets the job done, it requires coordination across federated services and is error prone. It is also limited in that 1) keys can be large and 2) encoding protected data requires encryption.

An ideal solution to this problem would:

  1. be declarative - Federated services exposing entities for use as extensions should have the ability to describe the inputs required. External manual coordination should not be necessary.
  2. not affect the Graph in a negative way - There should be no artifacts of the integration across schema exposed publicly. For example, it should not be necessary to expose fields or types externally to accomplish a schema extension.
  3. protect private data - It should not be necessary to expose private data to facilitate type extensions.

I'd like to propose a solution that ticks many of these boxes and looks like a reasonable way to address this deficiency.

  1. Introduce a new directive to mark fields and types as private.
    directive @Private on FIELD_DEFINITION | OBJECT

  2. Hide data and schema marked with the @Private directive at the federation server.
    a. Exclude private data from responses.
    b. Exclude private types and fields from introspection query responses.

This would be used as follows:

  1. Add fields and types to the extended schema to expose the data required by the entity loader.
  2. Mark appropriate fields and types with the @private directive.

The following modified Product with shipping estimate example illustrates the concept:

// Product Schema
type Product @key(fields: "upc") {
  upc: String!
  name: String
  weight: Int! @private
  sizeClass: Int! @private
}

// Shipping schema
extend type Product @key(fields: "upc") {
  upc: String! @external
  weight: Int @external
  sizeClass: Int! @external
  shippingEstimate: Int @requires(fields: "weight sizeClass")
}

And the following illustrates the concept with a private type:

// Product Schema
type ProductSpecifications @private {
  weight: Int!
  sizeClass: Int!
}

type Product @key(fields: "upc") {
  upc: String!
  name: String
  productSpecifications: ProductSpecifications!
}

// Inventory / Shipping Schema
extend type Product @key(fields: "upc") {
  upc: String! @external
  weight: Int @external
  price: Int @external
  inStock: Boolean
  shippingEstimate: Int @requires(fields: "productSpecifications")
}

This solution does not define the inputs required by the entity loader in a declarative way. Although it would be very useful to do so, this solution addresses the most important parts of the problem I've expressed here.

federation: node-fetch in @apollo/gateway doesn't support unix: urls

Version: @apollo/gateway@0.6.6

Description:
We use an envoy sidecar process to manage service-to-service routing. All requests go to the same unix socket and we add headers to tell envoy which service cluster to route to.

const gateway = new ApolloGateway({
  serviceList: [
    { name: 'service-a', url: `unix:/path/to/envoy.sock:/graphql` },
    { name: 'service-b', url: `unix:/path/to/envoy.sock:/graphql` },
  ],
  buildService({ name, url }) {
    return new RemoteGraphQLDataSource({
      url,
      willSendRequest({ request }: any) {
        request.http.headers.set('Host', `${name}.specialenvoyheader`);
      }
    });
  }
});

Currently, this cannot work because getServiceDefinitionsFromRemoteEndpoint in @apollo/gateway uses node-fetch, which throws this error: Only HTTP(S) protocols are supported.

We use unix sockets because we can chown the socket and restrict access to it, unlike a tcp port. If we used TCP we'd have to also use TLS, which is overhead and complexity we'd like to avoid.

Adding support for unix sockets in node-fetch is marked as "wontfix".

I would suggest the got library, which has the same API, but it has 58 transitive dependencies compared to node-fetch's zero. 😬

RFC: Cache entities during request if keys match across services

Since the idea behind keys in federation is that they represent a unique identity, over the course of a single request (for now), we should be able to put potential cache points into the query plan to be evaluated during a request. Take for example a request that goes to the same services at different levels in the tree:

{
  topProducts{
    name
    weight
    reviews{
      body
      product {
        name
        weight
      }
    }
  }
}

If the result of topProducts brings back entities { __typename: "Product", sku: "1234" }, { __typename: "Product", sku: "4567" } and the result of reviews is the same result, instead of refetching name and weight from the products service again, we can use the cached fields and return right away.

This may be a mirco optimization because if there are any additional fields we still need to make a fetch to that service. We could perhaps filter out cached fields in that case though to prevent potentially expensive fields from being done again.

Would this be a suprising set of behavoirs? It matches what the client would do when normalizing deeply nested entities with key matches so the data will be merged together at some point in the experience.

Hide fields from public federated gateway

Currently with schema stitching we can use the transformSchema to hide some root fields from the public graphql endpoint, we have some queries and mutations that are being used by cronjobs and scheduling services currently that shouldn't appear in the public graphql endpoint, they're supposed to be used only internally.

Currently we're using a function like this in the gateway:

const removeHiddenFields = (schema, hiddenFields) => transformSchema(schema, [
  new FilterRootFields(
    (operation, rootField) => hiddenFields.indexOf(rootField) === -1
  )
])

// usage example
removeHiddenFields(schemas.notifications, ['scheduledNotifications'])

Could we have a directive for this, maybe? @private for example, this way we can filter which fields will be available to the public.

Tracing breaks query plan viewing

If tracing is enabled, then the query plan isn't returned by the gateway.

Steps to reproduce

  1. Clone https://github.com/apollographql/federation-demo;
  2. Change gateway.js#L16 to const server = new ApolloServer({ schema, executor, tracing: true });;
  3. The query plan doesn't appear anymore on Playground.
Other issues

Tracing only shows the request and response ms, it's not displaying per field, query, type and so on =/

Cache control hints doesn't work on extended types on federated schemas

Packages

{
    "@apollo/federation": "^0.11.0",
    "apollo-server": "^2.9.9",
    "apollo-server-plugin-response-cache": "^0.3.5"
}

Issue

It seems like cache control hints doesn't work on extended types on federated schemas

Having these schemas in two different federated services

# This is defined on the users service
type User @key(fields: "id") {
  id: ID!
  username: String
}
extend type Query {
  me: User @cacheControl(maxAge: 60)
}
# This is defined on the organizations service
type Organization @key(fields: "id") {
  id: ID!
  name: String
}
extend type User @key(fields: "id") {
  id: ID! @external
  currentOrg: Organization @cacheControl(maxAge: 60)
}
extend type Query {
  currentOrg: Organization
}

and running this query

query {
  me {
    id
    username
    currentOrg {
      id
      name
    }
  }
}

results on the me query response being cached but not the one from me.currentOrg.

I also tried setting info.cacheControl.setCacheHint({ maxAge: 60 }) on both resolvers and the result is the same.

Expected behavior

The response from me and me.CurrentOrg are cached

Actual behavior

Only the response from me is cached

Steps to reproduce

I created a repo that reproduces this behavior here https://github.com/gnkz/apollo-federation-cache

Federation relation filtering and arguments problems (federation design concerns / feature request)

Hello everybody.

I'm building my own gateway on top of federation protocol (specific needs that can't be handled by apollo's gateway) so digging up things deeply and seeing some problems there.

For resolving relations __resolveReference is used. It is designed to return one and only one entity (throws error otherwise). From my perspective this is highly uneficient by itself, making gateway to produce enormous batch queries for fetching collections and/or multiple related entities and produces overcomplication on service side with dataloaders and complex stuff to optimize N+1 queries.

But ok, uneficient and complex, but has some pros (like batching from multiple sources) and can be handled (#2887).

Where real problem is - it makes literally impossible to filter and paginate queries. While some filtering can be done (btw will relation field arguments be passed down by gateway to _entities query?) in form of constrains, null-returns and filtering (again, increased complexity), pagination β€” no deal.

Usecases are obvious. We have a user in users service and user has posts in posts service. User has hundreds of posts and I want to render his public feed. I need filters (status: public) and pagination (cursor or take/skip based) for this. And now there is no proper way to do this as __resolveReference has no idea of collection, it works with only one entity.

Ok, one could say that users service should denormalise posts and keep fields needed for filtering and pagination at it's side, but this can work only for simple cases without complex filtering. If I want to fetch all users's posts that are public, published at specific location, with friends tagged and containing photos? "denormalise" entire post? No, filtering logic belongs to service hosting the entity.

Solution is fairly obvious at first glance: __resolveReference should be able to return arrays and act somehow like this.

extend type User @key(fields: "id") {
    id: ID! @external
    posts(where: PostsWhereInput, take: Int, skip: Int): [Post!]!
}
const resolvers = {
  Post: {
    __resolveReference(posts, arguments) {
      return products.where({id_in: posts.map(({id})=>id), ...arguments.where}, first: arguments.take, skip: arguments.skip); // and this returns array
    }
  },
}

Or maybe a separate resolver alike __resolveReferenceMany (but looks kinda ugly).

Maybe this can't work with current internal logic of gateway and needs huge rework. But honestly this is a big concern for building really complex graphs.

Your thoughts?

Federation for existing schemas: graphql-transform-federation

Sometimes a rebuild to use graphql federation is not possible / desirable. I'm working on a schema transform that allows you to add federation to any existing schema: graphql-transform-federation. The idea is that you add the information required for federation and the transform will ensure the schema conforms to the federation API.

I was wondering whether this approach aligns to with your ideas.

  1. Are you interested in adding something like this to buildFederatedSchema? buildFederationSchema(schema, config?: FederationConfig)
  2. What do you think of this approach compared to apollographql/apollo-server#3013 that relies on adding non-standard functions to GraphQL types

Other issues that could be solved with this:
prisma-labs/graphql-framework-experiment#148
MichalLytek/type-graphql#351

Apollo federation willSendRequest for schema lookup

Hey, doesn't Apollo federation use the willSendRequest function when getting the schema? We have a service that requires a auth token for all queries/mutations. We have implemented the willSendRequest function to set the auth token, however, this doesn't seem to work when looking up the schema. The lookup and querying works fine for other services when not all queries requires auth.

Example of willSendRequest:

new ApolloGateway({
  buildService: ({url}) => {
    return new RemoteGraphQLDataSource({
      url,
      willSendRequest: ({
        request,
        context,
      }: {
        request: GraphQLRequest
        context: any
      }) => {
        request.http.headers.set(
          'authorization',
          context.req.headers['authorization']
        )
      },
    })
  },
  serviceList: [
    {
      name: 'userService',
      url: 'http://localhost:3000',
    },
    {
      name: 'productService',
      url: 'http://localhost:4000',
    },
    {
      name: 'reviewService',
      url: 'http://localhost:5000',
    },
  ],
})

Thanks!

Apollo Federation/Gateway doesn't support Cache Control

It doesn't look like @apollo/[email protected] and @apollo/[email protected] support @cacheControl directives or cache control hints.

Related: apollographql/apollo-server#3181

Expected

Using static or dynamic cacheControl hints will result in cache-control http headers.

Actual

No headers are sent, and the hints are empty when cacheControl: true in ApolloServer.

Code Example:

import { gql } from 'apollo-server'

const typeDefs = gql`
    type Test @key(fields: "id") @cacheControl(maxAge: 60, scope: PRIVATE) {
        id: ID!
     }
`;

export { typeDefs }

Error: UnhandledPromiseRejectionWarning: GraphQLSchemaValidationError: Unknown directive "cacheControl".

Federation: unnecessary query

Let's say you have me query which returns type User defined in Accounts service. The current user can have some reviews which are served from Reviews service.

Let's say current user ID is known based on JWT token in an Authorization header.

When asking for:

me {
  reviews
}

the GQL gateway queries accounts service even though the client does not ask for any attributes from it. This seems unnecessary.

QueryPlan {
  Sequence {
    Fetch(service: "accounts") {
      {
        me {
          __typename
        }
      }
    },
    Flatten(path: "me") {
      Fetch(service: "reviews") {
        {
          ... on User {
            __typename
          }
        } =>
        {
          ... on User {
            reviews {
              body
            }
          }
        }
      },
    },
  },
}

The expected behavior would be that only a single query to Reviews service is sent and no query gets sent to the Accounts service.

Checklist for myself

  • A short, but descriptive title. The title doesn't need "Apollo" in it.
  • The package name and version of Apollo showing the problem.
  • If applicable, the last version of Apollo where the problem did not occur.
  • The expected behavior.
  • The actual behavior.
  • A simple, runnable reproduction!
    Please make a GitHub repository that anyone can clone and run and see the
    problem. Other great ways to demonstrate a reproduction are using our
    CodeSandbox template (https://codesandbox.io/s/apollo-server), or re-mixing
    our Glitch template (https://glitch.com/~apollo-launchpad).

Allow extra fields in serviceList

Package version: apollo-gateway 0.10.8

The config currently allows defining your own subclass of GraphQLDataSource with buildService, but the serviceList field does not allow any objects with fields other than name and url. It would be nice to be able to set extra parameters for these custom data sources.

Apollo Federation - How to share customized types between services, except scalar types and enum

I have two services, there are some types and inputs to share:

  • Service A
input Page {
  limit: Int
  offset: Int
}
type PageInfo {
  page: Int
  page_size: Int
}
  • Service B
input Page {
  limit: Int
  offset: Int
}
type PageInfo {
  page: Int
  page_size: Int
}

When i federate them use apollo-gateway, i got errors like:
There can be only one type named "Page".,There can be only one type named "PageInfo".

What can i do? Is this method is wrong?

[Federation] Pass existing GraphQLSchema to buildFederatedSchema.

Just bringing it to the issues area to give it a few eye-balls, feel free to close if it's not wanted or correct.

apollographql/apollo-server#3013

This PR adds support to decorate existing schemas with federation add-ons. While working on the implementation of federation in type-graphql (MichalLytek/type-graphql#369), I realized that it's sort of a pain and pointless to convert from GraphQLSchema to SDL and back and to SDL again as well as pluck out the GraphQL*Type resolvers when buildFederatedSchema should just be able to take in an already constructed schema and modify it.

why did @apollo/gateway support load balance?

Feature requests?
Please search for an existing feature request before opening a new one.
If there is an existing feature request, use the πŸ‘ to show your support
for it. If the specifics of your use case are not covered in the existing
conversation, please take the time to add new conversation which helps
move the feature's design forward.

As the document mentioned, Apollo gateway need url to specific implementing service where request sent . But the implementing service is running on multi server which has multi IP. So why can't give a list of url to specific a single implementing service which use load balance .

__resolveReference within the same service

Hi. I have been using Apollo Federation and while __resolveReference has worked great for resolving data between microservices, what do we do if we want to use __resolveReference within the same service?

Say there are 3 or 4 types in the same GraphQL service and I want to return ID and use __resolveReference within the same service. I have noticed that it only works when used in some other service.

Any thoughts? Thanks.

[Federated] Services namespaces

Currently I have a system built with microservices and with a self-written gateway that uses schema-stitching and a lot of magic.
This system was written several years ago and now needs a refresh and Apollo Federation has most of features that I've implemented by my own but better designed. Now I want to migrate to Federation but it seems to a problem.

My usecase maybe specific but I think it is not uniquely rare.

In short: services should have namespaces.

Namespaces are needed to prevent type collisions. The idea behind is that services are autonomous, are written by numerious developers, including external ones. And services can (and do) have alike types (e.g. News has Article and Blog has Article).

Controlling types uniqueness "administratively" is not an option due to big amount of services and people involved. Also considering auto-generated CRUD types (e.g. from Prisma) this is becoming even more of a problem. To 100% eliminate type collisions and bring a unified convention to end-users gateway should namespace services.

In practice it looks like this:

Services:

# news service
Query {
   articles: [Article!]!
}
Article {
  id
  body
}
# blog service
Query {
   articles: [Article!]!
}
Article {
  id
  content
}

At the gateway schemas are transforming before stitiching and result is like this:

Query {
   News {
      articles: [NewsArticle!]!
   }
   Blog {
     articles: [BlogArticle!]!
   }
}
NewsArticle {
  id
  body
}
BlogArticle {
  id
  content
}

This approach proven to be convenient with tens or hundreds of services with hundreds or thousands of types and with autogeneration on services level.

Looking at Apollo Federation I don't see a possibility to do this. So the questions:

  1. Maybe there is a possibility to transform service schemas that I've overlooked?
  2. Maybe such a feature should be considered like an option? Something like:
new ApolloGateway({
  serviceList: [
    { name: 'news', url: 'http://localhost:4001', namespace: 'News' },
    { name: 'blog', url: 'http://localhost:4001', namespace: 'Blog' }
  ],
});

Federation: Error when returning Query type from Mutation

Reproduction: https://github.com/ksaldana1/provides-fed-repro/tree/query-root-failure

Issue:
It is common to return the query type inside of a mutation return payload to be flexible to the downstream consumers and the type of fresh data they want from a mutation response. I have tried to implement this using a federated gateway (simple example in repro repo above), and am running into errors at the gateway level (buildQueryPlan.js specifically).

When running a simple query such as:

mutation {
  echo(input: "Hello World!") {
    message
    query {
      kitchen(id: "1") {
        id
      }
    }
  }
}

The gateway is erroring out with "message": "Couldn't find base service for type \"Query\"",. Perhaps I'm missing something in configuration of the root query object, but I was wondering if this functionality should be working.

Thanks again for your hard work!

proposal: making federation's @requires work on extensions from other services

Currently, the federation documentation says

Note that you can only require fields that live on the original type definition, not on type extensions defined in other services.

This issue proposes to remove this limitation. If the maintainers are open to this feature, we (@Khan) are interested in implementing it starting immediately.

Motivation:

Our goal, in implementing this, is to allow our services to coordinate more efficiently while separating concerns appropriately.

More specifically, we have written about how we are planning to use federation for permissions checking. To allow permissions to be owned by a service other than that which owns a type, we need to be able to @requires fields from such a service. This is a very common need for us. Specifically, we have a User type owned by a users service, which handles core data about a user on our site. Some of its fields may be visible to certain administrators -- a status which is owned by a separate permissions service. Other fields may be owned by a teachers service which owns the data mapping which teachers teach which students, such that a teacher can see their students' progress. In both cases, those fields may be defined on the users service, or on another service which extends User, for example the service which owns progress data (i.e. which content you've completed).

Another common use case for us, which would be enabled by this feature, is the use of @requires on toplevel query objects, which is currently impossible since nobody defines the base Query type. Again, for us a use case is permissions: maybe we have a toplevel field actorIsAdmin: Boolean and we want to @requires this for other fields that are only accessible to admins. We can imagine many other use cases.

We're proposing to implement this in Federation because we don't see any good workarounds (and none were suggested in our Spectrum thread). Since the changes are mostly in the query planner, I don't think a plugin makes sense.

Implementation details:

As far as we can tell, no changes to the federation spec, nor to backend servers, are needed: the changes are only in validation, documentation, and query planning. (Some backend servers may need additional functionality to support an @requires on Query, but only the server using the @requires needs to do so. I haven't looked into whether this is the case of Apollo; if this is a blocker for this issue to be accepted we can look into it but we don't have a desire to do so since we use Go for our backends.)

The changes to validation and documentation are simple: we need only prohibit circular @requires directives, that is, those which induce a cycle in the graph of fields which require other fields. Unfortunately, this is a global validation, rather than being type-local, because of nested @requires, but the rule is pretty simple.

The changes to the query planner are somewhat more complex, which is presumably why this was not implemented from day one. In particular, the query planner can no longer assume that to fetch some set of fields of a type it is sufficient to fetch some set of fields from the service which owns it, then, in parallel, fetch some set of fields from services which extend it, potentially including in the query some fields from the owning service.

Instead, the query planner will need to create the directed graph of @requires'd fields (which must be acyclic due to the validation constraint), traverse it assigning the fields to services, and then merge those fields which are owned by the same service and which have no dependency on each other, even transitively. (Later today or tomorrow, I'll write a precise description of this algorithm.) This will significantly increase the complexity of the query planner.

A note on what I've been calling "nested @requires": this iswhen a field f: U on type T to do @requires(fields: "f { g }") where g is a field of U. This is not explicitly documented, but it makes sense by analogy with @key. As far as I can tell, the spec allows it, and Apollo validates and executes the query, so I'm assuming it is supported.

Prior art:

I've written about our desire in Spectrum. I suspect a review of past threads might uncover additional use cases and users.

This would make moot my prior issue (#3399) about a related schema that Apollo validates but cannot query.

Adding JWT authorization to Federated Service stops the gateway from running?

I'm adding JWT authorization on /graphql federated service as follows and consuming the running service in Gateway. However, upon starting the gateway, I'm getting Encountered error when loading at http://localhost:5001/graphql: 401: Unauthorized πŸš€ Gateway ready at http://localhost:5000/graphql

Is there any way I can add JWT authentication to the federated service and still run the gateway seamlessly? I intentionally do not want to apply JWT authorization to the gateway server since not all federated services need it at the moment.

// 5001 federated Graphql service
const jwt = require('express-jwt');
const cors = require('cors')
const jwksRsa = require('jwks-rsa');

const server = new ApolloServer({
    playground: playground,
    schema: federatedSchema,
});
const app = express();
app.use('/graphql', jwt({secret: (req, header, payload, done) => {
    if (payload) {
      jwksRsa.expressJwtSecret({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: `${payload.iss}/.well-known/openid-configuration/jwks`
      })(req, header, payload, done);
    } else {
      return done('UnauthorizedError: Missing Access Token!', null);
    }
  }}), cors());

server.applyMiddleware({ app: app });
  app.listen({ port: 5001 }, () => console.log(`πŸš€ Gateway ready at http://localhost:5001${server.graphqlPath}`));

Multi-level Federation

The idea of composing all the GraphQL services across the organization declaratively is very useful indeed.

Currently, we're evaluating Federation and have stumbled upon an interesting (hopefully) question– What does the team think or have any plan on having multiple levels of federation?

I think the question above can be broken down into two:

  1. Is it something practical or even desirable to have multiple levels of federation? Do other companies see value in it? Or it is just a crazy idea?
  2. If the answer is YES to above, what are the possibilities currently to achieve it in the current form of federation?

To give an example:

           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         
           β”‚Mobile App β”‚             β”‚  Web App  β”‚         
           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         
                 β–²                         β–²               
                 β”‚                         β”‚               
                 β”‚                         β”‚               
                 β”‚                         β”‚               
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   
     β”‚Mobile Graph (Gateway)β”‚   β”‚  Web Graph (Gateway) β”‚   
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   
         β–²         β–²      β–²        β–²      β–²        β–²       
         β”‚         β”‚      β”‚        β”‚      β”‚        β”‚       
         β”‚         β”‚      β”‚        β”‚      β”‚        β”‚       
         β”‚         β”‚      β”‚        β”‚      β”‚        β”‚       
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” 
   β”‚Mobile BFF β”‚   β”‚   β”‚  Common BFF  β”‚   β”‚  β”‚  Web BFF  β”‚ 
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ 
                   β”‚                      β”‚                
                   β”‚                      β”‚                
                   β”‚                      β”‚                
                   β”‚                      β”‚                
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 Domain Graph (Gateway)                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      β–²              β–²              β–²               β–²      
      β”‚              β”‚              β”‚               β”‚      
      β”‚              β”‚              β”‚               β”‚      
      β”‚              β”‚              β”‚               β”‚      
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Accounts  β”‚  β”‚  Reviews  β”‚  β”‚  Payment  β”‚   β”‚   Users   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Where,

  • Accounts, Reviews, Payments, Users etc are the core services. The Domain Graph could be exposed to other services that do not need to worry about the UI part of the schema.
  • BFFs hold schema that is more close to the UI/Product of the application. It is federated by the Domain Graph, Common UI schema, and the app-specific schemas (maintained by respective teams).

I hope the explanation was clear enough. Happy to answer questions.

Gateway throws error when federated schema contains Subscription type

  • Package: @apollo/gateway (but the error is caused by @apollo/federation)
  • Version: since @apollo/federation v0.9.1
  • Last known working version: v0.6.x

When a federated service's schema contains a Subscription type and it's loaded by the Apollo Gateway, the following error is returned:

GraphQLSchemaValidationError: [accounts] Subscription -> `Subscription` is an extension type, but `Subscription` is not defined

I understand Subscriptions aren't supported by Federation yet, but having them in the downstream service shouldn't affect the creation of the federated graph.

While debugging the problem, I realized the Gateway was processing the Subscription as an extension type (the same way it does with Query/Mutation), but it's not adding an empty definition type for Subscription.

To reproduce it, just run the federation demo adding a Subscription to the accounts service:

const typeDefs = gql`
  extend type Query {
    me: User
  }

  type User @key(fields: "id") {
    id: ID!
    name: String
    username: String
  }

  type Subscription {
    somethingHappened(input: String!): String!
  }
`;

Fork including the change above: https://github.com/jsangilve/federation-demo/blob/wip/example_with_subscription/services/accounts/index.js

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.