【SpaceX REST API】GraphQLサンプルコードを試してみた

この記事では、SpaceX REST APIを使ったサンプルコードを動かしながら、GraphQLの概要について紹介していければと思います。

最近、AzureのAPI BuilderがGraphQLに対応する(記事投稿時点ではパブリックプレビュー)など、GraphQLの実装コストが下がってきていると感じています。

今後、GraphQLでの開発が一般的になればより、Webアプリの開発スピードは格段に上がると思います。ここでは、サンプルコードを動かしながらGraphQLの概要を紹介していきます。 GraphQLの魅力が伝わればと思っています!

サンプルコードについて

この記事でお試しするサンプルコードはこちらです。 github.com

このコードは、SpaceXの打ち上げの座席を予約するアプリケーションになっています。

ログイン&Profile機能

宇宙旅行の一覧機能、詳細機能

カート機能

GraphQLとは、何がいいのか

今回使うサンプルコードのデータソースは二つに散らばっており、

  • SQLiteに保持したユーザー情報
  • r/SpaceX APIから取得するロケットの打ち上げ情報

がありました。 従来のREST APIであれば、それぞれのデータソースに対してリクエストを投げる処理をクライアント上で個別に実装する必要があります。それが、GraphQLの場合は一つのエンドポイントのみで良くなります。

以下の画像はREST APIで実装した場合のアプリケーションとデータソースのやり取りのイメージです。

一つの画面にユーザー情報とロケットの打ち上げ情報を載せたい場合は2回以上のデータソースとのやり取りが発生しいます。 特にモバイル端末の場合は、小さい画面に多くの情報を載せる必要があるため、データソースとクライアントとのやり取りが複雑になりがちです。

それがGraphQLであれば以下のようにエンドポイントが一つに統一され、やり取りが単純化されます!

GraphQLの場合は、エンドポイントが一つにまとまっていることにより、一度のリクエストでまとめて情報を取得することができます。した画像は、SQLiteにあるデータソースとREST APIのデータソースを一度のリクエストで取得した例です。

データソースを取得するときのフロントエンド側の実装コストが減ると、別の端末への対応であったり、新規アプリケーションの構築にも取り組むことができます。 また、GraphQLは既存のREST APIなどをラップしているだけなので、期を見計らって、バックエンド側のModernizationに取り組むことも検討できます。

GraphQLの構築自体がコストであるため、トレードオフの関係ではありますが、フロントエンド側の構築コストが減ることは長い目で見たときにメリットになるのではと思います。

GraphQLとREST APIの比較まとめ

ここまでの内容を簡単に表にまとめました。

GraphQL REST API
Performance High
リクエストに必要な情報のみを一度で取得できるため、速度は早くなります
Low
必要な情報を取得するために複数のリクエストをする必要があるため、遅くなる
Query complexity Complex
エンドポイントを一つにまとめるため、裏のビジネスロジックは複雑になっていく可能性がある。
Simple エンドポイントは分離されているため、クエリ自体は単純になる
Popularity Still Growing
Who's Using
Very Popular
Resources & communication support Growing Large
Learning curve 難しい 簡単
Recommended use case - マイクロサービスアーキテクチャを採用したアプリケーション
- モバイルアプリ
- シンプルなアプリケーション
- 開発の初期段階

GraphQLの実装

GraphQLは大きく以下の4つで構成されています。

  1. Schema(データ型を定義)
  2. Query(データの取得)
  3. Mutation(データの更新)
  4. Resolver(ロジックを定義)

スキーマ

Schemaには型を定義します。

  """
  Simple wrapper around our list of launches that contains a cursor to the
  last item in the list. Pass this cursor to the launches query to fetch results
  after these.
  """
  type LaunchConnection {
    cursor: String!
    hasMore: Boolean!
    launches: [Launch]!
  }

  type Launch {
    id: ID!
    site: String
    mission: Mission
    rocket: Rocket
    isBooked: Boolean!
  }

  type Rocket {
    id: ID!
    name: String
    type: String
  }

  type User {
    id: ID!
    email: String!
    profileImage: String
    trips: [Launch]!
    token: String
  }

  type Mission {
    name: String
    missionPatch(size: PatchSize): String
  }

  enum PatchSize {
    SMALL
    LARGE
  }

Query

データの取得処理はQueryに定義します。

  type Query {
    launches(
      """
      The number of results to show. Must be >= 1. Default = 20
      """
      pageSize: Int
      """
      If you add a cursor here, it will only return results _after_ this cursor
      """
      after: String
    ): LaunchConnection!
    launch(id: ID!): Launch
    me: User
  }

Mutation

データの更新、作成処理はMutationで定義します。

  type Mutation {
    # if false, signup failed -- check errors
    bookTrips(launchIds: [ID]!): TripUpdateResponse!

    # if false, cancellation failed -- check errors
    cancelTrip(launchId: ID!): TripUpdateResponse!

    login(email: String): User
  }

Resolver

実際の処理はResolverに書かれます。 コードが長いのでQueryのみ抜粋しています。

ビジネスロジックは全てResolverに書かれていて、Query, Mutation, Schemaには定義のみが書かれています。

 Query: {
    launches: async (_, { pageSize = 20, after }, { dataSources }) => {
      const allLaunches = await dataSources.launchAPI.getAllLaunches();
      // we want these in reverse chronological order
      allLaunches.reverse();

      const launches = paginateResults({
        after,
        pageSize,
        results: allLaunches,
      });

      return {
        launches,
        cursor: launches.length ? launches[launches.length - 1].cursor : null,
        // if the cursor of the end of the paginated results is the same as the
        // last item in _all_ results, then there are no more results after this
        hasMore: launches.length
          ? launches[launches.length - 1].cursor !==
            allLaunches[allLaunches.length - 1].cursor
          : false,
      };
    },
    launch: (_, { id }, { dataSources }) =>
      dataSources.launchAPI.getLaunchById({ launchId: id }),
    me: async (_, __, { dataSources }) =>
      dataSources.userAPI.findOrCreateUser(),
  },

関連情報

GraphQLでアプリを作るのチュートリアル

www.howtographql.com

Space X-API

サンプルアプリの中で使っていた、Open Sourceとして公開されているREST APIです。

github.com

docs.spacexdata.com

GraphQLが作られた背景

記事の主旨とはずれますが、色々と調べるにあたってGraphQLの全体像が動画でまとまっていて内容がとてもよかったので紹介させてください。動画を見ると、当時GraphQLの前身を作った人たちの興奮が伝わってきます。 2012年頃から既にREST APIに問題意識を持っていたのと、それを技術で解決してしまうのがすごいところと感じました。

www.youtube.com

この記事には書ききれなかったのでまた別途GraphQLの導入事例などを掘り下げていければと思います。

まとめ

今回は、サンプルコードを例に、GraphQLの特徴をまとめました。 まだWeb上にもGraphQLの情報はそこまで多くなく、採用実績も少ないため、これからの技術だと思います。

今後VR上で動くアプリが増えてきた時に、広い画面で操作することが前提になることから、アプリ上で取得したい情報も増えていくと予想できます。 そういった未来が来た時に、GraphQLのような異なるデータソースをまとめて一つのエンドポイントを提供できる技術というのは有用なのではと感じました。

執筆担当者プロフィール
飯島 航己

飯島 航己(日本ビジネスシステムズ株式会社)

MSアライアンス部所属。Azure DevOps, .NETが好きです。

担当記事一覧