Skip to content
Permalink
Browse files

Move Apollo Federation into core (#2742)

This PR (though large) is comprised of months of work on Apollo
Federation, a new way to build a federated graph. This commit brings the
two core packages @apollo/federation and @apollo/gateway as well as
documentation for building a federated graph

Co-authored-by: Martijn Walraven <martijn@martijnwalraven.com>
Co-authored-by: Jake Dawkins <dawkinsjh@gmail.com> 
Co-authored-by: Jesse Rosenberger <git@jro.cc>
Co-authored-by: Trevor Scheer <trevor.scheer@gmail.com>
Co-authored-by: James Baxley <james@apollographql.com>

cc @martijnwalraven @JakeDawkins @abernix @trevor-scheer @jbaxleyiii
  • Loading branch information...
jbaxleyiii authored and abernix committed May 30, 2019
1 parent 43c02fc commit 408e9a99b7efb1f18a292c92b334ad999ef4a04b
Showing with 14,737 additions and 24 deletions.
  1. +16 βˆ’1 docs/gatsby-config.js
  2. +84 βˆ’0 docs/source/api/apollo-federation.mdx
  3. +111 βˆ’0 docs/source/api/apollo-gateway.mdx
  4. +123 βˆ’0 docs/source/federation/advanced-features.md
  5. +20 βˆ’0 docs/source/federation/concerns.mdx
  6. +166 βˆ’0 docs/source/federation/core-concepts.md
  7. +37 βˆ’0 docs/source/federation/errors.md
  8. +307 βˆ’0 docs/source/federation/federation-spec.md
  9. +198 βˆ’0 docs/source/federation/implementing.md
  10. +112 βˆ’0 docs/source/federation/introduction.mdx
  11. +220 βˆ’0 docs/source/federation/migrating-from-stitching.md
  12. BIN docs/source/images/composition_schema-1.png
  13. BIN docs/source/images/composition_schema-2.png
  14. BIN docs/source/images/playground.png
  15. +1 βˆ’1 jest.config.base.js
  16. +51 βˆ’10 package-lock.json
  17. +4 βˆ’0 package.json
  18. +6 βˆ’0 packages/apollo-federation/.npmignore
  19. +4 βˆ’0 packages/apollo-federation/CHANGELOG.md
  20. +20 βˆ’0 packages/apollo-federation/LICENSE.md
  21. +1 βˆ’0 packages/apollo-federation/README.md
  22. +5 βˆ’0 packages/apollo-federation/jest.config.js
  23. +24 βˆ’0 packages/apollo-federation/package.json
  24. +5 βˆ’0 packages/apollo-federation/src/__tests__/tsconfig.json
  25. +1,245 βˆ’0 packages/apollo-federation/src/composition/__tests__/compose.test.ts
  26. +112 βˆ’0 packages/apollo-federation/src/composition/__tests__/composeAndValidate.test.ts
  27. +8 βˆ’0 packages/apollo-federation/src/composition/__tests__/tsconfig.json
  28. +196 βˆ’0 packages/apollo-federation/src/composition/__tests__/utils.test.ts
  29. +485 βˆ’0 packages/apollo-federation/src/composition/compose.ts
  30. +21 βˆ’0 packages/apollo-federation/src/composition/composeAndValidate.ts
  31. +4 βˆ’0 packages/apollo-federation/src/composition/index.ts
  32. +5 βˆ’0 packages/apollo-federation/src/composition/rules.ts
  33. +76 βˆ’0 packages/apollo-federation/src/composition/types.ts
  34. +400 βˆ’0 packages/apollo-federation/src/composition/utils.ts
  35. +5 βˆ’0 packages/apollo-federation/src/composition/validate/__tests__/tsconfig.json
  36. +40 βˆ’0 packages/apollo-federation/src/composition/validate/index.ts
  37. +78 βˆ’0 ...pollo-federation/src/composition/validate/postComposition/__tests__/externalMissingOnBase.test.ts
  38. +65 βˆ’0 ...apollo-federation/src/composition/validate/postComposition/__tests__/externalTypeMismatch.test.ts
  39. +176 βˆ’0 packages/apollo-federation/src/composition/validate/postComposition/__tests__/externalUnused.test.ts
  40. +104 βˆ’0 ...ollo-federation/src/composition/validate/postComposition/__tests__/keyFieldsMissingOnBase.test.ts
  41. +152 βˆ’0 ...-federation/src/composition/validate/postComposition/__tests__/keyFieldsSelectInvalidType.test.ts
  42. +62 βˆ’0 ...eration/src/composition/validate/postComposition/__tests__/providesFieldsMissingExternals.test.ts
  43. +149 βˆ’0 ...ration/src/composition/validate/postComposition/__tests__/providesFieldsSelectInvalidType.test.ts
  44. +133 βˆ’0 .../apollo-federation/src/composition/validate/postComposition/__tests__/providesNotOnEntity.test.ts
  45. +62 βˆ’0 ...eration/src/composition/validate/postComposition/__tests__/requiresFieldsMissingExternals.test.ts
  46. +65 βˆ’0 ...federation/src/composition/validate/postComposition/__tests__/requiresFieldsMissingOnBase.test.ts
  47. +66 βˆ’0 packages/apollo-federation/src/composition/validate/postComposition/externalMissingOnBase.ts
  48. +73 βˆ’0 packages/apollo-federation/src/composition/validate/postComposition/externalTypeMismatch.ts
  49. +126 βˆ’0 packages/apollo-federation/src/composition/validate/postComposition/externalUnused.ts
  50. +12 βˆ’0 packages/apollo-federation/src/composition/validate/postComposition/index.ts
  51. +54 βˆ’0 packages/apollo-federation/src/composition/validate/postComposition/keyFieldsMissingOnBase.ts
  52. +97 βˆ’0 packages/apollo-federation/src/composition/validate/postComposition/keyFieldsSelectInvalidType.ts
  53. +58 βˆ’0 packages/apollo-federation/src/composition/validate/postComposition/providesFieldsMissingExternal.ts
  54. +101 βˆ’0 ...ges/apollo-federation/src/composition/validate/postComposition/providesFieldsSelectInvalidType.ts
  55. +62 βˆ’0 packages/apollo-federation/src/composition/validate/postComposition/providesNotOnEntity.ts
  56. +57 βˆ’0 packages/apollo-federation/src/composition/validate/postComposition/requiresFieldsMissingExternal.ts
  57. +56 βˆ’0 packages/apollo-federation/src/composition/validate/postComposition/requiresFieldsMissingOnBase.ts
  58. +45 βˆ’0 ...es/apollo-federation/src/composition/validate/preComposition/__tests__/externalUsedOnBase.test.ts
  59. +132 βˆ’0 ...llo-federation/src/composition/validate/preComposition/__tests__/keyFieldsMissingExternal.test.ts
  60. +46 βˆ’0 ...es/apollo-federation/src/composition/validate/preComposition/__tests__/requiresUsedOnBase.test.ts
  61. +132 βˆ’0 ...ges/apollo-federation/src/composition/validate/preComposition/__tests__/reservedFieldUsed.test.ts
  62. +96 βˆ’0 packages/apollo-federation/src/composition/validate/preComposition/__tests__/rootFieldUsed.test.ts
  63. +43 βˆ’0 packages/apollo-federation/src/composition/validate/preComposition/externalUsedOnBase.ts
  64. +5 βˆ’0 packages/apollo-federation/src/composition/validate/preComposition/index.ts
  65. +117 βˆ’0 packages/apollo-federation/src/composition/validate/preComposition/keyFieldsMissingExternal.ts
  66. +43 βˆ’0 packages/apollo-federation/src/composition/validate/preComposition/requiresUsedOnBase.ts
  67. +46 βˆ’0 packages/apollo-federation/src/composition/validate/preComposition/reservedFieldUsed.ts
  68. +87 βˆ’0 packages/apollo-federation/src/composition/validate/preComposition/rootFieldUsed.ts
  69. +134 βˆ’0 packages/apollo-federation/src/directives.ts
  70. +2 βˆ’0 packages/apollo-federation/src/index.ts
  71. +425 βˆ’0 packages/apollo-federation/src/service/__tests__/buildFederatedSchema.test.ts
  72. +5 βˆ’0 packages/apollo-federation/src/service/__tests__/tsconfig.json
  73. +97 βˆ’0 packages/apollo-federation/src/service/buildFederatedSchema.ts
  74. +2 βˆ’0 packages/apollo-federation/src/service/index.ts
  75. +383 βˆ’0 packages/apollo-federation/src/service/printFederatedSchema.ts
  76. +21 βˆ’0 packages/apollo-federation/src/snapshotSerializers/astSerializer.ts
  77. +15 βˆ’0 packages/apollo-federation/src/snapshotSerializers/index.ts
  78. +13 βˆ’0 packages/apollo-federation/src/snapshotSerializers/selectionSetSerializer.ts
  79. +11 βˆ’0 packages/apollo-federation/src/snapshotSerializers/typeSerializer.ts
  80. +125 βˆ’0 packages/apollo-federation/src/types.ts
  81. +10 βˆ’0 packages/apollo-federation/tsconfig.json
  82. +7 βˆ’0 packages/apollo-federation/tsconfig.test.json
  83. +6 βˆ’0 packages/apollo-gateway/.npmignore
  84. +3 βˆ’0 packages/apollo-gateway/CHANGELOG.md
  85. +20 βˆ’0 packages/apollo-gateway/LICENSE.md
  86. +1 βˆ’0 packages/apollo-gateway/README.md
  87. +7 βˆ’0 packages/apollo-gateway/jest.config.js
  88. +32 βˆ’0 packages/apollo-gateway/package.json
  89. +142 βˆ’0 packages/apollo-gateway/src/FieldSet.ts
  90. +63 βˆ’0 packages/apollo-gateway/src/QueryPlan.ts
  91. 0 packages/apollo-gateway/src/__tests__/.gitkeep
  92. +71 βˆ’0 packages/apollo-gateway/src/__tests__/__fixtures__/schemas/accounts.ts
  93. +65 βˆ’0 packages/apollo-gateway/src/__tests__/__fixtures__/schemas/books.ts
  94. +43 βˆ’0 packages/apollo-gateway/src/__tests__/__fixtures__/schemas/inventory.ts
  95. +125 βˆ’0 packages/apollo-gateway/src/__tests__/__fixtures__/schemas/product.ts
  96. +169 βˆ’0 packages/apollo-gateway/src/__tests__/__fixtures__/schemas/reviews.ts
  97. +813 βˆ’0 packages/apollo-gateway/src/__tests__/buildQueryPlan.test.ts
  98. +270 βˆ’0 packages/apollo-gateway/src/__tests__/executeQueryPlan.test.ts
  99. +94 βˆ’0 packages/apollo-gateway/src/__tests__/execution-utils.ts
  100. +80 βˆ’0 packages/apollo-gateway/src/__tests__/gateway/buildService.test.ts
  101. +61 βˆ’0 packages/apollo-gateway/src/__tests__/gateway/queryPlanCache.test.ts
  102. +316 βˆ’0 packages/apollo-gateway/src/__tests__/integration/abstract-types.test.ts
  103. +192 βˆ’0 packages/apollo-gateway/src/__tests__/integration/aliases.test.ts
  104. +309 βˆ’0 packages/apollo-gateway/src/__tests__/integration/boolean.test.ts
  105. +215 βˆ’0 packages/apollo-gateway/src/__tests__/integration/complex-key.test.ts
  106. +49 βˆ’0 packages/apollo-gateway/src/__tests__/integration/execution-style.test.ts
  107. +257 βˆ’0 packages/apollo-gateway/src/__tests__/integration/fragments.test.ts
  108. +307 βˆ’0 packages/apollo-gateway/src/__tests__/integration/mutations.test.ts
  109. +81 βˆ’0 packages/apollo-gateway/src/__tests__/integration/provides.test.ts
  110. +49 βˆ’0 packages/apollo-gateway/src/__tests__/integration/requires.test.ts
  111. +43 βˆ’0 packages/apollo-gateway/src/__tests__/integration/single-service.test.ts
  112. +96 βˆ’0 packages/apollo-gateway/src/__tests__/integration/variables.test.ts
  113. +105 βˆ’0 packages/apollo-gateway/src/__tests__/matchers/toCallService.ts
  114. +40 βˆ’0 packages/apollo-gateway/src/__tests__/matchers/toHaveBeenCalledBefore.ts
  115. +52 βˆ’0 packages/apollo-gateway/src/__tests__/matchers/toHaveFetched.ts
  116. +64 βˆ’0 packages/apollo-gateway/src/__tests__/matchers/toMatchAST.ts
  117. +4 βˆ’0 packages/apollo-gateway/src/__tests__/testSetup.ts
  118. +7 βˆ’0 packages/apollo-gateway/src/__tests__/tsconfig.json
  119. +857 βˆ’0 packages/apollo-gateway/src/buildQueryPlan.ts
  120. +69 βˆ’0 packages/apollo-gateway/src/cachedFetcher.ts
  121. +44 βˆ’0 packages/apollo-gateway/src/datasources/LocalGraphQLDatasource.ts
  122. +135 βˆ’0 packages/apollo-gateway/src/datasources/RemoteGraphQLDatasource.ts
  123. +220 βˆ’0 packages/apollo-gateway/src/datasources/__tests__/RemoteGraphQLDatasource.test.ts
  124. +3 βˆ’0 packages/apollo-gateway/src/datasources/index.ts
  125. +7 βˆ’0 packages/apollo-gateway/src/datasources/types.ts
  126. +429 βˆ’0 packages/apollo-gateway/src/executeQueryPlan.ts
  127. +271 βˆ’0 packages/apollo-gateway/src/index.ts
  128. +70 βˆ’0 packages/apollo-gateway/src/loadServicesFromRemoteEndpoint.ts
  129. +21 βˆ’0 packages/apollo-gateway/src/snapshotSerializers/astSerializer.ts
  130. +21 βˆ’0 packages/apollo-gateway/src/snapshotSerializers/index.ts
  131. +127 βˆ’0 packages/apollo-gateway/src/snapshotSerializers/queryPlanSerializer.ts
  132. +13 βˆ’0 packages/apollo-gateway/src/snapshotSerializers/selectionSetSerializer.ts
  133. +11 βˆ’0 packages/apollo-gateway/src/snapshotSerializers/typeSerializer.ts
  134. +11 βˆ’0 packages/apollo-gateway/src/utilities/MultiMap.ts
  135. +50 βˆ’0 packages/apollo-gateway/src/utilities/array.ts
  136. +17 βˆ’0 packages/apollo-gateway/src/utilities/deepMerge.ts
  137. +103 βˆ’0 packages/apollo-gateway/src/utilities/graphql.ts
  138. +14 βˆ’0 packages/apollo-gateway/src/utilities/predicates.ts
  139. +13 βˆ’0 packages/apollo-gateway/tsconfig.json
  140. +1 βˆ’1 packages/apollo-server-azure-functions/README.md
  141. +1 βˆ’1 packages/apollo-server-cloud-functions/README.md
  142. +1 βˆ’1 packages/apollo-server-express/README.md
  143. +1 βˆ’1 packages/apollo-server-fastify/README.md
  144. +1 βˆ’1 packages/apollo-server-hapi/README.md
  145. +1 βˆ’1 packages/apollo-server-koa/README.md
  146. +2 βˆ’2 packages/apollo-server-lambda/README.md
  147. +4 βˆ’4 packages/apollo-server-micro/README.md
  148. +2 βˆ’0 tsconfig.build.json
  149. +4 βˆ’0 types/loglevel-debug/index.d.ts
@@ -37,6 +37,16 @@ module.exports = {
'features/health-checks',
'features/file-uploads',
],
Federation: [
'federation/introduction',
'federation/concerns',
'federation/core-concepts',
'federation/implementing',
'federation/advanced-features',
'federation/errors',
'federation/migrating-from-stitching',
'federation/federation-spec',
],
// 'Schema stitching': [
// 'features/schema-stitching',
// 'features/remote-schemas',
@@ -51,7 +61,12 @@ module.exports = {
'deployment/netlify',
'deployment/azure-functions',
],
'API Reference': ['api/apollo-server', 'api/graphql-tools'],
'API Reference': [
'api/apollo-server',
'api/apollo-federation',
'api/apollo-gateway',
'api/graphql-tools',
],
Migration: [
'migration-two-dot',
'migration-engine',
@@ -0,0 +1,84 @@
---
title: "API Reference: @apollo/federation"
description: Apollo Federation API reference
---

## `buildFederatedSchema`

The `@apollo/federation` has one export for intended public use, the `buildFederatedSchema` function. This function is designed to take a list of GraphQL schema modules (i.e. `{ typeDefs: DocumentNode, resolvers: ResolverMap }`) and turn them into a federation ready schema.

#### Parameters

* `modules`: `GraphQLSchemaModule[]` _(required)_

An array of schema modules made up of typeDefs and resolvers

#### Usage

```js
const typeDefs = gql`
type Query {
me: User
}
type User @key(fields: "id") {
id: ID!
username: String
}
`;
const resolvers = {
Query: {
me() {
return { id: "1", username: "@ava" }
}
},
User: {
__resolveReference(user, { fetchUserById }){
return fetchUserById(user.id)
}
}
};
const server = new ApolloServer({
schema: buildFederatedSchema([{ typeDefs, resolvers }])
});
```

## `__resolveReference`

Though not an export from `@apollo/federation`, using the `buildFederatedSchema` function allows a resovler map to define a new special field called `__resolveReference` for each type that is an entity. This function is called whenever an entity is requested as part of the fufilling a query plan. `__resolveReference` is called with the following parameters:

#### Parameters

* `reference`: `Object`

The reference passed from another service. Will always contain at least the `__typename` and the primary key of the entity.

* `context`: `Object`

The context object generated by Apollo Server including `DataSources` if used.

* `info`: `GraphQLResolveInfo`

This argument contains information about the execution state of the query, including the field name, path to the field from the root, and more. It is currently only documented in the [GraphQL.js](https://github.com/graphql/graphql-js/blob/c82ff68f52722c20f10da69c9e50a030a1f218ae/src/type/definition.js#L489-L500) source code.

#### Usage

```js
const typeDefs = gql`
type User @key(fields: "id") {
id: ID!
username: String
}
`;
const resolvers = {
User: {
__resolveReference(user, { datasources }){
// user will always have at least the `id` and the `__typename` here
return datasources.users.fetchUserById(user.id)
}
}
};
```
@@ -0,0 +1,111 @@
---
title: "API Reference: @apollo/gateway"
description: Apollo Gateway API reference
---

This API reference documents the exports from the `@apollo/gateway`.


## `ApolloGateway`

The core of the federated gateway implementation. For an example, see the ["implementing"](/federation/implementing/) section.

### `constructor(options)`: `ApolloGateway`

#### Parameters

* `options`: `Object`

* `serviceList`: `ServiceDefinition[]` _(required)_

An array of service definitions. A service definition contains the `name` and `url` for a federated service.

```js{3-6}
const gateway = new ApolloGateway({
...
serviceList: [
{ name: 'products', url: 'https://products-service.dev/graphql' },
{ name: 'reviews', url: 'https://reviews-service.dev/graphql' }
]
});
```

* `buildService`: `(service: ServiceDefinition) => GraphQLDataSource`

A function to be called for each service in `options.serviceList`.
The `service` is provided as an argument, and a `GraphQLDataSource` is expected to be returned.
Allows for customizing transports.

```js{3-10}
const gateway = new ApolloGateway({
...
buildService({ name, url }) {
return new RemoteGraphQLDataSource({
url,
willSendRequest({ request, context }) {
request.http.headers.set('x-user-id', context.userId);
},
});
},
});
```

* `debug`: `Boolean`

With debug enabled, the server will log startup errors as well as query plans during incoming requests.

### `ApolloGateway.load()`: `Promise<{ schema: GraphQLSchema, exector: GraphQLExecutor }>`

#### Returns

`Promise` that resolves to an object that contains:

* `schema`: <`GraphQLSchema`>
A composed schema that represents all of the federated schemas
* `executor`: <`GraphQLExecutor`>
An executor, consumed by `ApolloServer`. Uses the query planner in order to
transform a single, incoming request into a set of requests to the federated
services.

It's intended that the result of the resolved `Promise` is passed into the constructor of `ApolloServer` like so:

```javascript{6-7}
...
(async () => {
const { schema, executor } = await gateway.load();
const server = new ApolloServer({
schema,
executor
});
...
})();
```

## RemoteGraphQLDataSource

The `RemoteGraphQLDataSource` export is used to connect to underlying federated services from the gateway. It allows for customizations of its request using the `willSendRequest` function. By default, the `ApolloGateway` creates a new `RemoteGraphQLDataSource` for each service using the `url` from the service definition as its target location.

### `constructor(options)`: `RemoteGraphQLDataSource`

#### Parameters

* `options`: `Object`

* `url`: `string` _(required)_

The location of the service to fetch using an HTTP request.

* `willSendRequest`: `(request: GraphQLRequest) => Promise<void>`

A function to be called on every fetch to the underlying GraphQL service. This method takes information about the incoming request, as well as the context from Apollo Server, to allow for customizing the request.

```js{3-5}
new RemoteGraphQLDataSource({
url,
willSendRequest({ request, context }) {
request.http.headers.set('x-user-id', context.userId);
},
});
```

@@ -0,0 +1,123 @@
---
title: Advanced features
description: Patterns for complex graphs
---

Federation supports several advanced features that make it easier to integrate with legacy APIs or build complex schemas.

## Multiple primary keys

In some cases there may be multiple ways of referring to an entity, such as when we refer to a user either by ID or by email. This pattern is especially common when a type spans services: your review system may refer to a product by UPC, while your inventory system stores SKUs.

Therefore, the programming model allows types to define multiple keys, which indicates they can be looked up in one of several ways:

```graphql
type Product @key(fields: "upc") @key(fields: "sku") {
upc: String!
sku: String!
price: String
}
```

> Note the difference from `@key(fields: "upc sku")`, a composite key, which would mean that only the combination of UPC and SKU is unique. See below.
Multiple keys are only allowed on the base type, not on type extensions. Type extensions are used to define external types, so a `@key` directive there is meant to specify which key of the base type will be used as a foreign key by the service that contains the type extension. For example, our reviews service could use `upc`:

```graphql
extend type Product @key(fields: "upc") {
upc: String! @external
reviews: [Review]
}
```

While the inventory service uses `sku`:

```graphql
extend type Product @key(fields: "sku") {
sku: ID! @external
inStock: Boolean
}
```

## Compound and nested keys

Keys may be complex and include nested fields, as when a user's ID is only unique within its organization:

```graphql
type User @key(fields: "id organization { id }") {
id: ID!
organization: Organization!
}
type Organization {
id: ID!
}
```

> Note that although the fields argument is parsed as a selection set, some restrictions apply to make the result suitable as a key. For example, fields shouldn't return lists.
## Computed fields

In many cases, what you need to resolve an extension field is a foreign key, which you specify through the `@key` directive on the type extension. With the `@requires` directive however, you can require any additional combination of fields (including subfields) from the base type that you may need in your resolver. For example, you may need access to a product's size and weight to calculate a shipping estimate:

```graphql{5}
extend type Product @key(fields: "sku") {
sku: ID! @external
size: Int @external
weight: Int @external
shippingEstimate: String @requires(fields: "size weight")
}
```

If a client requests `shippingEstimate`, the query planner will now request `size` and `weight` from the base `Product` type, and pass it through to your service, so you can access them directly from your resolver in the exact same way you would if `Product` was contained within a single service:

```js{4}
{
Product: {
shippingEstimate(product): {
return computeShippingEstimate(product.sku, product.size, product.weight);
}
}
}
```

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

In some cases, a service will be able to provide additional fields, even if these are not part of a key. For example, our review system may store the user's name in addition to the id, so we don't have to perform a separate fetch to the accounts service to get it. We can indicate which additional fields can be queried on the referenced type using the `@provides` directive:

```graphql{2,7}
type Review {
author: User @provides(fields: "username")
}
type User @key(fields: "id") {
id: ID! @external
username: String @external
}
```

The `@provides` directive acts as a hint to the gateway

```js{4}
{
Review: {
author(review) {
return { id: review.authorID, username: review.authorUsername };
}
}
}
```

This knowledge can be used by the gateway to generate a more efficient query plan and avoids a fetch to a separate service because a field is already provided. In this case, we can return the author's name as part of the fetch to the reviews service:

```graphql
query {
topReviews {
author {
username
}
}
}
```
@@ -0,0 +1,20 @@
---
title: Separation of concerns
description: Organizing schemas by features and teams
---

Apollo Federation introduces an important new principle to modular schema design: proper separation by concern. It's the key architectural quality that allows separate teams to collaborate on a single product-centric graph without stepping on each other's toes.

When you start thinking about how to break up a monolithic schema into modular parts, it can be tempting to divide things up by type. For example, you could make one module responsible for the `User` type, another one for your `Product` type, and a third one for the `Review` type:

![3 separate types divided into 3 services](../images/composition_schema-1.png)

This may seem to make sense at first, but it quickly breaks down. The problem is that features or concerns usually span types. For example, suppose we want to add a new `topNegativeReviews` field to the `Product` type in the above schema. Even though it lives on the `Product` type, this addition is almost certainly the responsibility of the team responsible for reviews. This isn't just a conceptual point about team structure: the product service won't know how to resolve a query for `topNegativeReviews`, because data about reviews is stored in the reviews system (as a reviews table with foreign key references to products, for example).

Another way to think about the same problem is to consider an important type like `User` or `Product`. Often, no single team controls every aspect of such types, and it becomes quite awkward to colocate the definitions of each field on such types.

So instead, Apollo Federation allows you to extend an existing type with additional fields, using GraphQL's `extend type` functionality. That means we can break up a schema across boundaries that correspond to features or team structure. For example, a team working on a new reviews feature could develop their changes in one place, combining new types like `Review` with extensions to existing types, like adding the ability to navigate from a `User` or `Product` to the associated reviews:

![Splitting those 3 types by data source rather than by type. Includes type extensions across services](../images/composition_schema-2.png)

The result is the best of both worlds: an implementation that keeps all the code for a given feature in a single service and separated from unrelated concerns, and a product-centric schema with rich types that reflects the natural way an application developer would want to consume the graph.

0 comments on commit 408e9a9

Please sign in to comment.
You can’t perform that action at this time.