Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support gateways without executors #5539

Merged
merged 1 commit into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The version headers in this history reflect the versions of Apollo Server itself
- `apollo-server-core`: If a client does not provide a value or provides null for a variable declared to be non-null, this is now reported as an error with an `extensions.code` of `BAD_USER_INPUT` rather than `INTERNAL_SERVER_ERROR`. (This is similar to a change we made in v2.23.0 for variables that are sent as the wrong type.) [PR #5508](https://github.com/apollographql/apollo-server/pull/5508) [Issue #5353](https://github.com/apollographql/apollo-server/issues/5353)
- `apollo-server-core`/`apollo-server-plugin-base`: Add support for `schemaDidLoadOrUpdate` event hooks, to be specified by the `serverWillStart` event hook. Plugins listening for this event will receive the API schema (and core schema for gateways) when the server's schema is initially loaded and when the server's schema is updated. For more information about this plugin event, see [the plugin event reference documentation](https://www.apollographql.com/docs/apollo-server/integrations/plugins-event-reference/). [PR #5187](https://github.com/apollographql/apollo-server/pull/5187)
- `apollo-server-core`: Add support for schema reporting when using Apollo Gateway. At the time of this package's release, Apollo Studio does not yet support schema reporting from gateways, so you should not use this feature yet for gateways (unless instructed otherwise by Apollo staff or by the Studio docs). If you do enable schema reporting for a gateway, the version of `@apollo/gateway` must be at least `0.35.0` , or else `start()` will error. [PR #5187](https://github.com/apollographql/apollo-server/pull/5187)
- `apollo-server-core`: Support gateways without executors, to help with mocking gateways. Note that if you have a custom `GatewayInterface` implementation, Apollo Server will now honor the `executor` returned from `load` and will ignore the `executor` method on the gateway itself. See the PR for details. [PR #5539](https://github.com/apollographql/apollo-server/pull/5539)

## v3.0.2

Expand Down
15 changes: 6 additions & 9 deletions packages/apollo-server-core/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,14 +281,6 @@ export class ApolloServerBase<
logger: this.logger,
}),
};

// The main thing that the Gateway does is replace execution with
// its own executor. It would be awkward if you always had to pass
// `gateway: gateway, executor: gateway` to this constructor, so
// we let specifying `gateway` be a shorthand for the above.
// (We won't actually invoke the executor until after we're successfully
// called `gateway.load`.)
this.requestOptions.executor = gateway.executor;
} else {
// We construct the schema synchronously so that we can fail fast if the
// schema can't be constructed. (This used to be more important because we
Expand Down Expand Up @@ -376,10 +368,15 @@ export class ApolloServerBase<
schemaManager,
};
try {
await schemaManager.start();
const executor = await schemaManager.start();
this.toDispose.add(async () => {
await schemaManager.stop();
});
if (executor) {
// If we loaded an executor from a gateway, use it to execute
// operations.
this.requestOptions.executor = executor;
}

const schemaDerivedData = schemaManager.getSchemaDerivedData();
const service: GraphQLServiceContext = {
Expand Down
15 changes: 6 additions & 9 deletions packages/apollo-server-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import type {
ApolloConfig,
ValueOrPromise,
GraphQLExecutor,
GraphQLExecutionResult,
GraphQLRequestContextExecutionDidStart,
ApolloConfigInput,
} from 'apollo-server-types';

Expand Down Expand Up @@ -50,7 +48,7 @@ export type SchemaChangeCallback = (apiSchema: GraphQLSchema) => void;

export type GraphQLServiceConfig = {
schema: GraphQLSchema;
executor: GraphQLExecutor;
executor: GraphQLExecutor | null;
};

export interface GatewayInterface {
Expand All @@ -71,13 +69,12 @@ export interface GatewayInterface {
}) => void,
): Unsubscriber;

// Note: The `TContext` typing here is not conclusively behaving as we expect:
// https://github.com/apollographql/apollo-server/pull/3811#discussion_r387381605
executor<TContext>(
requestContext: GraphQLRequestContextExecutionDidStart<TContext>,
): Promise<GraphQLExecutionResult>;

stop(): Promise<void>;

// Note: this interface used to have an `executor` method, and also return the
// executor from `load()`. ApolloServer would only use the former. We dropped
// this method and now use the latter, which allows you to make a "mock
// gateway" that updates the schema over time but uses normal execution.
}

// This was the name used for GatewayInterface in AS2; continue to export it so
Expand Down
6 changes: 5 additions & 1 deletion packages/apollo-server-core/src/utils/schemaManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { GraphQLSchema } from 'graphql';
import {
ApolloConfig,
GraphQLExecutor,
GraphQLSchemaContext,
Logger,
} from 'apollo-server-types';
Expand Down Expand Up @@ -81,8 +82,9 @@ export class SchemaManager {
* - Initialize schema-derived data.
* - Synchronously notify onSchemaLoadOrUpdate() listeners of schema load, and
* asynchronously notify them of schema updates.
* - If we started a gateway, returns the gateway's executor; otherwise null.
*/
public async start(): Promise<void> {
public async start(): Promise<GraphQLExecutor | null> {
if (this.modeSpecificState.mode === 'gateway') {
const gateway = this.modeSpecificState.gateway;
if (gateway.onSchemaLoadOrUpdate) {
Expand Down Expand Up @@ -115,10 +117,12 @@ export class SchemaManager {
if (!this.schemaDerivedData) {
this.processSchemaLoadOrUpdateEvent({ apiSchema: config.schema });
}
return config.executor;
} else {
this.processSchemaLoadOrUpdateEvent({
apiSchema: this.modeSpecificState.apiSchema,
});
return null;
}
}

Expand Down
12 changes: 4 additions & 8 deletions packages/apollo-server-integration-testsuite/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import {
ApolloServerBase,
PluginDefinition,
GatewayInterface,
GraphQLExecutor,
GraphQLServiceConfig,
ApolloServerPluginInlineTrace,
ApolloServerPluginUsageReporting,
Expand Down Expand Up @@ -126,11 +125,9 @@ const schema = new GraphQLSchema({
const makeGatewayMock = ({
optionsSpy = (_options) => {},
unsubscribeSpy = () => {},
executor = async () => ({}),
}: {
optionsSpy?: (_options: any) => void;
unsubscribeSpy?: () => void;
executor?: GraphQLExecutor;
} = {}) => {
let resolution: GraphQLServiceConfig | null = null;
let rejection: Error | null = null;
Expand All @@ -145,7 +142,6 @@ const makeGatewayMock = ({
};

const mockedGateway: GatewayInterface = {
executor,
load: async (options) => {
optionsSpy(options);
// Make sure it's async
Expand Down Expand Up @@ -414,7 +410,7 @@ export function testApolloServer<AS extends ApolloServerBase>(
Promise.resolve({ data: { testString: 'hi - but federated!' } }),
);

const { gateway, triggers } = makeGatewayMock({ executor });
const { gateway, triggers } = makeGatewayMock();

triggers.resolveLoad({ schema, executor });

Expand Down Expand Up @@ -2701,7 +2697,7 @@ export function testApolloServer<AS extends ApolloServerBase>(
? Promise.resolve({ data: { testString1: 'hello' } })
: Promise.resolve({ data: { testString2: 'aloha' } });

const { gateway, triggers } = makeGatewayMock({ executor });
const { gateway, triggers } = makeGatewayMock();

triggers.resolveLoad({
schema: makeQueryTypeWithField('testString1'),
Expand Down Expand Up @@ -2782,7 +2778,7 @@ export function testApolloServer<AS extends ApolloServerBase>(
return { data: { testString: 'hi - but federated!' } };
};

const { gateway, triggers } = makeGatewayMock({ executor });
const { gateway, triggers } = makeGatewayMock();

triggers.resolveLoad({ schema, executor });
const { url: uri } = await createApolloServer({
Expand Down Expand Up @@ -2844,7 +2840,7 @@ export function testApolloServer<AS extends ApolloServerBase>(
return { data: { [`testString${i}`]: `${i}` } };
};

const { gateway, triggers } = makeGatewayMock({ executor });
const { gateway, triggers } = makeGatewayMock();

triggers.resolveLoad({
schema: makeQueryTypeWithField('testString1'),
Expand Down