GraphQLでフロントエンドの複雑性とたたかう

5.8K Views

September 17, 19

スライド概要

Facebook社により開発されたオープンソースのクエリ言語である、GraphQLについて必要である理由や便利なツールを紹介しています。

profile-image

2023年10月からSpeaker Deckに移行しました。最新情報はこちらをご覧ください。 https://speakerdeck.com/lycorptech_jp

シェア

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

GraphQLでフロントエンドの複雑性とたたかう 2019-09-03 Bonfire Frontend #4 Yahoo! JAPAN / GYAO!

2.

Masanari Hamada @narirou お仕事 2015年新卒入社 GYAO! WEB のアーキテクチャ刷新 GYAO 動画プレーヤー刷新 GYAO iOS Swift化/リファクタリング等 デザインシステム / JAMStack / WEB標準 に興味があります 執筆 WEB+DB PRESS のフロントエンド連載記事としてStorybook、 HeadlessCMSの記事を執筆

3.

APENDIX GraphQLが誕生した背景と課題 複雑性を解決するクライアントサイドの仕組み

4.

GraphQL GraphQLはFacebookにより開発されたオープンソースのク エリ言語 2012年 Facebook社内で開発 2015年 オープンソース化 現在は Githubの graphql/graphql-spec にて汎用的なクエリ 言語仕様として策定されている

5.

GraphQLがなぜ必要か

6.

1. クライアントサイドの複雑な要求 2000年 RESTful API (Roy Fielding) RESTは汎用的に作られたエンドポイントに対して汎用化されたデータを取得する

7.

単純なクエリでも複数回の直列なRESTApiリクエストが必要になある場合がある 取得したデータには不要な項目が存在する

8.

表示の要件は本質的にクライアントの画面仕様に 依存する (そしてそれは、頻繁に変更される) ❖ 画面のデザインは変わる.. ❖ 表示したい項目も変わる.. ❖ 特殊な条件でABテストしたい.. ❖ デバイスによって複数のデータが必要.. RESTでどうやって対応する?

9.

-> RESTfulの限界… すべてのクライアント側の要件を満たすには RESTfulでは最適化が困難 拡張性 / パフォーマンス / 開発速度 が犠牲になる

10.

例えば映像の視聴履歴の例は、GraphQLであれば、以下のクエリで目 的のデータが取得できる query(userId: ID!) { history(userId: $userId) { videos { id title } } }

11.

2. サービス間のスキーマ連携 ❖ マイクロサービスアーキテクチャでは、サービス間の連携が圧倒 的に増加する ❖ 各サービスで必要なデータ項目は明示的にし、サービス間の連 携を安全に行う必要性がある

12.

クライアントサイドでも同様の問題 「コンポーネント」ごとに必要なデータは異なり、明示的にする必要がある 各コンポーネントで必要なデータを宣言的に記述できる仕組みがないとス ケールしない

13.
[beta]
query Heros {
heros(first: 10) {
name
}
}

{

}

"heros": [
{ "name": "R2-D2" }
]

export function Users() {
const { data } = useQuery(HeroNameQuery);
return data.heros.map(hero => <User name={hero.name} />);
}

14.

GraphQLを知る

15.

GraphQL SDL (schema definition language) スキーマ設計の記述言語をSDLとして汎用的に策定 型の定義として明示的で理解しやすい type Post { id: String! title: String! publishedAt: DateTime! likes: Int! @default(value: 0) blog: Blog @relation(name: "Posts") } type Blog { id: String! name: String! description: String posts: [Post!]! @relation(name: "Posts") }

16.

Interface interface Post { id: ID! title: String! text: String! } ❖ 標準で持つ型(Scalar Type)には、String, Int など ❖ ! は non-nullable を表す

17.

Type type BlogPost implements Post { id: ID! title: String! text: String! } ❖ implementsで定義したinterface型を表現できる

18.

Query query BrogPostQuery($id: ID!) { post(id: $id) { title } } { "data": { "post": { "title": "Learning GraphQL" } } } ❖ $id 部分が引数、実際にクエリ時に変数として入力する

19.

Fragment { } leftComparison: hero(episode: EMPIRE) { ...comparisonFields } rightComparison: hero(episode: JEDI) { ...comparisonFields } fragment comparisonFields on Character { name appearsIn friends { name } } ❖ 繰り返しが多く出現するなどクエリ全体が肥大化する ❖ 再利用可能なクエリの一部を切り出して表現できる

20.

Mutations mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { stars commentary } } ❖ データの書き込みはmutationクエリを発行して処理する

21.

Directives @skip @include など特定条件で処理を行うことを、クエリに記述できる UnionTypes 複数のtypeを持ったtypeを定義する これ以外にも、様々な定義があるので以下を参照 https://graphql.org/learn/schema https://graphql.org/learn/queries

22.

APIドキュメントとPlayground

23.

イントロスペクション GraphQLサーバーは、自身のスキーマ定義を公開する仕組みを備える 実際にクエリをたどってみると、内部で参照している型情報がわかる { } { __schema { types { name } } } "data": { "__schema": { "types": [ { "name": "ID" } ] } }

24.

GUI (GraphiQL, GraphQL Playground) ❖ 右側のペインから、型情報を閲覧 ❖ 左側のペインから、実際にクエリを投げることができる 定義にコメントを記述しておくことで、開発時にはPlaygroundを ドキュメンテーションの代替として利用できる

25.

クライアントとの連携 Apollo Client, Relay…

26.

Apollo Client GraphQLの通信処理をクライアント側で扱うためのライブラリ。 利用側は、宣言的なクエリの記述で基本的な通信を利用できる。 ❖状態の管理 ❖キャッシュ管理の抽象化 ❖通信間の処理の抽象化

27.
[beta]
Apollo Client を利用する
import ApolloClient from 'apollo-boost';
import gql from 'graphql-tag';
const client = new ApolloClient({
uri: 'https://48p1r2roz4.sse.codesandbox.io',
});
client
.query({
query: gql`
{
rates(currency: "USD") {
currency
}
}
`
})
.then(result => console.log(result));

29.

通信間の処理の抽象化 (Apollo Link) ApolloLinkはクライアント側の通信の抽象化層に当たる部分であ り、簡易的なプロキシとして機能する expressのミドルウェアのような構成になっている ApolloClientは、この仕組みでリクエストの最適化を行うことで、発 生する様々な複雑性を回避している

30.

通信間の処理の抽象化 (Apollo Link) ❖ apollo-link-http (基本的な通信) ❖ apollo-link-batch-http (バッチ処理) ❖ apollo-link-rest (REST APIをGraphQLのように扱う抽象化層) ❖…

31.

クライアントの状態を管理する apollo-link-state は、ローカルの状態を管理するためのApolloLink ❖ この構成はReduxに似ている ❖ Apolloで管理するデータ (ApolloCache) をクライアント 側の唯一のデータストアのよ うにも扱うことができる

32.
[beta]
import React from "react";
import { useQuery } from "@apollo/react-hooks";
import gql from "graphql-tag";
import Link from "./Link";
const GET_VISIBILITY_FILTER = gql`
{
visibilityFilter @client
}
`;

function FilterLink({ filter, children }) {
const { data, client } = useQuery(GET_VISIBILITY_FILTER);
return (
<Link
onClick={() => client.writeData({ data: { visibilityFilter: filter } })
active={data.visibilityFilter === filter}
>
{children}
</Link>
)
}

33.

コードの自動生成と最適化

34.

GraphQL SDLは汎用的に策定されており、この定義から様々な言語 で活用できるよう変換できる

35.

TypeScriptとの連携 graphql-codegen、apollo-tooling によって、クエリからTypeScript 用のコードを出力する ❖ @graphql-codegen/typescript ❖ @graphql-codegen/typescript-operations ❖ @graphql-codegen/typescript-react-apollo

36.
[beta]
例えば graphql-codegen で左のようなクエリを変換すると、
右記のようなtypesが生成される

{
posts {
title
text
labels {
name
}
thumbnail {
width
height
url
}
}
}

export type BlogPostsQuery = {
posts: Maybe<
Array<
Pick<
BlogPost,
| "title"
| "text"
> & {
labels: Maybe<Array<Pick<
thumbnail: Pick<Image, "u
}
>
>;
};

37.

Reactとの連携 @apollo/react-hooksのカスタムフックuseQueryを利用して、 GraphQLのリクエストを行う

38.
[beta]
const GreetingQuery = gql`
query getGreeting($language: String!) {
greeting(language: $language) {
message
}
}
`;
function Hello() {
const { loading, error, data } = useQuery(GreetingQuery, {
variables: { language: 'english' },
});
if (loading) return <p>Loading ...</p>;
return <h1>Hello {data.greeting.message}!</h1>;
}

39.
[beta]
クエリ(CustomHook)の自動生成
さらに、@apollo/react-hooks との連携すると、クエリから
ReactHooks用のカスタムフックを自動生成し、型安全な状態でクエ
リを利用できる。

export function useGreetingQuery(
baseOptions?: ReactApolloHooks.QueryHookOptions<
GetGreetingQuery,
GetGreetingQueryVariables
>
) {
return ReactApolloHooks.useQuery<
GetGreetingQuery,
GetGreetingQueryVariables
>(StoreTitlesDocument, baseOptions);
}
export type GetGreetingQueryHookResult = ReturnType<typeof useGetGreetingQuery

40.

これらの生成はコマンドから容易に生成するようにしておくとGood { } "scripts": { "schema": “node ./script/graphql-codegen.js" }

41.

課題

42.

1. パフォーマンスクリティカルなクエリが発行される 公開するGraphQLサーバーは、セキュリティを意識する必要がある 例えば、以下のようなクエリは許容したくない… query Heros { messages(first: 99999999) { title messages(first: 99999999) { title messages(first: 99999999) { title } } } }

43.

1. パフォーマンスクリティカルなクエリが発行される ❖ サイズを制限する ❖ ネストの制限する graphql-depth-limit でネストを制限する ❖ 複雑度を計算し制限する ❖ クエリをホワイトリスト登録する (Persisted Queries) クエリを事前にhash化しておき、通信時もhash値を用いて行う 許可されたhashのクエリのみ許容してレスポンスを返す 手順が複雑なため、よりよい解決策がほしいところ..

44.

2. 表示要素で利用する型に変形しにくい 複雑なクエリになると、表示用の型として扱うための変換が必要 自動生成された巨大な型情報からの変換となるので、型を維持して 変換することが難しい ts-toolbelt などを利用して、型の一部を変更することで対応している

45.

まとめ

46.

まとめ ❖ GraphQLはクライアントの複雑な要件に迅速に対応するための、 1つの解決手段 コードの自動生成 ネットワーク層の自動最適化 ❖ 取得するデータを宣言的に記述し、型安全に開発できる世界を目 指している ❖ 負荷的なセキュリティの課題や、N+1問題等、GraphQL特有の複 雑性があり、これからの課題である

47.

まとめ クライアントサイドは、複雑な仕様変更・ユーザー操作による変化など、アプ リケーションの中でもっとも複雑な副作用が発生する場所でもあります。 GraphQL, Falcor, gRPC… RESTが発明されてから数10年、課題を感じている各社からAPIのパラダイム・ シフトを予感させる技術が出始めていますが、GraphQLはその潮流の一端を担 う技術の1つです。 こうした複雑性の問題と、その解決案として考えられている背景を知って、よ りよい技術でこれからのWEB開発を乗り越えていきましょう。💪

48.

より深く知るには (参考文献) ❖ GraphQL, Apolloの公式ドキュメント ❖ クエリの基礎を知る: [Queries and Mutations](https://graphql.org/learn/queries/) ❖ 背景を知る: [Github Blog | The GitHub GraphQL API](https://githubengineering.com/thegithub-graphql-api/) ❖ ApolloLinkを知る: [Apollo Link Overview](https://www.apollographql.com/docs/link/ overview/) ❖ ベストプラクティスを知る: [GraphQL Best Practice](https://graphql.org/learn/best-practices/) ❖ 制限をかける: [HOW TO SECURE A GRAPHQL API (THE COMPLETE VULNERABILITY CHECKLIST)](https://leapgraph.com/graphql-api-security) ❖ 制限をかける: [GraphQLとPsersisted Query by @Quramy](https://qiita.com/Quramy/items/ b3943a0c27f3ade2c57d)