Skip to content

Commit

Permalink
feat(gateway): Implement new supergraphSdl() config option for dyna…
Browse files Browse the repository at this point in the history
…mic gateway updates (#1246)

(copied from the changelog entry)

This change improves the `supergraphSdl` configuration option to provide a clean and flexible interface for updating gateway schema on load and at runtime. This PR brings a number of updates and deprecations to the gateway. Previous options for loading the gateway's supergraph (`serviceList`, `localServiceList`, `experimental_updateServiceDefinitions`, `experimental_supergraphSdl`) are all deprecated going forward. The migration paths all point to the updated `supergraphSdl` configuration option.

The most notable change here is the introduction of the concept of a `SupergraphManager` (one new possible type of `supergraphSdl`). This interface (when implemented) provides a means for userland code to update the gateway supergraph dynamically, perform subgraph healthchecks, and access subgraph datasources. All of the mentioned deprecated configurations now either use an implementation of a `SupergraphManager` internally or export one to be configured by the user (`IntrospectAndCompose` and `LocalCompose`).

For now: all of the mentioned deprecated configurations will still continue to work as expected. Their usage will come with deprecation warnings advising a switch to `supergraphSdl`.
* `serviceList` users should switch to the now-exported `IntrospectAndCompose` class.
* `localServiceList` users should switch to the similar `LocalCompose` class.
* `experimental_{updateServiceDefinitions|supergraphSdl}` users should migrate their implementation to a custom `SupergraphSdlHook` or `SupergraphManager`.

Since the gateway itself is no longer responsible for composition:
* `experimental_didUpdateComposition` has been renamed more appropriately to `experimental_didUpdateSupergraph` (no signature change)
* `experimental_compositionDidFail` hook is removed

`experimental_pollInterval` is deprecated and will issue a warning. Its renamed equivalent is `pollIntervalInMs`.

Some defensive code around gateway shutdown has been removed which was only relevant to users who are running the gateway within `ApolloServer` before v2.18. If you are still running one of these versions, server shutdown may not happen as smoothly.
  • Loading branch information
trevor-scheer committed Jan 10, 2022
1 parent 345d5f0 commit 3270c51
Show file tree
Hide file tree
Showing 42 changed files with 2,564 additions and 888 deletions.
4 changes: 2 additions & 2 deletions codegen.yml
Expand Up @@ -4,8 +4,8 @@ schema: [
"https://outofbandreporter.api.apollographql.com/",
]
documents:
- gateway-js/src/loadSupergraphSdlFromStorage.ts
- gateway-js/src/outOfBandReporter.ts
- gateway-js/src/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts
- gateway-js/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts
generates:
gateway-js/src/__generated__/graphqlTypes.ts:
plugins:
Expand Down
176 changes: 172 additions & 4 deletions docs/source/api/apollo-gateway.mdx
Expand Up @@ -100,11 +100,20 @@ const gateway = new ApolloGateway({

###### `supergraphSdl`

`String`
`string | SupergraphSdlHook | SupergraphManager`
</td>
<td>

A [supergraph schema](../#federated-schemas) ([generated with the Rover CLI](https://www.apollographql.com/docs/rover/supergraphs/#composing-a-supergraph-schema)) that's composed from your subgraph schemas. The supergraph schema includes directives that specify routing information for each subgraph.
You provide your supergraph schema to the gateway with this option. You can provide it as a `string`, via a `SupergraphSdlHook`, or via a `SupergraphManager`.

**When `supergraphSdl` is a `string`:** A [supergraph schema](../#federated-schemas) ([generated with the Rover CLI](https://www.apollographql.com/docs/rover/supergraphs/#composing-a-supergraph-schema)) that's composed from your subgraph schemas. The supergraph schema includes directives that specify routing information for each subgraph.

**When `supergraphSdl` is a `SupergraphSdlHook`:** This is an `async` function that returns an object containing a `supergraphSdl` string as well as a `cleanup` function. The hook accepts an object containing the following properties:
- `update`: A function that updates the supergraph schema
- `healthCheck`: A function that issues a health check against the subgraphs
- `getDataSource`: A function that gets a data source for a particular subgraph from the gateway

**When `supergraphSdl` is a `SupergraphManager`:** An object containing an `initialize` property. `initialize` is an `async` function of the `SupergraphSdlHook` type described directly above.

**If you are using managed federation,** do not provide this field.

Expand All @@ -124,7 +133,7 @@ A [supergraph schema](../#federated-schemas) ([generated with the Rover CLI](htt
</td>
<td>

**This option is discouraged in favor of [`supergraphSdl`](#supergraphsdl).**
**This option is deprecated in favor of the drop-in replacement, [`IntrospectAndCompose`](#class-introspectandcompose).**

An array of objects that each specify the `name` and `url` of one subgraph in your federated graph. On startup, the gateway uses this array to obtain your subgraph schemas via introspection and compose a supergraph schema.

Expand All @@ -143,10 +152,12 @@ You can specify any string value for the `name` field, which is used for identif

###### `introspectionHeaders`

`Object | (service: ServiceEndpointDefinition) => Promise<Object> | Object`
`Object | (service: ServiceEndpointDefinition) => (Promise<Object> | Object)`
</td>
<td>

**This option is deprecated in favor of the drop-in replacement, [`IntrospectAndCompose`](#class-introspectandcompose).**

An object (or an optionally async function _returning_ an object) that contains the names and values of HTTP headers that the gateway includes _only_ when making introspection requests to your subgraphs.

**If you are using managed federation,** do not provide this field.
Expand Down Expand Up @@ -557,3 +568,160 @@ The details of the `fetch` response sent by the subgraph.
</tr>
</tbody>
</table>

## `class IntrospectAndCompose`

`IntrospectAndCompose` is a development tool for fetching and composing subgraph SDL into a supergraph for your gateway. Given a list of subgraphs and their URLs, `IntrospectAndCompose` will issue queries for their SDL, compose them into a supergraph, and provide that supergraph to the gateway. It can also be configured to update via polling and perform subgraph health checks to ensure that supergraphs are updated safely. `IntrospectAndCompose` implements the `SupergraphManager` interface and is passed in to `ApolloGateway`'s `supergraphSdl` constructor option.

> `IntrospectAndCompose` is the drop-in replacement for `serviceList`.
### Methods

#### `constructor`

Returns an initialized `IntrospectAndCompose` instance, which you can then pass to the `supergraphSdl` configuration option of the `ApolloGateway` constructor, like so:

```javascript{3-7}
const server = new ApolloServer({
gateway: new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
// ...
],
}),
}),
});
```

Takes an `options` object as a parameter. Supported properties of this object are described below.

##### Examples

###### Providing a `subgraphs` list and headers to authorize introspection

```js
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'products', url: 'https://products-service.dev/graphql',
{ name: 'reviews', url: 'https://reviews-service.dev/graphql' },
],
introspectionHeaders: {
Authorization: 'Bearer abc123'
},
}),
});
```
###### Configuring the subgraph fetcher
`IntrospectAndCompose` uses the data sources constructed by `ApolloGateway`. To customize the gateway's data sources, you can provide a [`buildService`](#buildservice) function to the `ApolloGateway` constructor. In the example below, `IntrospectAndCompose` makes authenticated requests to the subgraphs
via the `AuthenticatedDataSource`s that we construct in the gateway's `buildService` function.
```js
const gateway = new ApolloGateway({
buildService({ name, url }) {
return new AuthenticatedDataSource({ url });
},
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'products', url: 'https://products-service.dev/graphql',
{ name: 'reviews', url: 'https://reviews-service.dev/graphql' },
],
}),
});
```
##### Options
<table class="field-table">
<thead>
<tr>
<th>Name /<br/>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
###### `subgraphs`
`Array<ServiceEndpointDefinition>`
</td>
<td>
An array of objects that each specify the `name` and `url` of one subgraph in your federated graph. On startup, `IntrospectAndCompose` uses this array to obtain your subgraph schemas via introspection and compose a supergraph schema.
The `name` field is a string that should be treated as a subgraph's unique identifier. It is used for query planning, logging, and reporting metrics to Apollo Studio.
> For Studio users, subgraph names **must:**
- Begin with a letter (capital or lowercase)
- Include only letters, numbers, underscores (_), and hyphens (-)
- Have a maximum of 64 characters
</td>
</tr>
<tr>
<td>
###### `introspectionHeaders`
`Object | (service: ServiceEndpointDefinition) => (Promise<Object> | Object)`
</td>
<td>
An object (or an optionally async function _returning_ an object)that contains the names and values of HTTP headers that the gateway includes _only_ when making introspection requests to your subgraphs.
**If you define a [`buildService`](#buildservice) function in your `ApolloGateway` config, ** specify these headers in that function instead of providing this option. This ensures that your `buildService` function doesn't inadvertently overwrite the values of any headers you provide here.
</td>
</tr>
<tr>
<td>
###### `pollIntervalInMs`
`number`
</td>
<td>
Specify this option to enable supergraph updates via subgraph polling. `IntrospectAndCompose` polls each subgraph at the given interval.
</td>
</tr>
<tr>
<td>
###### `subgraphHealthCheck`
`boolean`
</td>
<td>
> This option applies only to subgraphs that are configured for polling via the `pollIntervalInMs` option.
If `true`, the gateway performs a health check on each subgraph before performing a supergraph update. Errors during health checks will result in skipping the supergraph update, but polling will continue. The health check is a simple GraphQL query (`query __ApolloServiceHealthCheck__ { __typename }`) to ensure that subgraphs are reachable and can successfully respond to GraphQL requests.
**This option is the `IntrospectAndCompose` equivalent of `ApolloGateway`'s `serviceHealthCheck` option. If you are using `IntrospectAndCompose`, enabling `serviceHealthCheck` on your `ApolloGateway` instance has no effect.**
</td>
</tr>
<tr>
<td>
###### `logger`
[`Logger`](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-server-types/src/index.ts#L166-L172)
</td>
<td>
An object to use for logging in place of `console`. If provided, this object must implement all methods of [the `Logger` interface](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-server-types/src/index.ts#L166-L172).
`IntrospectAndCompose` doesn't share the same logger as the `ApolloGateway` it's configured with. In most cases, you probably want to pass the same logger to both `ApolloGateway` and `IntrospectAndCompose`.
</td>
</tr>
</tbody>
</table>
6 changes: 3 additions & 3 deletions docs/source/entities.mdx
Expand Up @@ -391,11 +391,11 @@ We're done! `Bill` now originates in a new subgraph, and it was resolvable durin

</ExpansionPanel>

<ExpansionPanel title="With serviceList">
<ExpansionPanel title="With `IntrospectAndCompose`">

> ⚠️ We strongly recommend _against_ using `serviceList`. For details, see [Limitations of `serviceList`](./gateway/#limitations-of-servicelist).
> ⚠️ We strongly recommend _against_ using `IntrospectAndCompose` in production. For details, see [Limitations of `IntrospectAndCompose`](./gateway/#limitations-of-introspectandcompose).
When you provide a `serviceList` to `ApolloGateway`, it performs composition _itself_ on startup after fetching all of your subgraph schemas. If this runtime composition fails, the gateway fails to start up, resulting in downtime.
When you provide `IntrospectAndCompose` to `ApolloGateway`, it performs composition _itself_ on startup after fetching all of your subgraph schemas. If this runtime composition fails, the gateway fails to start up, resulting in downtime.

To minimize downtime for your graph, you need to make sure all of your subgraph schemas successfully compose whenever your gateway starts up. When migrating an entity, this requires a **coordinated deployment** of your modified subgraphs and a restart of the gateway itself.

Expand Down

0 comments on commit 3270c51

Please sign in to comment.