Amazon Web Services ブログ

Amplify DataStore – GraphQL でオフラインアプリの開発を簡素化

オープンソースの Amplify Framework は、ウェブおよびモバイル開発者がクラウドベースのサービスを簡単にプロビジョニングおよびアクセスできるようにするコマンドラインツールおよびライブラリです。たとえば、モバイルアプリケーション用に GraphQL API を作成したければ、開発マシンで amplify add api を使用してバックエンド API を設定します。いくつかの質問に答えた後、amplify push と入力して、クラウドで AWS AppSync API バックエンドを作成します。Amplify はコードを生成し、アプリが新しく作成された API に簡単にアクセスできるようにします。AmplifyAngularReactVue などの一般的なウェブフレームワークをサポートしています。また、React NativeiOS 用の SwiftAndroid 用の Java で開発されたモバイルアプリケーションもサポートしています。モバイルアプリケーションで Amplify を使用する方法について詳しく知りたい場合は、re:Invent 2019 カンファレンス向けに準備したワークショップ (iOS または React Native) にお気軽にご参加ください。

AWS のお客様は、ウェブおよびモバイルアプリケーションを開発する際に最も難しいタスクは、デバイス間でデータを同期し、オフライン操作を処理することだと語っています。理想的には、デバイスがオフラインのときも、お客様はデータにアクセスするだけでなく、データを作成および変更するためにアプリケーションを引き続き使用できる必要があります。デバイスがオンラインに戻ると、アプリケーションはバックエンドに再接続し、データを同期して、競合がある場合は解決する必要があります。AWS AppSync SDK のオンデバイスキャッシュをオフラインミューテーションデルタ同期で使用している場合でも、すべてのエッジケースを正しく処理するには、多くの未分化コードが必要です。

本日、Amplify DataStore を導入いたします。これは、開発者がデータへの変更を書き込み、読み取り、監視するための永続的なオンデバイスストレージリポジトリです。Amplify DataStore を使用すると、開発者は、オフラインまたはオンラインシナリオ用の追加コードを作成することなく、分散データを活用するアプリを作成できます。Amplify DataStore は、クラウドへの接続や AWS アカウントを必要とせずに、ウェブおよびモバイルアプリケーションでスタンドアロンのローカルデータストアとして使用できます。ただし、クラウドバックエンドで使用する場合、Amplify DataStore は、ネットワーク接続が利用可能であれば、データを AWS AppSync API と透過的に同期します。Amplify DataStore はデータを自動的にバージョン管理し、AppSync を使用してクラウドで競合の検出と解決を実装します。このツールチェーンは、開発者が提供する GraphQL スキーマに基づいて、プログラミング言語のオブジェクト定義も生成します。

それでは、その仕組み見てみましょう。

最初に Amplify CLI をインストールし、React アプリを作成します。これは標準の React であり、そのスクリプトは 私の git リポジトリにあります。そして、Amplify DataStorenpx amplify-app によってアプリに追加します。npxNodeJS に固有です。また、Amplify DataStore は、Android Studio用 Gradle プラグインや、iOS 用のカスタム XCode 構築フェーズを作り出す CocoaPods などのネイティブモバイルツールチェーンとも統合できます。

これで、アプリの土台が完了したので、これらの投稿での PostsComments という 2 つのエンティティを表す GraphQL スキーマを追加します。依存関係をインストールし、AWS Amplify CLI を使用して、GraphQL スキーマで定義されたオブジェクトのソースコードを生成します。

# graphql スキーマを amplify/backend/api/amplifyDatasource/schema.graphql に追加
echo "enum PostStatus {
  ACTIVE
  INACTIVE
}

type Post @model {
  id: ID!
  title: String!
  comments: [Comment] @connection(name: "PostComments")
  rating: Int!
  status: PostStatus!
}
type Comment @model {
  id: ID!
  content: String
  post: Post @connection(name: "PostComments")
}" > amplify/backend/api/amplifyDatasource/schema.graphql

# 依存関係をインストール 
npm i @aws-amplify/core @aws-amplify/DataStore @aws-amplify/pubsub

# モデルを表すソースコードを生成 
npm run amplify-modelgen

# クラウドで API を作成 
npm run amplify-push

@model および @connection は、Amplify GraphQL Transformer がコードを生成するために使用するディレクティブです。 @model アノテーションが付けられたオブジェクトは、API の最上位オブジェクトであり、DynamoDB に保存されます。これらは、 検索可能にしたり、バージョン管理したり、許可されたユーザーだけにアクセスを制限したりすることができます。@connection は、リレーショナルデータベースを使用するときに定義するものと同様に、オブジェクト間の 1-n 関係を表現できます (@key ディレクティブを使用して n-n 関係をモデル化できます)。

最後のステップは、React アプリ自体を作成することです。すぐに始めるために、非常に単純なサンプルアプリをダウンロードすることをお勧めします。

# 単純な react アプリをダウンロード
curl -o src/App.js https://raw.githubusercontent.com/sebsto/amplify-datastore-js-e2e/master/src/App.js

# アプリを起動 
npm run start

ブラウザをアプリ http://localhost:8080 に接続し、アプリのテストを開始します。

デモアプリは、項目を作成、照会、削除するための基本的な UI (ご覧いただければわかるように、私はグラフィックデザイナーではありません) を提供します。Amplify DataStore は、データを保存、照会、削除するための使いやすい API を開発者に提供します。読み取りおよび書き込みは、バックグラウンドでクラウド内の AppSync エンドポイントに伝播されます。Amplify DataStore はストレージアダプター経由でローカルデータストアを使用します。ウェブ用の IndexedDB とモバイル用の SQLite を出荷しています。Amplify DataStore はオープンソースですので、必要に応じて、他のデータベースのサポートを追加できます。

コードの観点から見ると、データとのやり取りは、DataStore オブジェクトで save()delete()query() 操作を呼び出すのと同じくらい簡単です (これは Javascript の例ですが、Swift または Java でも同様のコードを記述することになります)。query() 操作は、item.rating("gt", 4)Predicates.All などの Predicates 式に基づいてフィルターを受け入れることに注意してください。

function onCreate() {
  DataStore.save(
    new Post({
      title: `New title ${Date.now()}`,
      rating: 1,
      status: PostStatus.ACTIVE
    })
  );
}

function onDeleteAll() {
  DataStore.delete(Post, Predicates.ALL);
}

async function onQuery(setPosts) {
  const posts = await DataStore.query(Post, c => c.rating("gt", 4));
  setPosts(posts)
}

async function listPosts(setPosts) {
  const posts = await DataStore.query(Post, Predicates.ALL);
  setPosts(posts);
}

Amazon DynamoDB コンソールに接続して、項目がバックエンドに保存されていることを確認します。

オフラインモードをサポートするためにコードを変更する必要はありません。オフラインモードをシミュレートするために、wifi をオフにします。アプリに 2 つの項目を追加し、wifi を再度オンにします。アプリはオフラインでも通常どおり動作し続けます。唯一の顕著な変化は、バックエンドによって設定されるため、オフライン中は [_version] フィールドが更新されないことです。

ネットワークが復旧すると、Amplify DataStore は透過的にバックエンドと同期します。現在、DynamoDB に 5 つの項目があることを確認します (テーブル名はデプロイごとに異なりますので、以下のテーブルの名前を必ず調整してください)。

aws dynamodb scan --table-name Post-raherug3frfibkwsuzphkexewa-amplify \
                   --filter-expression "#deleted <> :value"            \
                   --expression-attribute-names '{"#deleted" : "_deleted"}' \
                   --expression-attribute-values '{":value" : { "BOOL": true} }' \
                   --query "Count"

5 // <= 現在、テーブルには 5 つの削除されていない項目があります

Amplify DataStore は GraphQL サブスクリプションを活用して、バックエンドで発生する変更を追跡します。お客様は別のデバイスからデータを変更することができ、Amplify DataStore がローカルデータストアを透過的に同期します。GraphQL の知識は必要ありません。Amplify DataStore が低レベルの GraphQL API 呼び出しを自動的に処理します。リアルタイムデータ、接続、スケーラビリティ、ファンアウト、ブロードキャストはすべて、内部的に WebSocket プロトコルを使用して、Amplify クライアントと AppSync によって処理されます。

ネットワークインスタンスとして GraphQL を効果的に使用して、モデルインスタンスを HTTPS 経由で GraphQL ドキュメントに動的に変換します。

バックエンドで変更が発生したときに UI を更新するには、useEffect() React フックに次のコードを追加します。DataStore.observe() メソッドを使用して、コールバック関数 (msg => { ... }) を登録します。Amplify DataStore は、バックエンドで Post のインスタンスが変更されたときにこの関数を呼び出します。

const subscription = DataStore.observe(Post).subscribe(msg => {
  console.log(msg.model, msg.opType, msg.element);
  listPosts(setPosts);
});

ここで、AppSync コンソールを開きます。既存の投稿を照会して投稿 ID を取得します。

query ListPost {
  listPosts(limit: 10) {
    items {
      id
      title
      status
      rating
      _version
    }
  }
}

アプリで、7d8… で始まる最初の投稿を選択し、次の GraphQL の変形を送信します。

mutation UpdatePost {
  updatePost(input: {
    id: "7d80688f-898d-4fb6-a632-8cbe060b9691"
    title: "updated title 13:56"
    status: ACTIVE
    rating: 7
    _version: 1
  }) {
    id
    title
    status
    rating
    _lastChangedAt
    _version
    _deleted    
  }
}

すぐに、アプリが通知を受信し、そのユーザーインターフェイスを更新するのがわかります。

最後に、複数のデバイスでテストします。まず、amplify add hostingamplify publish を使用して、アプリのホスティング環境を作成します。アプリが公開されたら、iOS シミュレーターと Chrome を並べて開きます。どちらのアプリも、最初は同じ項目のリストを表示します。両方のアプリで新しい項目を作成し、アプリがほぼリアルタイムで UI を更新するのを確認します。テストの最後に、すべての項目を削除します。

DynamoDB に、それ以上項目がないことを確認します (テーブル名はデプロイごとに異なりますので、以下のテーブルの名前を必ず調整してください)。

aws dynamodb scan --table-name Post-raherug3frfibkwsuzphkexewa-amplify \
                   --filter-expression "#deleted <> :value"            \
                   --expression-attribute-names '{"#deleted" : "_deleted"}' \
                   --expression-attribute-values '{":value" : { "BOOL": true} }' \
                   --query "Count"

0 // <= すべての項目が削除されました

ローカルデータをバックエンドと同期させる場合、AWS AppSync はバージョン番号を追跡して競合を検出します。競合がある場合、デフォルトの解決戦略は、バックエンドで変更を自動マージすることです。自動マージは、クライアント側のコードを作成せずに競合を解決する簡単な戦略です。たとえば、最初の Post を持っているふりをしてみると、Bob と Alice は同時に投稿を更新します。

元の項目:

{
   "_version": 1,
   "id": "25",
   "rating": 6,
   "status": "ACTIVE",
   "title": "DataStore is Available"
}
Alice が rating を更新:

{
   "_version": 2,
   "id": "25",
   "rating": 10,
   "status": "ACTIVE",
   "title": "DataStore is Available"
}
同時に、Bob が title を更新:

{
   "_version": 2,
   "id": "25",
   "rating": 6,
   "status": "ACTIVE",
   "title": "DataStore is great !"
}
自動マージ後の最終的な項目:

{
   "_version": 3,
   "id": "25",
   "rating": 10,
   "status": "ACTIVE",
   "title": "DataStore is great !"
}

自動マージは、GraphQL スキーマで定義されたタイプ情報に基づいて、フィールドレベルでマージルールを厳密に定義します。たとえば、ListMap がマージされ、スカラー (数値や文字列など) の競合する更新ではサーバーに存在する値を保持します。開発者は、楽観的並行 (競合する更新は拒否) またはカスタム (AWS Lambda 関数が呼び出されて、正しいバージョンを決定) などの他の競合解決戦略を選択できます。amplify update api によって、競合解決戦略を選択できます。こうしたさまざまな戦略の詳細については、AppSyncドキュメントをお読みください。

このデモの完全なソースコードは、私の git リポジトリで入手できます。このアプリのコードは 100 行未満で、20% は単なる UI 関連です。GraphQL コードは 1 行も記述しておらず、すべてが Amplify DataStore で行われていることに注意してください。

Amplify DataStore クラウドバックエンドは、AppSync が利用可能なすべての AWS リージョンで使用できます。この投稿を執筆している時点では、米国東部 (バージニア北部)米国東部 (オハイオ)米国西部 (オレゴン)アジアパシフィック (ムンバイ)アジアパシフィック (ソウル)アジアパシフィック (シンガポール)アジアパシフィック (シドニー)アジアパシフィック (東京)欧州 (フランクフルト)欧州 (アイルランド)欧州 (ロンドン) の各リージョンです。

アプリケーションで Amplify DataStore を使用するための追加料金はありません。AppSyncDynamoDB など、使用するバックエンドリソースに対してのみお支払いいただきます (価格の詳細については、こちらこちらをご覧ください)。両方のサービスに無料利用枠があり、無料で発見や実験をすることができます。

Amplify DataStore を使用すると、未分化コードを書く代わりに、アプリのビジネス価値に集中できます。皆さんがこれを使って構築しようとしている素晴らしいアプリケーションを発見するのがとても楽しみです。

— seb