Fusic Tech Blog

Fusicエンジニアによる技術ブログ

ReactからAppSyncへQueryを投げる
2024/03/25

ReactからAppSyncへQueryを投げる

こんにちは、Fusicの小原です。
前回はAppSyncの認証をCognitoでできるようにしました。今回はその続きで実際にQueryを投げてみたいと思います。

Queryを投げる準備

パッケージのインストール

ReactでAppSyncへ接続するために準備をしていきます。
まずは必要なパッケージをインストールします。

"aws-appsync": "^1.3.4",
 "aws-appsync-react": "^1.1.4",
 "graphql-tag": "^2.9.2",
 "react-apollo": "^2.1.11",

package.jsonの中です。AppSyncではApolloを使っているのでApolloもインストールします。
GraphQLのリクエストはgraphql-tagreact-apolloも直接使ってQueryやMutationもつかいます。

Provider

まずはApolloで使うコンポーネントをProviderで囲みます。

const client = new AWSAppSyncClient({
  ・・・
});

class App extends Component {
  render() {
    return (
      <ApolloProvider client={ client }>
        <MainContainer />
      </ApolloProvider>
    );
  }
}

これでMainContainer内ではprops経由でApolloのリクエスト結果などにアクセスすることができます。

ReactでQueryを投げる

Queryを投げるコンポーネント

次にReactでQueryを投げてみたいと思います。さきほどのMainContainer内でGraphQLのリクエストを投げます。

class ConditionsContainer extends React.Component {
  constructor(props) {
    super(props);
  }
  onEdit(c) {
    this.props.history.push("/conditions/edit/" + c.user_id + "/" + c.date);
  }

  matchToCondition({ data : { listConditions: conditions }}) {
    return conditions ? conditions.items : [];
  }

  render() {
    return(
      ・・・
      <ListConditions
      conditions={ this.matchToCondition(this.props) }
      onEdit={ this.onEdit.bind(this) }
      />
      ・・・
    );
  }
}

const mapStateToProps = (state) => ({
  user: state.user
});

export default connect(
  mapStateToProps,
)(compose(
  graphql(ListConditionsQuery, {
    options: (props) => {
      const variable = { filter: {user_id: {eq: props.user.username}} };
      return {
        fetchPolicy: "cache-and-network",
        variables: variable
      }
    },
    props: ({ data }) => ({
      data
    })
  })
)(ConditionsContainer));

ConditionContainerを作ります。ここにAppSyncへのリクエストの結果などを扱えるようにします。connectはreduxの関数です。コンポーネントをconnectで囲むとreduxのstoreのデータにアクセスできます。storeにはユーザデータを入れるようにしています。ここで言うユーザデータはCognitoで認証したユーザデータをそのまま入れています。
compose関数でGraphQLのリクエストの準備をし、スキーマ定義とリクエスト等でgraphql関数を使います。

AppSyncで定義されているスキーマ

ここでgraphqlの説明に入る前に一度AppSyncとでで定義されているSchemaやDataSoucesについて触れておきます。
graphqlの第一引数にGraphQLのタグで作ったスキーマを入れます。こんな感じのものです。

import gql from 'graphql-tag';

export default gql`
  query listConditions($filter: TableConditionFilterInput, $limit: Int, $nextToken: String) {
    listConditions(filter: $filter, limit: $limit, nextToken: $nextToken) {
      items {
        id
        user_id
        date
        physical_state
        physical_note
        work_state
        work_note
        leave_time
        work_after
      }
    }
  }
`;

gqlで囲ったところがスキーマの定義になります。ここからはGraphQLの話ですね。
queryなのでQueryを投げます。リクエストはlistConditionsで引数にfilterlimitnextTokenをとるスキーマになります。ここらへんはAppSyncがデフォルトで提供してくれてるインターフェイスになります。(厳密に言うとAppSyncからDynamoDBへリクエストするResolverでリクエストできるようにするものです)

とくにfilterはQueryでリクエストをするときにDynamoDBへ保存されているデータのフィルターに使われます。
filterはこんな感じでAppSyncに定義されています。

input TableConditionFilterInput {
  id: TableIDFilterInput
  user_id: TableStringFilterInput
  date: TableAWSDateFilterInput
  physical_state: TableIntFilterInput
  physical_note: TableStringFilterInput
  work_state: TableIntFilterInput
  work_note: TableStringFilterInput
  work_after: TableStringFilterInput
  leave_time: TableStringFilterInput
}

DynamoDBの各カラムごとにリクエストで検索条件を定義できます。一つピックアップしてdateTableAWSDateFilterInputの中を見てみましょう。

input TableAWSDateFilterInput {
  ne: AWSDate
  eq: AWSDate
  le: AWSDate
  lt: AWSDate
  ge: AWSDate
  gt: AWSDate
  contains: AWSDate
  notContains: AWSDate
  between: [AWSDate]
}

それぞれ検索条件でたとえばneですとnot equalなので等しくないときの条件ですね。
ここらへんは関係演算子(比較演算子かな)のと一緒です。betweenなんかでも検索できたりと大抵の検索条件は整っています。

これか各カラムにあるのですべてのカラムに対して検索条件を決めれるっということです。
当たり前だと思うかもしれませんが割と重要なことでGraphQLはあくまでもクライアントとサーバ間でのインターフェイスのみを定義しており(いわゆるスキーマ)検索方法、条件の細かい設定はサーバ側で実装しないといけません。
これをAppSyncはデフォルトでサポートしてるのでとてもうれしいですね。

このリクエストをAppSyncが受け取ったらDynamoDBへリクエストするんですが、その時のマッピング情報を定義してあげる必要があります。

{
 "version": "2017-02-28",
 "operation": "Scan",
 "filter": #if($context.args.filter) $util.transform.toDynamoDBFilterExpression($ctx.args.filter) #else null #end,
 "limit": $util.defaultIfNull($ctx.args.limit, 100),
 "nextToken": $util.toJson($util.defaultIfNullOrEmpty($ctx.args.nextToken, null)),
 }

このような形で定義してあげます。先程のfilter内のリクエスト条件は$ctx.args.filterに入っており、 $util.transform.toDynamoDBFilterExpression関数に渡すことで検索条件通りにリクエストを変換してくれます。

Reactでのリクエスト結果

AppSyncで定義されているスキーマを確認しましたので、Reactに戻って実際のリクエストを投げるところを見てみたいと思います。

compose関数で囲まれた中にgraphqlでスキーマごとにリクエストするときの引数の設定、リクエスト結果の処理などを決めていきます。

graphql(ListConditionsQuery, {
  options: (props) => {
    const variable = { filter: {user_id: {eq: props.user.username}} };
    return {
      fetchPolicy: "cache-and-network",
      variables: variable
    }
  },
  props: ({ data }) => ({
    data
  })
})

ListConditionsQueryは先程見ましたね。GraphQLのスキーマになります。
次にoptionsでリクエストするときの引数を渡します。
optionsの引数にはConditionsContainerのpropsへアクセスすることができます。これでreduxのstoreに入っているユーザデータにアクセスします。ここではユーザの名前を取得しています。

次に引数となるvariablesにはユーザデータのユーザ名で検索するように検索条件を指定します。
それをoptionsの返り値としてvariablesに入れることでListConditionsQueryスキーマの引数として渡すことができます。

最後にpropsっとありますが、これはリクエストした結果をコンポーネントへ渡すところになります。引数として検索結果が返ってきておりdataの中に検索結果のデータが入っています。

リザルト

これでQueryを投げて結果が返ってくるところまでできました。後はQueryの結果をコンポーネントに渡して画面に表示させれば一通りのQuery処理の完了となります。

matchToCondition({ data : { listConditions: conditions }}) {
  return conditions ? conditions.items : [];
}

render() {
  return(
    ・・・
    <ListConditions
      conditions={ this.matchToCondition(this.props) }

matchToConditionの引数でコンディションリストへ一発でアクセスするよう引数に階層をつけてコンポーネントのpropsを渡します。dataは上記のgraphql関数内でprops関数で返り値として渡したdataになります。data内に入っているオブジェクトのlistConditionsキーで指定しているものへアクセスしています。conditionsがある場合はそのまま返し、ない場合はからのリストを返すようにしています。これでListConditionsコンポーネントには確実に配列のオブジェクトを渡すことできるようになります。

まとめ

  • 今回はReactからAppSyncへQueryを投げるときの実装を見ていきました。クライアント側は簡単で、GraphQLのスキーマを定義し、Queryを投げ、結果を画面に描画するだけでした。
  • サーバ側ではQueryの検索条件を実装せずにAppSync + DynamoDBですべて解決できほぼほぼ手を加えることなくサーバ側の実装もすることができかなり楽でした(これを自前で組もうとするとマジしんどいです、、)
  • AppSyncからDynamoDBへのマッピング処理ですかさらっと流しましたが、実はここの処理が大事でAppSyncからどのような形でDynamoDBへリクエストを投げるかを定義している部分になります。ここを深く知っていればもっと自由度高く検索することができるのではないでしょうか。
  • またMutationのときには違う実装となっていますのでそのときに触れればと思います。
kobaru

kobaru

インフラ好きっ子