Apollo RouterでGraphQLサーバーをマイクロサービス構成にする

## まえがき

Apollo Routerを使用するとGraphQL + マイクロサービスなシステム開発ができそうだったので、実際に単純なアプリを作成して試してみました。

## 対象読者

- GraphQLについて基礎的な知識がある方
- Apollo Federation及びApollo Routerを使用してみたい方
- GraphQLを使用してマイクロサービスを構築したい方

---

## この記事のゴール

今回はApollo Routerを使用して商品のレビューを作成できるアプリを開発します。

条件として
- マイクロサービスで作成する
- 各アプリケーションはGraphQLで通信する
- DBは何でも良いが、関係性の解決はApollo Routerにやらせる

を満たすように作成していきます。

データモデルは最低限に留めるため、以下3点のみ作成します。
- レビューを行うユーザー
- レビュー対象の商品
- レビュー自体

具体的には以下の構成図のようなアーキテクチャでアプリを構築していきます。

![archtecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730831/db14f33d-275d-88d4-014c-2b9f409a44af.png)

## 用語解説

### Apollo Routerとは
[Appollo Router](https://www.apollographql.com/docs/router/)とは**Supergraph**を扱うためのRust製のグラフルーターで、**Elastic Licence v2.0**で保護されたOSSです。

尚、**Supergraph**のようなApollo Federationの専門用語が多く出てきますので、詳しくは以下の**Apollo Federation**についての節で説明していきます。


### Apollo Federation とは

[公式ページ](https://www.apollographql.com/docs/federation/)によると… 

> Apollo Federation is a powerful, open architecture for creating a supergraph that combines multiple GraphQL APIs

とのことで、個別のGraphQLスキーマを1つに合体させることができるアーキテクチャを指します。

合体させる元になっている個別のGraphQLスキーマを**Subgraph**、合体後の1つになったGraphQLスキーマを**Supergraph**と言います。

どちらもGraphQLのDSLにApollo Federation特有のディレクティブを用いることで表現します。

以下は公式ページから一部だけ抜粋してきたものです。
```graphql
# subgraph
extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0",
        import: ["@key", "@shareable", "@provides", "@external"])

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

# supergraph
schema
  @link(url: "https://specs.apollo.dev/link/v1.0")
  @link(url: "https://specs.apollo.dev/join/v0.2", for: EXECUTION)
{
  query: Query
}

directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR

type Product
  @join__type(graph: PRODUCTS, key: "upc")
  @join__type(graph: REVIEWS, key: "upc")
{
  upc: String!
  name: String! @join__field(graph: PRODUCTS)
  price: Int @join__field(graph: PRODUCTS)
  reviews: [Review] @join__field(graph: REVIEWS)
}

```

@keyや@join__typeと言った専用のディレクティブを使用しているのがわかるかと思います。
それぞれの意味は**実際に使ってみる**の章で説明していきたいと思います。

## 実際に使ってみる

### 開発環境

それでは実際にApollo Federationを使用してアプリを開発していきたいと思います。

今回使用した開発環境は以下のとおりです。

- OS: macOS Ventura 13.0.1
- IDE: VS Code 1.75.0
- Docker Engine: v20.10.22
- App Platform: Deno v1.30.3

### 各サービスアプリの開発

#### スキーマ定義を作成する

まずはじめにGraphQLでスキーマ定義を作成していきます。
Subgraph -> Supergraphの順で作成していきます。


##### Subgraphを作成する

最初にレビューを書くユーザーのスキーマ定義から作成します。

```graphql
extend schema
  @link(
    url: "https://specs.apollo.dev/federation/v2.0"
    import: ["@key"]
  )

type Query {
  getUser(id: ID!): User
}

type Mutation {
  createUser(name: String!): User
}

type User @key(fields: "id") {
  id: ID!
  name: String!
}
```

ユーザー情報は名前だけの簡素な作りになっています。

**extend schema**の行でApollo Federationから特有のディレクティブである@keyをインポートしています。

@keyディレクティブは型定義に付与することで**エンティティ**を定義することができます。

エンティティの使い方はSupergraphを定義する際に説明します。

続いて、レビュー対象である商品のスキーマを定義します。

```graphql
extend schema
  @link(
    url: "https://specs.apollo.dev/federation/v2.0"
    import: ["@key", "@shareable"]
  )

type Query {
  getProduct(id: ID!): Product
}

type Mutation {
  createProduct(name: String!, price: Int!): Product
}

type Product @key(fields: "id") {
  id: ID!
  name: String!
  price: Int!
}
```

こちらも名前と値段だけの簡素な作りになっています。

続いて、レビューそのもののスキーマを定義します。

```graphql
extend schema
  @link(
    url: "https://specs.apollo.dev/federation/v2.0"
    import: ["@key", "@shareable", "@provides", "@external"]
  )

type Query {
  getReview(id: ID!): Review
  latestReviews: [Review!]!
}

type Mutation {
  createReview(
    productId: ID!
    userId: ID!
    score: Int!
    description: String!
  ): Review
}

type Product @key(fields: "id") {
  id: ID!
}

type User @key(fields: "id") {
  id: ID!
}

type Review {
  id: ID!
  score: Int!
  description: String!
  product: Product!
  user: User!
}
```

こちらは先の2つとは異なり、エンティティ同士の関連が定義されています。

また、別のスキーマで定義されているユーザーと商品のIDだけを定義しています。
これはApollo Federationが**関心ごとの分離**を設計原則として推奨しているため、それに倣って作成しています。

簡単に説明すると、レビューそのものを管理するアプリからは**誰がどの商品に対して作成したレビューであるのか**という部分だけが表現できれば良いので、その他の情報を省いています。

ただ、データを取得するときは一緒に取得できた方がAPI的にも利便性が高いので、そこは後述のSupergraphで表現していきます。

##### Supergraphを作成する

さて、ここまででアプリを作成するのに必要なSubgraphを作成できたので、**Supergraph**を作成してスキーマを連結していきたいと思います。

実際に作成したSupergraphが以下になります。

```graphql
schema
  @link(url: "https://specs.apollo.dev/link/v1.0")
  @link(url: "https://specs.apollo.dev/join/v0.2", for: EXECUTION) {
  query: Query
}

directive @join__field(
  graph: join__Graph!
  requires: join__FieldSet
  provides: join__FieldSet
  type: String
  external: Boolean
  override: String
  usedOverridden: Boolean
) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION

directive @join__graph(name: String!, url: String!) on ENUM_VALUE

directive @join__implements(
  graph: join__Graph!
  interface: String!
) repeatable on OBJECT | INTERFACE

directive @join__type(
  graph: join__Graph!
  key: join__FieldSet
  extension: Boolean! = false
  resolvable: Boolean! = true
) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR

directive @link(
  url: String
  as: String
  for: link__Purpose
  import: [link__Import]
) repeatable on SCHEMA

scalar join__FieldSet

enum join__Graph {
  USERS @join__graph(name: "users", url: "http://user-app")
  PRODUCTS @join__graph(name: "products", url: "http://product-app")
  REVIEWS @join__graph(name: "reviews", url: "http://review-app")
}

scalar link__Import

enum link__Purpose {
  """
  `SECURITY` features provide metadata necessary to securely resolve fields.
  """
  SECURITY
  """
  `EXECUTION` features provide metadata necessary for operation execution.
  """
  EXECUTION
}

type Mutation {
  createUser(name: String!): User @join__field(graph: USERS)
  createProduct(name: String!, price: Int!): Product
    @join__field(graph: PRODUCTS)
  createReview(
    productId: ID!
    userId: ID!
    score: Int!
    description: String!
  ): Review @join__field(graph: REVIEWS)
}

type User
  @join__type(graph: USERS, key: "id")
  @join__type(graph: REVIEWS, key: "id") {
  id: ID!
  name: String! @join__field(graph: USERS)
}

type Query
  @join__type(graph: REVIEWS)
  @join__type(graph: PRODUCTS)
  @join__type(graph: USERS) {
  getUser(id: ID!): User @join__field(graph: USERS)
  getProduct(id: ID!): Product @join__field(graph: PRODUCTS)
  getReview(id: ID!): Review @join__field(graph: REVIEWS)
  latestReviews: [Review] @join__field(graph: REVIEWS)
}

type Review @join__type(graph: REVIEWS, key: "id") {
  id: ID!
  score: Int!
  description: String!
  product: Product! @join__field(graph: REVIEWS)
  user: User! @join__field(graph: REVIEWS)
}

type Product
  @join__type(graph: PRODUCTS, key: "id")
  @join__type(graph: REVIEWS, key: "id") {
  id: ID!
  name: String! @join__field(graph: PRODUCTS)
  price: Int! @join__field(graph: PRODUCTS)
}
```

Subgraphに比べるとだいぶ記述量が増えてきました。
専用のディレクティブも大量に追加されています。

以降で重要な箇所をピックアップして解説していきたいと思います。

###### Subgraphの解決先の指定

SupergraphはSubgraphを連結して定義するものですが、実際にリクエストを受けて解決するのは各Subgraphのアプリケーションが行います。
そのため、各アプリケーションの解決先となるエンドポイントを定義する必要があります。

```graphql
enum join__Graph {
  USERS @join__graph(name: "users", url: "http://user-app")
  PRODUCTS @join__graph(name: "products", url: "http://product-app")
  REVIEWS @join__graph(name: "reviews", url: "http://review-app")
}
```

上記に示したように、@join__graphディレクティブを使用して、エンドポイントとSupergraph上での名前を設定していきます。

###### エンティティの結合

Subgraphを作成する過程で、@keyディレクティブを使用してエンティティを定義してきました。
Supergraphでは@keyで定義したキー情報を元にエンティティを結合します。

```graphql
type Product
  @join__type(graph: PRODUCTS, key: "id")
  @join__type(graph: REVIEWS, key: "id") {
  id: ID!
  name: String! @join__field(graph: PRODUCTS)
  price: Int! @join__field(graph: PRODUCTS)
}
```

上記の例で言うと、商品スキーマとレビュースキーマのエンティティを統合して新たに商品スキーマを定義しています。この際、@join__typeディレクティブでどこのSubgraphに定義されたエンティティなのかとkeyが何であるかを記述する必要があります。

また、共通項目でないプロパティが存在する場合、どのSubgraphから取得できる情報であるかを@join__fieldディレクティブで定義する必要があります。

@join__fieldディレクティブはQueryやMutationがそれぞれどのSubgraphで定義されているものなのかを定義するのにも使用します。

```graphql
type Query
  @join__type(graph: REVIEWS)
  @join__type(graph: PRODUCTS)
  @join__type(graph: USERS) {
  getUser(id: ID!): User @join__field(graph: USERS)
  getProduct(id: ID!): Product @join__field(graph: PRODUCTS)
  getReview(id: ID!): Review @join__field(graph: REVIEWS)
  latestReviews: [Review] @join__field(graph: REVIEWS)
}

type Mutation {
  createUser(name: String!): User @join__field(graph: USERS)
  createProduct(name: String!, price: Int!): Product
    @join__field(graph: PRODUCTS)
  createReview(
    productId: ID!
    userId: ID!
    score: Int!
    description: String!
  ): Review @join__field(graph: REVIEWS)
}
```

#### バックエンドアプリを実装する

ここまでで、GraphQLスキーマ定義の作成が完了したので、GraphQLリクエストを受け付けるアプリを実装していきます。

基本的には以下のライブラリを使用して、GraphqlサーバーをDeno向けに実装していきます。
- Apollo Server
- @graphql-tools/load-files
- denodb

以下に実際に実装したサーバーアプリのソースを記載します。
（内容はほぼ一緒なので、代表して商品アプリのソースを載せます）

```typescript
import { Env } from "https://deno.land/x/env@v2.2.0/env.js";
import { ApolloServer } from "npm:@apollo/server";
import { startStandaloneServer } from "npm:@apollo/server/standalone";
import { buildSubgraphSchema } from "npm:@apollo/subgraph";
import { loadFiles } from "npm:@graphql-tools/load-files";
import {
  Database,
  PostgresConnector,
} from "https://deno.land/x/denodb@v1.2.0/mod.ts";
import Product from "./models/product.ts";

const env = new Env();

const connector = new PostgresConnector({
  database: env.require("DATABASE_NAME"),
  host: env.require("DATABASE_HOST"),
  username: env.require("DATABASE_USER"),
  password: env.require("DATABASE_PASS"),
  port: 5432,
});

const typeDefs = await loadFiles("schema.graphql");

const resolvers = {
  Query: {
    async getProduct(parent: any, args: any, context: any, info: any) {
      return await Product.find(args.id);
    },
  },
  Mutation: {
    async createProduct(parent: any, args: any, context: any, info: any) {
      let newProduct = new Product();
      newProduct.name = args.name;
      newProduct.price = args.price;
      return await newProduct.save();
    },
  },
  Product: {
    async __resolveReference(product: Product, context: any) {
      return await Product.find(product.id);
    },
  },
};

if (import.meta.main) {
  const server = new ApolloServer({
    schema: buildSubgraphSchema({ typeDefs, resolvers }),
  });

  const db = new Database(connector);
  await db.link([Product]);
  await db.sync();

  const { url } = await startStandaloneServer(server, {
    listen: env.require("APP_PORT"),
  });
  console.log(`🚀  Server ready at ${url}`);
}
```

GraphQL APIとしての実装は**resolvers**の部分になります。
基本的には、QueryやMutation内にGraphQLスキーマ定義で定義したクエリに対するメソッドを実際に実装します。

今回はリクエスト内容のデータを登録する or 参照すると言った簡単な機能だけを実装しているため、処理内容はDBへの登録、参照のみです。

余談ですが、DBアクセスはdenodbを使用してモデルクラスを作成することで実装しています。
例えば、商品モデルのモデルクラスは以下の様になります。

```typescript
import { DataTypes, Model } from "https://deno.land/x/denodb@v1.2.0/mod.ts";

export default class Product extends Model {
  static table = "products";

  static fields = {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true,
    },
    name: DataTypes.STRING,
    price: DataTypes.INTEGER,
  };

  id!: number;
  name!: string;
  price!: number;
}
```

Subgraphの実装は、通常のリゾルバと異なる部分があります。
それは、エンティティ用のプロパティと **__resolveReference** メソッドです。

```typescript
  Product: {
    async __resolveReference(product: Product, context: any) {
      return await Product.find(product.id);
    },
  },
```

この部分ではSupergraphによってエンティティを解決する際に使用されるメソッドを定義します。
上記の例では商品エンティティを解決する際の問い合わせとしてidを渡されるため、それを元にDBから取得したデータを返却する実装を行なっています。
idの部分を他の値にしたい場合は、GraphQLスキーマで@keyディレクティブに設定するフィールドを変更します。

## 動かしてみる

### コンテナの準備

アプリケーションの実装までできたので、ここからは実際にアプリケーションを動かしてSupergraphが期待通り動作するのか確認していきたいと思います。

今回はマイクロサービスアーキテクチャをとっていることもあるので、アプリの構築にdocker-composeを使用します。

#### Apollo Router 用コンテナ定義の作成

早速、Apollo Routerのコンテナを用意していきます。
Dockerfileの内容は、[Apollo GraphQLのGitHubページ](https://github.com/apollographql/router/blob/main/dockerfiles/Dockerfile.router)を参考に作成します。

```Dockerfile
FROM debian:bullseye-slim

ARG ROUTER_RELEASE=latest
ARG DEBUG_IMAGE=false

WORKDIR /dist

# Install curl
RUN \
  apt-get update -y \
  && apt-get install -y \
  curl

# If debug image, install heaptrack and make a data directory
RUN \
  if [ "${DEBUG_IMAGE}" = "true" ]; then \
  apt-get install -y heaptrack && \
  mkdir data; \
  fi

# Clean up apt lists
RUN rm -rf /var/lib/apt/lists/*

# Run the Router downloader which puts Router into current working directory
RUN curl -sSL https://router.apollo.dev/download/nix/${ROUTER_RELEASE}/ | sh

# Make directories for config and schema
RUN mkdir config schema

# Copy configuration for docker image
COPY router.yml config

LABEL org.opencontainers.image.authors="Apollo Graph, Inc. https://github.com/apollographql/router"
LABEL org.opencontainers.image.source="https://github.com/apollographql/router"

ENV APOLLO_ROUTER_CONFIG_PATH="/dist/config/router.yaml"

COPY supergraph-schema.graphql schema

# Create a wrapper script to run the router, use exec to ensure signals are handled correctly
RUN \
  echo '#!/usr/bin/env bash \
  \nset -e \
  \n \
  \nif [ -f "/usr/bin/heaptrack" ]; then \
  \n    exec heaptrack -o /dist/data/router_heaptrack /dist/router "$@" \
  \nelse \
  \n    exec /dist/router "$@" \
  \nfi \
  ' > /dist/router_wrapper.sh

# Make sure we can run our wrapper
RUN chmod 755 /dist/router_wrapper.sh

# Default executable is the wrapper script
ENTRYPOINT ["/dist/router_wrapper.sh", "--config", "/dist/config/router.yml", "--supergraph", "/dist/schema/supergraph-schema.graphql"]
```

注意点として、ここまでで作成したSupergraphのスキーマ定義ファイルが同梱されるように作成します。（Apollo RouterはSupergraphのスキーマ定義を読み込んで動作するため）

また、併せてApollo Router用の設定ファイルも同梱します。今回はApollo Sandboxを使用して動作確認を行いたいので、最低限の設定を記述していきます。

```yaml
# ---router.yml---

# Configuration of the router's HTTP server
# Default configuration for container
supergraph:
  # The socket address and port to listen on
  listen: 0.0.0.0:80
  introspection: true

sandbox:
  enabled: true

# Sandbox requires the default landing page to be disabled.
homepage:
  enabled: false

```



#### Subgraphアプリケーション用コンテナ定義の作成

次にApollo Routerからリクエストを受け取るバックエンドアプリ用のコンテナ定義を作成します。
Denoが動かせる内容であれば良いので、[denolandのイメージ](https://hub.docker.com/r/denoland/deno)にアプリを同梱する形で定義します。

```Dockerfile
FROM denoland/deno:1.30.3

# The port that your application listens to.
EXPOSE 80

WORKDIR /app

# Prefer not to run as root.
USER deno

# These steps will be re-run upon each file change in your working directory:
COPY src/ .
# Compile the main app so that it doesn't need to be compiled each startup/entry.
RUN deno cache main.ts

CMD ["run", "--allow-net", "--allow-env", "--allow-read", "main.ts"]
```

#### docker-compose.ymlの作成

最後に、docker-composeファイルに作成したコンテナ定義をまとめて完成です。

```docker
version: '3.1'
services:
  db:
    image: postgres
    restart: always
    ports:
      - 5432:5432
    environment:
      POSTGRES_DB: $DATABASE_NAME
      POSTGRES_PASSWORD: $DATABASE_PASS
      POSTGRES_USER: $DATABASE_USER
  
  router:
    build:
      context: .
      dockerfile: ./Dockerfile.router
    ports:
        - 8080:80
    depends_on:
      - user-app
      - product-app
      - review-app

  user-app:
    build:
      context: users
      dockerfile: ../Dockerfile.deno
    environment:
      DATABASE_NAME: $DATABASE_NAME
      DATABASE_USER: $DATABASE_USER
      DATABASE_PASS: $DATABASE_PASS
      DATABASE_HOST: db
      APP_PORT: $APP_PORT
    ports:
      - 80
    depends_on:
      - db
  
  product-app:
    build:
      context: products
      dockerfile: ../Dockerfile.deno
    environment:
      DATABASE_NAME: $DATABASE_NAME
      DATABASE_USER: $DATABASE_USER
      DATABASE_PASS: $DATABASE_PASS
      DATABASE_HOST: db
      APP_PORT: $APP_PORT
    ports:
      - 80
    depends_on:
      - db

  review-app:
    build:
      context: reviews
      dockerfile: ../Dockerfile.deno
    environment:
      DATABASE_NAME: $DATABASE_NAME
      DATABASE_USER: $DATABASE_USER
      DATABASE_PASS: $DATABASE_PASS
      DATABASE_HOST: db
      APP_PORT: $APP_PORT
    ports:
      - 80
    depends_on:
      - db

volumes:
  graphql-supergraph-test-vol:

```

### 動作確認

以上で準備が整いましたので、早速 **docker-compose up -d** を実行してアプリを起動します。

起動が完了すると、Apollo Routerが http://localhost:8080 で待ち受けるのでブラウザで開きます。

今回はApollo Sandboxを有効にしたので、以下の様な画面が表示されます。

![スクリーンショット 2023-02-27 13.16.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730831/9c9e74aa-a6e5-52c3-39df-8d5c10646920.png)

この画面でGraphQLのクエリを作成して実行ボタンを押すことで、Apollo Routerにクエリを投げることができます。

返却されたレスポンスは右側のResponseパネルに表示されます。

では、実際にクエリを投げて結果を確認してみましょう。

#### ユーザーの作成

まずはレビューを作成するためにユーザーを作成します。
実行するクエリは以下を使用します。

```graphql
mutation CreateUser {
    createUser(name: "test-user") {
        id
    }
}
```

実際にユーザーを作成した結果が以下になります。

![スクリーンショット 2023-02-27 13.25.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730831/9e1a563e-bf2f-97a8-a3e3-e5ef1fbb3c1a.png)

レスポンスの指定をidのみにしたので、作成したユーザーのIDのみ返却されました。

確認のため、ユーザーの参照も行なってみます。
使用するクエリは以下の通りです。

```graphql
query getUser {
    getUser(id: 1) {
        name
    }
}
```

結果は以下の通りです。

![スクリーンショット 2023-02-27 13.30.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730831/c899d07b-99c5-66ac-215b-94df15d92a83.png)

上記結果で、Apollo Routerの動作とユーザーアプリとの疎通が確認できました。

#### 商品の作成

次にレビュー対象の商品を作成します。
クエリは以下を使用します。

```graphql
mutation CreateProd {
    createProduct(name: "test-prod", price: 100) {
        id
    }
}
```


実行結果は以下の通りです。

![スクリーンショット 2023-02-27 13.34.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730831/4d5acc69-ca98-30df-d5be-65527126ee8d.png)

一応参照も行ってみます。
使用するクエリと結果は以下の通りです。

```graphql
query GetProd {
    getProduct(id: 1) {
        name
        price
    }
}
```

![スクリーンショット 2023-02-27 13.37.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730831/d2f5e1ad-4792-f7fb-84cc-20c7b345dcc8.png)

これで商品アプリとも疎通できていることを確認できました。

#### レビューの作成

最後にレビューを作成します。

使用するクエリは以下の通りです。

```graphql
mutation CreateReview {
    createReview(
        productId: 1,
        userId: 1,
        score: 5,
        description: "期待を込めて星5です") {
            id
    }
}
```

結果は以下の通りです。

![スクリーンショット 2023-02-27 13.42.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730831/ab46ec16-3343-e65a-09a3-9b054f792a44.png)

ここまでで、レビューアプリとの疎通も取れたことが確認できました。
次の節でApollo Routerの便利機能を使用していきます。

#### レビューの取得（Apollo Routerにエンティティを取得させる）

一通りのデータを作成することができましたが、まだレビューの参照を行なっていません。
では早速レビューを参照するためのクエリを作成していきましょう。

```graphql
query GetReview {
    getReview(id: 1) {
        score
        description
        user {
            name
        }
        product {
            name
            price
        }
    }
}
```

レビューデータとしてレビューを実施したユーザーと対象の商品を取得するクエリになっています。getReviewクエリ自体はレビューアプリに定義されているクエリであり、ユーザーと商品の情報はIDしか持っていないはずです。
しかし、Supergraphでエンティティを結合したことにより、Apollo Routerがそれぞれのアプリに問い合わせを行うことで要求したデータを全て結合した状態でレスポンスしてくれます。

実際に実行した結果が以下になります。

![スクリーンショット 2023-02-27 13.53.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730831/7deff4be-f098-b237-dcb6-b0476d860e0b.png)

ちゃんと指定したデータを全て結合した上で返してくれました。
この機能によって、マイクロサービスでアプリを開発した際それぞれのアプリにデータを一元管理させることができると同時に、クライアントがリクエストを行うエンドポイントを一つにまとめることができるようになります。

## 最終的なフォルダ構成

作成した成果物のフォルダ構成は以下のようになりました。

```shell
.
├── Dockerfile.deno
├── Dockerfile.router
├── docker-compose.yml
├── products
│   └── src
│       ├── main.ts
│       ├── models
│       │   └── product.ts
│       └── schema.graphql
├── reviews
│   └── src
│       ├── main.ts
│       ├── models
│       │   └── review.ts
│       └── schema.graphql
├── router.yml
├── supergraph-schema.graphql
└── users
    └── src
        ├── main.ts
        ├── models
        │   └── user.ts
        └── schema.graphql
```


## 最後に

最後まで読んでいただきありがとうございます！
記事中に誤っている箇所がありましたら、ご連絡いただけると助かります。