-
-
Notifications
You must be signed in to change notification settings - Fork 437
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
516 additions
and
198 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
# Entity representation | ||
|
||
To reference an entity that originates in another subgraph, first subgraph needs to define a stub of that entity to make its own schema valid. | ||
The stub includes just enough information for the subgraph to know how to uniquely identify a particular entity in another subgraph. | ||
Read more about the entity representation in the [Apollo Federation docs](https://www.apollographql.com/docs/federation/v1/entities/#entity-representations). | ||
|
||
A representation always consists of: | ||
- A `__typename` field; | ||
- Values for the entity's primary key fields. | ||
|
||
## Eloquent Model representation | ||
|
||
If there is an Eloquent relationship between entities from different subgraphs, it's not mandatory to define an entity representation. | ||
Lighthouse will automatically determine the necessary information based on the relationship directive. | ||
|
||
```graphql | ||
type Post { | ||
id: ID! | ||
title: String! | ||
comments: [Comment!]! @hasMany | ||
} | ||
|
||
type Comment @extends @key(fields: "id") { | ||
id: ID! @external | ||
} | ||
``` | ||
|
||
## Non-Eloquent representation | ||
|
||
If entities don't have an Eloquent relationship within the subgraph, it's necessary to specify a separate resolver that will return the required information. | ||
The resolver should return data containing information about the `__typename` field, which corresponds to the entity's name and the primary key that can identify the entity. | ||
The `__typename` can either be provided as an explicit field or implicitly by returning an object with a matching class name. | ||
|
||
### Example 1 | ||
|
||
In this example, subgraph for order service has an entity called `Order`, which in turn has an entity called `Receipt` | ||
defined in a separate subgraph for payment service. The relationship between `Order` and `Receipt` is one-to-one. | ||
|
||
```graphql | ||
type Order { | ||
id: ID! | ||
receipt: Receipt! | ||
} | ||
|
||
type Receipt @extends @key(fields: "uuid") { | ||
uuid: ID! @external | ||
} | ||
``` | ||
|
||
The resolver for receipt in order service returns an array consisting of: | ||
- `__typename` - the entity name from the payment service; | ||
- `uuid` - the primary key of the receipt. | ||
|
||
```php | ||
namespace App\GraphQL\Types\Order; | ||
|
||
final class Receipt | ||
{ | ||
public function __invoke($order): array | ||
{ | ||
return [ | ||
'__typename' => 'Receipt', | ||
'uuid' => $order->receipt_id, | ||
]; | ||
} | ||
} | ||
``` | ||
|
||
### Example 2 | ||
|
||
In this example, subgraph for order service has an entity called `Order`, which in turn has an entity called `Product` | ||
defined in a separate subgraph for product service. The relationship between `Order` and `Product` is one-to-many. | ||
|
||
```graphql | ||
type Order { | ||
sum: Int! | ||
products: [Product!]! | ||
} | ||
|
||
type Product @extends @key(fields: "uuid") { | ||
uuid: ID! @external | ||
} | ||
``` | ||
|
||
The resolver for product in order service returns an array of arrays. Each sub-array consists of: | ||
- `__typename` - the entity name from the product service; | ||
- `uuid` - the primary key of a specific product. | ||
|
||
```php | ||
namespace App\GraphQL\Types\Order; | ||
|
||
final class Products | ||
{ | ||
public function __invoke($order): iterable | ||
{ | ||
return ProductService::retrieveProductsForOrder($order) | ||
->map(fn (Product $product): array => [ | ||
'__typename' => 'Product', | ||
'uuid' => $product->id, | ||
]); | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
# Reference resolvers | ||
|
||
To enable the current subgraph to provide entities for other subgraphs, you need to implement reference resolvers. These | ||
reference resolvers act as helpers to enable cross-subgraph communication and provide data from one subgraph to another | ||
when needed. Read more about the reference resolvers in | ||
the [Apollo Federation docs](https://www.apollographql.com/docs/federation/v1/entities#reference-resolvers). | ||
|
||
Lighthouse will look for a class which name is equivalent to `__typename` in the | ||
namespace configured in `lighthouse.federation.entities_resolver_namespace`. | ||
|
||
When you need to retrieve information from subgraphs, the gateway automatically generates a request to the corresponding | ||
endpoint of the subgraph. More details about this can be found in | ||
section [Query._entities of the Apollo Federation docs](https://www.apollographql.com/docs/federation/building-supergraphs/subgraphs-overview#query_entities). | ||
|
||
An example of such a request is shown below: | ||
|
||
```graphql | ||
{ | ||
_entities(representations: [{ __typename: "Foo", id: 1 }]) { | ||
... on Foo { | ||
id | ||
} | ||
} | ||
} | ||
``` | ||
|
||
## Single Entity Resolvers | ||
|
||
After validating that type `Foo` exists, Lighthouse will look for a resolver class in | ||
the namespace configured in `lighthouse.federation.entities_resolver_namespace`. The resolver class is expected to | ||
contain a method `__invoke()` which takes a single argument: the array form of the representation. | ||
|
||
```php | ||
namespace App\GraphQL\ReferenceResolvers; | ||
|
||
final class Foo | ||
{ | ||
/** @param array{__typename: string, id: int} $representation */ | ||
public function __invoke(array $representation) | ||
{ | ||
// TODO return a value that matches type Foo | ||
} | ||
} | ||
``` | ||
|
||
The method should return an object that has the same name as the entity, or additionally return the field `__typename`. | ||
|
||
```php | ||
namespace App\GraphQL\ReferenceResolvers; | ||
|
||
use App\Repositories\FooRepository; | ||
use Illuminate\Support\Arr; | ||
|
||
final class Foo | ||
{ | ||
public function __invoke($representation): array | ||
{ | ||
$id = Arr::get($representation, 'id'); | ||
$foo = FooRepository::byID($id)->toArray(); | ||
|
||
return Arr::add($foo, '__typename', 'Foo'); | ||
} | ||
} | ||
``` | ||
|
||
## Batched Entity Resolvers | ||
|
||
When the client requests a large number of entities with the same type, it can be more efficient to resolve | ||
them all at once. When your entity resolver class implements `Nuwave\Lighthouse\Federation\BatchedEntityResolver`, | ||
Lighthouse will call it a single time with an array of all representations of its type. The resolver can then do | ||
some kind of batch query to resolve them and return them all at once. | ||
|
||
```php | ||
namespace App\GraphQL\ReferenceResolvers; | ||
|
||
use Nuwave\Lighthouse\Federation\BatchedEntityResolver; | ||
|
||
final class Foo implements BatchedEntityResolver | ||
{ | ||
/** | ||
* @param array<string, array{__typename: string, id: int}> $representations | ||
*/ | ||
public function __invoke(array $representations): iterable | ||
{ | ||
// TODO return multiple values that match type Foo | ||
} | ||
} | ||
``` | ||
|
||
The returned iterable _must_ have the same keys as the given `array $representations` to enable Lighthouse | ||
to return the results in the correct order. | ||
|
||
```php | ||
namespace App\GraphQL\ReferenceResolvers; | ||
|
||
use App\Repositories\ProductRepository; | ||
use Illuminate\Support\Arr; | ||
use Nuwave\Lighthouse\Federation\BatchedEntityResolver; | ||
|
||
final class Product implements BatchedEntityResolver | ||
{ | ||
public function __invoke(array $representations): iterable | ||
{ | ||
$products = ProductRepository::byIDs(Arr::pluck($representations, 'id')); | ||
|
||
$result = []; | ||
foreach ($representations as $key => $representation) { | ||
$result[$key] = $products->firstWhere('id', $representation['id']); | ||
} | ||
|
||
return $result; | ||
} | ||
} | ||
``` | ||
|
||
## Eloquent Model Resolvers | ||
|
||
When no resolver class can be found, Lighthouse will attempt to find the model that | ||
matches the type `__typename`, using the namespaces configured in `lighthouse.namespaces.models`. | ||
|
||
```graphql | ||
{ | ||
_entities(representations: [{ __typename: "Foo", bar: "asdf", baz: 42 }]) { | ||
... on Foo { | ||
id | ||
} | ||
} | ||
} | ||
``` | ||
|
||
The additional fields in the representation constrain the query builder, which is then | ||
called and expected to return a single result. In simplified terms, Lighthouse will do this: | ||
|
||
```php | ||
$results = App\Models\Foo::query() | ||
->where('bar', 'asdf') | ||
->where('baz', 42) | ||
->get(); | ||
|
||
if ($results->count() > 1) { | ||
throw new GraphQL\Error\Error('The query returned more than one result.'); | ||
} | ||
|
||
return $results->first(); | ||
``` | ||
|
||
The default model resolver makes one database query for each entity. Therefore, for a large number of entities, it is | ||
worth considering [Batched Entity Resolvers](reference-resolvers.md#batched-entity-resolvers) to avoid this issue. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.