LoginSignup
28
20

More than 3 years have passed since last update.

AWS Ampliy の DataStore を使ったら AppSync のデータアクセスがめちゃくちゃ楽になった

Last updated at Posted at 2019-12-24

概要

AWS Amplify Advent Calendar 2019、23日目は 今年のre:Inventで発表されたAmplifyのDataStoreについてです。本記事ではサンプルコードの実装にReactを用いていますが、基本的な考え方はVueなどの他のフレームワークでも同じです。

想定読者

  • Amplify 使ったことがある
  • AppSync 使ったことがある

Amplify DataStore の概要

まずは簡単に DataStore についておさらいします。DataStore は AWS AppSync に接続するための新たなインターフェースです。DataStore は以下のような特徴を持ちます。

1. クラウドへのデータ同期、及び競合検知

端末のネットワーク状況(オンライン or オフライン)を検知して、端末がオンラインになったら自動的にローカルデータをクラウドに同期し、データの競合をいい感じに解決してくれます。

image.png

2. デベロッパーフレンドリーなインターフェース

デベロッパーフレンドリーなインターフェースでGraphQLに詳しくなくてもAppSyncからデータを操作できます。GraphQLの操作を抽象化した関数を組み合わせることでAppSyncに対してクエリを発行できるようになりました。

スクリーンショット 2019-12-23 18.08.59.png

クラウドへのデータ同期、及び競合検知

DataStoreを用いることで、オフライン時のアプリの開発を簡素化することができます。
DataStoreはローカルストレージとクラウドの両方にデータの書き込みを試みます。端末がオフラインの場合、リクエストを失敗させることなく、ローカルストレージにのみデータ書き込みを行い、オンライン復旧後にクラウドへのデータ同期を行います。オフライン期間にクラウド上のデータとローカルストレージのデータで差分が発生した場合は、データのマージ、データの競合検知を自動的に行います。

そしてこれらの挙動は全て隠蔽されており、開発者はこれらの挙動について、意識をする必要はありません!!

DataStoreでは、データの競合時の動作を以下の3つから選択できるようになっています。

  • Auto Merge (デフォルト)
  • Optimistic Concurrency
  • Custom Lambda
データの競合が起こった時の挙動1(Auto Merge)

データの競合が起こった際の挙動はデフォルトではAutomergeが選択されています。
以下の例ではClienrt A、Client Bがオフライン中に同時にデータの更新を行った場合を想定します。DataStoreは互いの更新内容を確認し、自動でデータをマージしクラウド上のデータと各クライアントのローカルストレージ上のデータに対し更新を行います。

// 元のデータ
{
  id: 'id_001',
  name: 'Andy',
  hobby: [],
  _version: 1,
  _lastChangedAt: 1577001382093,
  _deleted: false
}
// Client A がオフライン中にローカルで変更したデータ
{
  id: 'id_001',
  name: 'Andy',
  hobby: [baseball],  // Client A が hobbyに baseballを追加
  _version: 2,
  _lastChangedAt: 1577001382094,
  _deleted: false
}
// Client B がオフライン中にローカルで変更したデータ
{
  id: 'id_001',
  name: 'Jeff',     // Client B が nameを Jeffに変更
  hobby: [tennis],  // Client B が hobbyに tennisを追加
  _version: 2,
  _lastChangedAt: 1577001382095,
  _deleted: false
}

この場合、Client A と Client Bの更新は矛盾なく実施することができるため、以下のようなデータにマージされます。

{
  id: 'id_001',
  name: 'Jeff',     // Client B の変更が反映
  hobby: [baseball, tennis],  // Client A,B の変更を反映
  _version: 2,
  _lastChangedAt: 1577001382524,
  _deleted: false
}
データの競合が起こった時の挙動2(Optimistic Concurrency)

Optimistic Concurrencyでは、データの競合を検知した際に、単純にクライアントからのリクエストを拒否します。クライアントは、再度、クラウド上のデータを正としてデータの更新処理を行う必要があります。

データの競合が起こった時の挙動3(Custom Lambda)

Custom Lambdaでは競合が起こった際に、独自に定義したLambda関数を起動させることができます。Custom Lambdaを用いることで、より柔軟なデータ競合時の処理を実装することが可能です。

データの競合戦略の変更はAmplify CLIから行うことができます。特に指定がなければAuto Mergeを選択するのが良いかと思われます。

スクリーンショット 2019-12-23 20.34.26.png

デベロッパーフレンドリーなインターフェース

DataStoreを用いたAppSyncのデータアクセス方法

先ほども言及したように、DataStoreの登場で、デベロッパーはGraphQLを記述しなくてもAppSync経由でデータストアにアクセスできるようになりました。

例えば、以下のようなスキーマがあった場合に、

enum PostStatus {
  ACTIVE
  INACTIVE
}

type Post @model {
  id: ID!
  title: String!
  rating: Int!
  status: PostStatus!
}

評価(rating)が4以上の投稿を抽出するには以下のように記述することができます。

import { Post } from "./models";

const posts = await DataStore.query(Post, c => c.rating("gt", 4));

第一引数には、取得する対象のデータモデルのClassを指定し、第二引数には取得するデータの検索条件を指定します。
さらに複雑な条件を記述することもできます。たとえば、以下の例では、「ratingが4以上、もしくは、ステータスがACTIVEな投稿」を取得しています。

import { Post } from "./models";
const posts = await DataStore.query(Post, c => c.or(
  c => c.rating("gt", 4).status("eq", PostStatus.ACTIVE)
));

ちなみにこれと同じクエリをGraphQLで発行すると以下のようになります。


const posts = await API.graphql(graphqlOperation(`
  query listPosts {
    listPosts(
      filter: {
        or: {
          rating: {
            gt: 4
          }
          status: {
            eq: ACTIVE
          }
        }
      }
    ) {
      items {
        id
        title
        rating
      }
    }
  }
`));

どうでしょうか。このようにGraphQLに精通していなくてもAppSyncに対しクエリを発行することができます。

静的型付け言語の恩恵

関数でクエリの操作を行えるようになったことで、TypeScriptやKotlin、Swiftといった静的型付け言語の恩恵を受けやすくなります。今まではGraphQLのクエリを文字列で記述していたのに対し、DataStoreでは関数でクエリを組み立てていくため、IDEの補完機能を用いながら実装を行うことができるようになります。
スクリーンショット 2019-12-23 22.58.24.png

Amplify CLI のcodegen というコマンドを実行することで、作成したモデル定義からAppSyncへのデータアクセスの実装に必要なソースコードを自動で生成してくれます。このコマンドを用いることで、静的型付けに必要なコードも自動で出力してくれます。

先ほどのモデルを定義した状態で、codegenコマンドを発行すると、以下のようなコードが出力されます。

import { ModelInit, MutableModel, PersistentModelConstructor } from "@aws-amplify/datastore";

export enum PostStatus {
  ACTIVE = "ACTIVE",
  INACTIVE = "INACTIVE"
}

export declare class Post {
  readonly id: string;
  readonly title: string;
  readonly rating: number;
  readonly status: PostStatus | keyof typeof PostStatus;
  constructor(init: ModelInit<Post>);
  static copyOf(source: Post, mutator: (draft: MutableModel<Post>) => MutableModel<Post> | void): Post;
}

型定義されたClassをimportすることで、先ほどのようにIDEの機能を活用しながら実装を進めることが可能です。

上記のソースはTypeScriptを用いることを前提としています。TypeScriptを用いない場合であってもある程度補完は効きますが、やはりTypeScriptの方が補完は強力です。

スクリーンショット 2019-12-23 22.38.44.png

まとめ

いかがでしょうか。DataStoreを用いればオフライン時のアプリの実装が非常に簡単になります。また、AppSyncへのデータ操作が関数で記述できることにより、非常に効率的に開発を行うことができるようになりました。Amplifyを使って開発をされている方は、是非DataStoreの導入も検討いただけると良いと思います!

おわり

28
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
20