Can we run Apollo federation along with the monolithic GraphQL server

Hello,
We have a monolithic GraphQL server and now we are trying to migrate to Apollo federation but we want to do one service at a time so we were wondering if we can have a single Apollo server that can serve traffic to both monolithic server and to the federated server.
Federation document does have a section about incremental approach(Introduction to Apollo Federation - Apollo GraphQL Docs) but we are not sure if we can have production traffic with an Apollo gateway serving traffic to both instances.

Thanks

If you have native and web apps using the existing monolith, I think the best option is to set up a gateway service with the same endpoint as your monolith, update the schema document in your monolith to use federation architecture (extend type query). With these two pieces in place, your production traffic should now go through the gateway and then hit your monolith (which is now a federated service).

Once you have the above, you can start migrating types/queries from your monolith into different federated services. Here’s a hellpful guide for migrations - Entities in Apollo Federation - Apollo GraphQL Docs

Hi kartik_gujarati!

Thanks for the response, I was hoping not to migrate our entire graph to use federation architecture ( extend type query ). Our graph is quite big so we wanted to migrate only a part of our graph to use federations to see the gains we get from it before we migrate the entire graph. That’s the reason we wanted a way to serve traffic to our monolith and federated service based on the request with the help of gateway or without.

Please let me know if this is something that we can do out of the box.

Thanks

If you want to maintain both federated server and the monolith at the same time, one option I can think of is to setup the federated server gateway with a different endpoint and have the newer versions of your clients talk to the new federated services through gateway’s endpoint while continuing to support older clients through the monolith. You can use a phased rollout or feature flagging approach to safely rollout the switch-over.

Got it, Thanks @kartik_gujarati for all the help.

@Nagarchith_Nama_Bala We recommend having a single endpoint. If you put a Gateway in front of a single existing GraphQL service, you don’t need to use federated features and directives (e.g., extend types are native to GraphQL, but you don’t even need to use them) you should find success and be able to avoid the separate endpoint and be in the best position to start adding new services.

@abernix Thanks for that suggestion but even with a gateway in front of our existing service, I don’t seem to find a way to serve both my legacy/monolith graph and the federated graph at the same time via the gateway. Can you please point me to any documentation that can help me with this? We already have migrated part of our graph over to use federation but we wanted to test it out in production before switching the rest over. During this testing phase we want to run both of our graphs/servers to not cause any issues to our client apps.

1 Like

@abernix We have a similar situation. We’re starting with a very small set of services, and there’s some resistance to adding a network hop (even if on same host) to reach out to a subgraph.

We definitely want to be structured properly for some point down the line when we add more services however, and as a result want to have ourselves set up correctly for Federated Graphs even if initially we’re not federated. – Any thoughts?

If you really really really want to avoid the network hop, you can run a gateway and a monolith GraphQL API in the same process.

import { ApolloGateway, LocalGraphQLDataSource, RemoteGraphQLDataSource } from '@apollo/gateway';

// your current graphql schema
const monolithSchema = makeExecutableSchema({ ... });

const gateway = new ApolloGateway({
  buildService({ name, url }) {
    if (name === 'monolith') {
      return new LocalGraphQLDataSource(monolithSchema);
    } else {
      return new RemoteGraphQLDataSource({ name, url });
    }
  }
}); 

When using managed federation, you can publish your monolith schema as a subgraph. It can be your only subgraph until you start breaking up the monolith.

(This works only if you’re doing everything in JavaScript/TypeScript today, of course.)

That being said, don’t fear the extra network hop! It’s minimal with a well-tuned Node.js server.

3 Likes

@lennyburdette We don’t use managed federations for our graph. I do see any error that our monolith schema does not support federation specifications.

All GraphQL requests will now fail. The startup error was: Couldn’t load service definitions for “monolith” at http://localhost:4000: Cannot query field “_service” on type “Query”.

We use type-graphql(https://github.com/MichalLytek/type-graphql/blob/master/docs/bootstrap.md#create-typedefs-and-resolvers-map) for our graphs so I updated our schema generation step to

import { makeExecutableSchema } from "graphql-tools";
import { buildTypeDefsAndResolvers } from 'type-graphql';
const { typeDefs, resolvers } = await buildTypeDefsAndResolvers({
   resolvers
 }); 
const schema =  makeExecutableSchema({ typeDefs, resolvers });

// Gateway code
const serviceList = [
    { name: "test", url: 'http://localhost:5000'  },
    { name: "monolith", url: 'http://localhost:4000' },
];

const monolithSchema = await schema();

  const gateway = new ApolloGateway({
    serviceList,
    buildService: ({ name, url }): any => {
      if (name === 'monolith') return new LocalGraphQLDataSource(monolithSchema);
      return new RemoteGraphQLDataSource({ name, url });
    },
  });

We don’t want to migrate our monolith graph to federation specification right now. So let me know if I need to modify something for it to work without a network hop.

Any help will bee much appreciated. Thanks

This is helpful, thanks! If you don’t want to add the federation spec to your monolith schema, or start using managed federation, then you have one more option: static composition with rover.

Instead of using serviceList to have the gateway fetch subgraph schemas and compose them at runtime, you can create a “supergraph schema” with a config file and a command:

subgraphs:
  monolith:
    routing_url: "doesnt matter, this will be a local graphql data source"
    schema: 
      file: monolith.graphql
  test:
    routing_url: http://localhost:5000
    schema:
      file: test.graphql
rover supergraph compose --config config.yaml > supergraph.graphql

Then remove the serviceList argument:

const gateway = new ApolloGateway({
  supergraphSdl: readFileSync('supergraph.graphql', 'utf-8'),
  buildService() { ... }
});

I’d still very much recommend managed federation in production so that the gateway can update to new versions of the subgraph schemas automatically. But this pattern works great in development.

1 Like

@lennyburdette Thanks for that suggestion but looks like rover doesn’t support merging/composing a federated schema with a monolithic schema. I am looking for another library that can help me merge these schemas but haven’t had any luck so far.

error: UNKNOWN: Unknown type “userName”.

We are planning to just go with an additional network hop for now to speed up our development.

That surprises me—I’ve gotten it to compose various kinds of schemas before—but I guess I’d have to see your schemas to debug. But :+1: to using the gateway as intended.

On a side note, regarding “additional network hop for now”, if your gateway and monolith (or federated) services are deployed in the same cluster (assuming K8s setup), this network hop latency should not be of concern. You can use query tracing to find more details about the query resolution timing.

1 Like

Here’s how we’re doing it:

  • we have an existing monolith service (REST)
  • We’ll create an accounts service which will have federation enabled
  • In a feature flag, we’ll migrate to account service which is inside a federated gateway
  • Slowly we add more services inside our one graph.
  • At any point during the rollout, if something goes wrong, we turn off the feature flag
  • At the end of everything we should have all requests coming to gateway and no request going to REST API
  • At which point we’ll archive the github repository and remove deployments related to that project.

I don’t know if that’s something you can try, but I think it’s very safe given you’re using feature flags and you have rollback strategy.

Here is an example of the types of schemas we would want to support.

This first one represents a legacy monolith that we are replacing with federation. We want to keep it intact as a monolith so we can roll back to it, split traffic to it when deploying the new service(s), etc. We would prefer to make minimal, if any changes to it:

type Query {
  commonFunction(foo: Int!): Boolean
  aThing: Thing
  anotherThing: AnotherThing
}

type Thing {
  name: String!
}

type AnotherThing {
  name: String!
}

The next one represents our first subgraph, which we want to run in federation with the legacy service:

extend type Query {
  commonFunction(foo: Int!): Boolean
  aThing: Thing
}

extend type Thing {
  name: String!
}

Later services will be developed to replace the rest, and the monolith will be retired.

The config file for Rover looks like:

subgraphs:
  subgraph1:
    routing_url: https://subgraph1.example.com
    schema:
      file: ./subgraph1.graphqls
  legacy:
    routing_url: https://legacy.example.com
    schema:
      file: ./legacy.graphqls

When running rover supergraph compose --config ./federate.yml, we get:

error[E029]: Encountered 3 build errors while trying to build a supergraph.

Caused by:
    Encountered 3 build errors while trying to build the supergraph.
    UNKNOWN: Field "Query.commonFunction" can only be defined once.
    UNKNOWN: Field "Query.aThing" can only be defined once.
    UNKNOWN: [subgraph1] Thing.name -> Field "Thing.name" already exists in the schema. It cannot also be defined in this type extension. If this is meant to be an external field, add the `@external` directive.
    
        The subgraph schemas you provided are incompatible with each other. See https://www.apollographql.com/docs/federation/errors/ for more information on resolving build errors.

We have tried sprinkling @external around, but that makes thing worse, actually.

:wave: thanks for this example!

In Fed 1, it’s not possible for two graphs to provide the same field — that the error you’re running up against. I tried to illustrate the full field migration workflow in my GraphQL Summit talk last year: Change Management with Apollo Federation - Promo - YouTube … please let me know if I can elaborate on this!

Fortunately, we’re making this much easier in Fed 2. You’ll be able to provide the same field in multiple subgraphs, and we’re designing some new directives to inform the query planner which subgraphs to use for resolution. We’re tracking this work in this github issue: Add a mechanism to force query-planner choices (tentatively `@override`) · Issue #1177 · apollographql/federation · GitHub

We’re also trying to migrate from a monolithic schema (implemented with plain graphql-js) to a federated schema.

We tried the solution above (Can we run Apollo federation along with the monolithic GraphQL server - #11 by lennyburdette) with LocalGraphQLDataSource

subgraphs:
  monolith:
    routing_url: "doesnt matter, this will be a local graphql data source"
    schema: 
      file: data/schema.graphql
const gateway = new ApolloGateway({
  supergraphSdl: readFileSync('./data/supergraph.graphql', 'utf-8'),
  buildService() {
    return new LocalGraphQLDataSource(monolithicSchema);
  },
});

However, when we make queries, we get a 404 error:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot POST /graphql</pre>
</body>
</html>

I suspect this is because the monolithicSchema is not a proper federation subgraph. Is this expected to work?

If we need to migrate the monolithic schema, what is the best path to achieving that? We want something along the lines of

const monolithicSchema = new GraphQLSchema({
  query,
  mutation,
  // ...
});

const { USE_FEDERATION } = process.env;

let apolloServer;
if (USE_FEDERATION) {
  // !!! This doesn't work; buildSubgraphSchema doesn't accept a GraphQLSchema object
  const schemaAsSubgraph = buildSubgraphSchema(monolithicSchema);
  const supergraphSdl = readFileSync('../data/supergraph.graphql').toString();

  const gateway = new ApolloGateway({
    supergraphSdl,
    buildService() {
      return new LocalGraphQLDataSource(schemaAsSubgraph);
    },
  });

  apolloServer = new ApolloServer({
    gateway,
    // ...
  });
} else {
  apolloServer = new ApolloServer({
    monolithicSchema,
    // ...
  });
}

What should replace the buildSubgraphSchema(monolithicSchema) call?

Actually, the above works–I had just generated supergraph.graphql incorrectly. Once I fixed that, I was able to issue a basic query.

What is the migration path for subscriptions in the monolithic server? We have type Subscription declared in the monolithic schema, but when I run rover supergraph compose --config ./supergraph-config.yaml > ./data/supergraph.graphql, I get the following error:

error[E029]: Encountered 1 build error while trying to build a supergraph.

Caused by:
    Encountered 1 build error while trying to build the supergraph.
    UNKNOWN: [whatever] Subscription -> `Subscription` is an extension type, but `Subscription` is not defined in any service
    
        The subgraph schemas you provided are incompatible with each other. See https://www.apollographql.com/docs/federation/errors/ for more information on resolving build errors.