Apollo Federation 2 Guide
This guide refers to Apollo Federation 2, if you're looking for the 1.0 guide, please see the federation v1 guide.
Apollo Federation allows you to combine multiple GraphQL APIs into one. This can be extremely useful when working with a service oriented architecture.
Strawberry supports Apollo Federation 2 out of the box, that means that you can create services using Strawberry and federate them via Apollo Gateway or Apollo Router.
Federated schema example
Letβs look at an example on how to implement Apollo Federation using Strawberry. Let's assume we have an application with two services that each expose a GraphQL API:
books
: a service to manage all the books we havereviews
: a service to manage book reviews
Our folder structure will look something like this:
my-app/
ββ books/
β ββ app.py
ββ reviews/
β ββ app.py
This guide assumes you've installed strawberry in both the books and reviews service
Books service
Let's create the books
service, copy the following inside books/app.py
from typing import List
import strawberry
@strawberry.federation.type(keys=["id"])class Book: id: strawberry.ID title: str
def get_all_books() -> List[Book]: return [Book(id=strawberry.ID("1"), title="The Dark Tower")]
@strawberry.typeclass Query: all_books: List[Book] = strawberry.field(resolver=get_all_books)
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
enable_federation_2=True
is used to enable Apollo Federation 2 and currently
defaults to False
. This will change in a future version of Strawberry.
We defined two types: Book
and Query
, where Query
has only one field that
allows us to fetch all the books.
Notice that the Book
type is using the strawberry.federation.type
decorator,
as opposed to the normal strawberry.type
, this new decorator extends the base
one and allows us to define federation-specific attributes on the type.
Here, we are telling the federation system that the Book
's id
field is its
uniquely-identifying key.
Federation keys can be thought of as primary keys. They are used by the gateway to query types between multiple services and then join them into the augmented type.
Reviews service
Now, letβs take a look at our review service: we want to define a type for a
review but also extend the Book
type to have a list of reviews.
Copy the following inside reviews/app.py
:
from typing import List
import strawberry
@strawberry.typeclass Review: id: int body: str
def get_reviews(root: "Book") -> List[Review]: return [ Review(id=id_, body=f"A review for {root.id}") for id_ in range(root.reviews_count) ]
@strawberry.federation.type(keys=["id"])class Book: id: strawberry.ID reviews_count: int reviews: List[Review] = strawberry.field(resolver=get_reviews)
@classmethod def resolve_reference(cls, id: strawberry.ID): # here we could fetch the book from the database # or even from an API return Book(id=id, reviews_count=3)
@strawberry.typeclass Query: _hi: str = strawberry.field(resolver=lambda: "Hello World!")
schema = strawberry.federation.Schema(query=Query, types=[Book, Review], enable_federation_2=True)
Now things are looking more interesting; the Review
type is a GraphQL type
that holds the contents of the review.
But we also have a Book
which has 3 fields, id
, reviews_count
and
reviews
.
In Apollo Federation 1 we'd need to mark the Book
type as an extension and
also we'd need to mark id
as an external field, this is not the case in Apollo
Federation 2.
Finally, we also have a class method, resolve_reference
, that allows us to
instantiate types when they are referred to by other services. The
resolve_reference
method is called when a GraphQL operation references an
entity across multiple services. For example, when making this query:
{ # query defined in the books service books { title # field defined in the reviews service reviews { body } }}
resolve_reference
is called with the id
of the book for each book returned
by the books service. Recall that above we defined the id
field as the key
for the Book
type. In this example we are creating an instance of Book
with
the requested id
and a fixed number of reviews.
If we were to add more fields to Book
that were stored in a database, this
would be where we could perform queries for these fields' values.
We also defined a Query
type that has a single field, _hi
, which returns a
string. This is required because the GraphQL spec mandates that a GraphQL server
defines a Query type, even if it ends up being empty/unused.
Finally we also need to let Strawberry know about our Book and Review types.
Since they are not reachable from the Query
field itself, Strawberry won't be
able to find them.
If you don't need any custom logic for your resolve_reference, you can omit it
and Strawberry will automatically instanciate the type for you. For example,
if we had a Book
type with only an id
field, Strawberry would be able to
instanciate it for us based on the data returned by the gateway.
@strawberry.federation.type(keys=["id"])class Book: id: strawberry.ID reviews: List[Review] = strawberry.field(resolver=get_reviews)
Let's run our services
Before starting Apollo Router to compose our schemas we need to run the services.
In two terminal windows, run the following commands:
cd booksstrawberry server --port 3500 app
cd reviewsstrawberry server --port 3000 app
Apollo Router
Now we have our services up and running, we need to configure a gateway to consume our services. Apollo provides a router that can be used for this.
Before continuing we'll need to install Apollo Router by following their installation guide and we'll need to install Apollo's CLI to compose the schema.
Composing the schema means combining all our service's schemas into a single schema. The composed schema will be used by the router to route requests to the appropriate services.
Create a file called supergraph.yaml
with the following contents:
federation_version: 2subgraphs: reviews: routing_url: http://localhost:3000 schema: subgraph_url: http://localhost:3000
books: routing_url: http://localhost:3500 schema: subgraph_url: http://localhost:3500
This file will be used by rover to compose the schema, which can be done with the following command:
# Creates prod-schema.graphql or overwrites if it already existsrover supergraph compose --config ./supergraph.yaml > supergraph-schema.graphql
Now that we have the composed schema, we can start the router.
./router --supergraph supergraph-schema.graphql
Now that router is running we can go to http://localhost:4000 and try to run the following query:
{ allBooks { id reviewsCount reviews { body } }}
if everything went well we should get the following result:
{ "data": { "allBooks": [ { "id": "1", "reviewsCount": 3, "reviews": [ { "body": "A review for 1" }, { "body": "A review for 1" }, { "body": "A review for 1" } ] } ] }}
We have provided a full example that you can run and tweak to play with Strawberry and Federation. The repo is available here: https://github.com/strawberry-graphql/federation-demo