Apollo Federation ================= What is Apollo Federation ------------------------- Apollo Federation is a technology to implement distributed GraphQL by composing multiple subgraphs into one supergraph. In the terms of Apollo Federation: #. A subgraph is a standalone GraphQL server with enabled federation support. Hiku supports Apollo Federation and can be used to implement a subgraph. #. A supergraph is a composition of multiple subgraphs into a single data graph. To run a supergraph, you need to use special gateway (or sometimes called router) that will route requests to the appropriate subgraph and combine the results into a unified response. Hiku supports both Federation v1 and v2, but it is recommended to use v2 as v1 is deprecated. For more details about Apollo Federation v2, please refer to `Apollo GraphQL Federation v2 Docs `_. The specification for subgraphs can be found here: `Apollo GraphQL Subgraph Spec `_. How it Works ------------ In a federated architecture, there are two main components: the gateway/router and the subgraphs. In order to run supergraph you need to compose one. There are two primary methods to compose a supergraph: * Using `schema registry` (managed federation in Apollo Studio is one of them), where composition happens automatically when we push new subgraph schema * Manually, using the Rover CLI, by providing a configuration file with subgraph URLs and routing URLs. The Rover CLI will then fetch remote services and compose schema into a file. Then you can run gateway/router with the composed schema and router should start routing requests to the appropriate subgraph and combine the results into a unified response. For more detailed information, please refer to the `Apollo GraphQL Federation Setup Guide `_. Setup Federation Subgraph ------------------------- .. note:: You can find the source code for this example `on GitHub `_. Let's start with a simple example of a federated subgraph using the following GraphQL schema: Order Service .. code-block:: graphql type Order @key(fields: "id") { id: ID! status: Int! cartId: Int! } type Query { order: [Order!]! } Shopping Cart Service .. code-block:: graphql type ShoppingCart @key(fields: "id") { id: ID! items: [ShoppingCartItem!]! } type ShoppingCartItem { id: ID! productName: String! price: Int! quantity: Int! } type Query { cart(id: ID!): ShoppingCart } Now let's implement the ``Order`` service using Hiku: .. literalinclude:: federation/order_service.py :lines: 1-76 * Using ``hiku.federation.graph.Graph`` instead of ``hiku.graph.Graph`` * Using ``hiku.federation.graph.FederatedNode`` instead of ``hiku.graph.Node`` * Also using ``hiku.federation.schema.Schema`` instead of ``hiku.schema.Schema`` * By default, the federation version is set to 2. To enable v1, you need to pass ``federation_version=1`` to the ``Schema`` constructor. We define the ``Order`` type with the ``@key`` directive. This directive specifies the primary key of the type. In our case, ``id`` is the primary key of the ``Order`` type. ``Router`` now knows, that in order for it to fetch an order, it needs to provide the ``id`` field value when requesting ``Order`` service. ``Router`` then joins different parts of data from different subgraphs into one response using the ``Key``. A type can have many ``Key`` directives. We define ``cartId`` as another key. This will allow us to join ``Order`` and ``ShoppingCart`` types together. Also we define ``resolve_reference`` function which in our case is ``resolve_order_reference``. A ``resolve_reference`` function works very similar to ``Link`` resolver - it returns a list of values that will be passed to ``Node`` as a root values. .. note:: Since we want to pass representations as is to ``Node`, we need to convert each representation to ``ImmutableDict`` because ``resolve_reference`` function must return **hashable** values just like ``Link`` resolver. Then in ``order_fields_resolver`` we fetch orders either by ``id`` or by ``cartId``. .. note:: In the example above, we fetch orders by one. In real-world applications, you would fetch orders in butches to avoid N+1. Next, let's implement the ``ShoppingCart`` service using Hiku: .. literalinclude:: federation/cart_service.py :lines: 1-116 In the ``ShoppingCart`` service, we define the ``ShoppingCart`` and ``ShoppingCartItem`` types. But also, we define a stub ``Order`` type. This is needed because we want to extend the ``Order`` type with a ``cart`` field. In the ```Order`` type, we specify ``cartId`` as a key. This will allow us to join ``Order`` and ``ShoppingCart`` types together. Now we need to compose subgraph schemas into a supergraph schema and run an instance of the router. Start the ``Order`` service on port 4001 and the ``ShoppingCart`` service on port 4002. Apollo Router ------------- With our services up and running, we need to configure a gateway to consume our services. Apollo provides a router for this purpose. Before proceeding, install the Apollo Router by following their `installation guide `_. Also, install Apollo's CLI (rover) `here `_ to compose the schema. Create a file named `supergraph.yaml` with the following contents: .. code-block:: yaml federation_version: 2.3 subgraphs: order: routing_url: http://localhost:4001/graphql schema: subgraph_url: http://localhost:4001/graphql shopping_cart: routing_url: http://localhost:4002/graphql schema: subgraph_url: http://localhost:4002/graphql This file will be used by Rover to compose the schema, which can be done with the following command: .. code-block:: bash rover supergraph compose --config ./supergraph.yaml > supergraph-schema.graphql With the composed schema, we can now start the router: .. code-block:: bash ./router --supergraph supergraph-schema.graphql With the router running, visit http://localhost:4000 and try running the following query: .. code-block:: graphql { order(id: 1) { id status cart { id items { id productName price } } } }