Skip to content

Commit

Permalink
Gateway support via new @apollo/server-gateway-interface package (#6771)
Browse files Browse the repository at this point in the history
Until now, the refactored AS4 did not support Apollo Gateway (or any
implementation of the AS3 `gateway` option). That's because
`GraphQLRequestContext` is part of the API between Apollo Gateway and
Apollo Server, and that type has changed in some minor but incompatible
ways in AS4.

(Additionally, clashes between `declare module` declarations in AS3 and
AS4 caused issue, but we removed those declarations from this branch in
PRs #6764 and #6759.)

This commit restores gateway support. It does this by having AS4 produce
an AS3-style request context object. It uses a new
`@apollo/server-gateway-interface` package to define the appropriate
types for connecting to Gateway.

(Note: this package will be code reviewed in this PR in this repo, but
once it's approved, it will move to the apollo-utils repo and be
released as non-alpha there. Once we've released AS4.0.0 we can move
that package back here, but trying to do some prereleases and some
non-prereleases on the same branch seems challenging.)

This PR removes the top-level `executor` function, which is redundant
with the `gateway` option. (Internally, the relevant field is now named
`gatewayExecutor`.)

Some types had been parametrized by `TContext`, because in AS3,
`GraphQLExecutor` (now `GatewayExecutor`) appeared to take a
`<TContext>`. However, even though the type itself took a generic
argument, its main use in the return from `gateway.load` implicitly
hardcoded the default `TContext`. So we are doubling down on that and
only allowing `GraphQLExecutor` to use AS3's default `TContext`, the
quite flexible `Record<string, any>`.

Most of the way toward #6719.

Co-authored-by: Trevor Scheer <trevor.scheer@gmail.com>
  • Loading branch information
glasser and trevor-scheer committed Aug 8, 2022
1 parent 10c387c commit bce9150
Show file tree
Hide file tree
Showing 18 changed files with 313 additions and 101 deletions.
6 changes: 6 additions & 0 deletions .changeset/eleven-needles-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@apollo/server-integration-testsuite": patch
"@apollo/server": patch
---

Support Gateway. Remove executor constructor option.
53 changes: 48 additions & 5 deletions docs/source/migration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ If supporting older versions of TypeScript is important to you and you'd like to

### `@apollo/gateway`

<!-- TODO(AS4): Fix before release -->
<!-- TODO(AS4): Fix before release. This now works at runtime; TS builds may require a new release of Gateway, and we'll update the docs when that's ready. -->

> ⚠️ Note: The alpha version of Apollo Server 4 does **not** work as an [Apollo Gateway](/federation/gateway). You can still use this alpha to serve [subgraphs](/federation/subgraphs), just not Gateways. We will fix this before the general release of Apollo Server 4.

Expand Down Expand Up @@ -741,6 +741,45 @@ new ApolloServer<MyContext>({
});
```

### `executor`

In Apollo Server 3, there are two different ways to specify a replacement for `graphql-js`'s execution functionality. Both of them involve defining a function of the type `GraphQLExecutor`. One way is to specify that function directly as the `executor` constructor option. The other way involves using the `gateway` option.

In Apollo Server 4, this redundancy has been removed: there is no longer an `executor` constructor option. (Additionally, the TypeScript `GraphQLExecutor` type has been renamed `GatewayExecutor` and moved to the `@apollo/server-gateway-interface` package.)

If your Apollo Server 3 code defined an `executor` function and used it like this:

<MultiCodeBlock>

```ts
new ApolloServer({
executor,
// ...
});
```

</MultiCodeBlock>

your Apollo Server code can use `gateway`, like so:

<MultiCodeBlock>

```ts
new ApolloServer({
gateway: {
async load() {
return { executor };
},
onSchemaLoadOrUpdate() {
return () => {};
},
async stop() {},
},
});
```

</MultiCodeBlock>


## Removed features

Expand Down Expand Up @@ -1326,14 +1365,18 @@ Apollo Server 4 more consistently handles errors thrown by multiple plugin hooks

The `gateway` option to the `ApolloServer` constructor is designed to be used with the `ApolloGateway` class from the `@apollo/gateway` package. Apollo Server 4 changes the details of how Apollo Server interacts with this object. If you use a [supported version of `@apollo/gateway`](#apollo-gateway) as your server's `gateway`, these changes won't affect you. However, if you provide something _other_ than an `ApolloGateway` instance to this option, you might need to adjust your custom code.

In Apollo Server 2, the TypeScript type used for the `gateway` constructor option is called `GraphQLService`. In Apollo Server 3, the TypeScript type changed to `GatewayInterface`, but the `apollo-server-core` package continued to export an identical `GraphQLService` type. Apollo Server 4 no longer exports the legacy `GraphQLService` type. Instead, use `GatewayInterface`.
All the TypeScript types you need to define your `gateway` are now part of the `@apollo/server-gateway-interface` package rather than being exported from an Apollo Server package directly. Additionally, many of these types have been renamed.

In Apollo Server 3, your `gateway` may define either `onSchemaChange` or the newer `onSchemaLoadOrUpdate`. In Apollo Server 4, your `gateway` must define `onSchemaLoadOrUpdate`.
In Apollo Server 2, the TypeScript type used for the `gateway` constructor option is called `GraphQLService`. In Apollo Server 3, the TypeScript type changed to `GatewayInterface`, but the `apollo-server-core` package continued to export an identical `GraphQLService` type. Apollo Server 4 no longer exports the legacy `GraphQLService` type. Instead, use `GatewayInterface`, now exported from `@apollo/server-gateway-interface`.

In Apollo Server 3, the `GatewayInterface.load` method returns `Promise<GraphQLServiceConfig>`, which contains a `schema` and an `executor`. Apollo Server 4 renames `GraphQLServiceConfig` to `GatewayLoadResult`, which now only has an `executor` field. You can use the `onSchemaLoadOrUpdate` hook if you want to receive the schema.
In Apollo Server 3, your `gateway` may define either `onSchemaChange` or the newer `onSchemaLoadOrUpdate`. In Apollo Server 4, your `gateway` must define `onSchemaLoadOrUpdate`.

In Apollo Server 3, `GatewayInterface.load` returned an object with an `executor` field with the TypeScript type `GraphQLExecutor`. The `executor` field returned the `GraphQLExecutionResult` type, a type defined by Apollo Server 3. In Apollo Server 4, the `GraphQLExecutor` type now returns the `ExecutionResult` type from `graphql-js`. These two types are essentially the same, except that in `ExecutionResult` the `data` and `extensions` fields are now `Record<string, unknown>`, rather than `Record<string, any>`.
In Apollo Server 3, the `GatewayInterface.load` method returns `Promise<GraphQLServiceConfig>`, which contains a `schema` and an `executor`. Apollo Server 4 renames `GraphQLServiceConfig` to `GatewayLoadResult` (exported from `@apollo/server-gateway-interface`), which now only has an `executor` field. You can use the `onSchemaLoadOrUpdate` hook if you want to receive the schema.

Additionally, the following types have been renamed and are now exported from `@apollo/server-gateway-interface`:
- `GraphQLExecutor` is now `GatewayExecutor`
- `SchemaLoadOrUpdateCallback` is now `GatewaySchemaLoadOrUpdateCallback`
- `Unsubscriber` is now `GatewayUnsubscriber`


## Changes to defaults
Expand Down
30 changes: 30 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 14 additions & 14 deletions packages/integration-testsuite/src/apolloServerTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,7 @@ import {
import type {
ApolloServerOptions,
ApolloServer,
GatewayInterface,
SchemaLoadOrUpdateCallback,
BaseContext,
GraphQLExecutor,
GraphQLRequestContextExecutionDidStart,
PluginDefinition,
} from '@apollo/server';
import fetch from 'node-fetch';
Expand Down Expand Up @@ -71,6 +67,12 @@ import {
it,
} from '@jest/globals';
import type { Mock } from 'jest-mock';
import type {
GatewayExecutor,
GatewayGraphQLRequestContext,
GatewayInterface,
GatewaySchemaLoadOrUpdateCallback,
} from '@apollo/server-gateway-interface';

const quietLogger = loglevel.getLogger('quiet');
function mockLogger() {
Expand Down Expand Up @@ -122,18 +124,18 @@ const makeGatewayMock = ({
optionsSpy = (_options) => {},
unsubscribeSpy = () => {},
}: {
executor: GraphQLExecutor<BaseContext> | null;
executor: GatewayExecutor | null;
schema: GraphQLSchema;
optionsSpy?: (_options: any) => void;
unsubscribeSpy?: () => void;
}) => {
const triggers = {
// This gets updated later, when ApolloServer calls gateway.load().
triggerSchemaChange: null as SchemaLoadOrUpdateCallback | null,
triggerSchemaChange: null as GatewaySchemaLoadOrUpdateCallback | null,
};

const listeners: SchemaLoadOrUpdateCallback[] = [];
const mockedGateway: GatewayInterface<BaseContext> = {
const listeners: GatewaySchemaLoadOrUpdateCallback[] = [];
const mockedGateway: GatewayInterface = {
load: async (options) => {
optionsSpy(options);
// Make sure it's async
Expand Down Expand Up @@ -459,7 +461,7 @@ export function defineIntegrationTestSuiteApolloServerTests(
});

it("accepts a gateway's schema and calls its executor", async () => {
const executor = jest.fn<GraphQLExecutor<{}>>();
const executor = jest.fn<GatewayExecutor>();
executor.mockReturnValue(
Promise.resolve({ data: { testString: 'hi - but federated!' } }),
);
Expand All @@ -482,7 +484,7 @@ export function defineIntegrationTestSuiteApolloServerTests(
const loadError = new Error(
'load error which should be be thrown by start',
);
const gateway: GatewayInterface<BaseContext> = {
const gateway: GatewayInterface = {
async load() {
throw loadError;
},
Expand Down Expand Up @@ -2247,7 +2249,7 @@ export function defineIntegrationTestSuiteApolloServerTests(
}),
});

const executor = (req: GraphQLRequestContextExecutionDidStart<any>) =>
const executor = (req: GatewayGraphQLRequestContext) =>
(req.source as string).match(/1/)
? Promise.resolve({ data: { testString1: 'hello' } })
: Promise.resolve({ data: { testString2: 'aloha' } });
Expand Down Expand Up @@ -2417,9 +2419,7 @@ export function defineIntegrationTestSuiteApolloServerTests(
};
});

const executor = async (
req: GraphQLRequestContextExecutionDidStart<any>,
) => {
const executor = async (req: GatewayGraphQLRequestContext) => {
const source = req.source as string;
const { startPromise, endPromise, i } = executorData[source];
startPromise.resolve();
Expand Down
4 changes: 3 additions & 1 deletion packages/integration-testsuite/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@
"outDir": "./dist"
},
"include": ["src/**/*"],
"references": [{ "path": "../server" }]
"references": [
{ "path": "../server" },
],
}
1 change: 1 addition & 0 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
},
"dependencies": {
"@apollo/cache-control-types": "^1.0.2",
"@apollo/server-gateway-interface": "^1.0.0",
"@apollo/usage-reporting-protobuf": "^4.0.0-alpha.1",
"@apollo/utils.createhash": "^1.1.0",
"@apollo/utils.fetcher": "^1.0.0",
Expand Down
Loading

0 comments on commit bce9150

Please sign in to comment.