概要
AWS Amplify Advent Calendar 2019、23日目は 今年のre:Inventで発表されたAmplifyのDataStoreについてです。本記事ではサンプルコードの実装にReactを用いていますが、基本的な考え方はVueなどの他のフレームワークでも同じです。
想定読者
- Amplify 使ったことがある
- AppSync 使ったことがある
Amplify DataStore の概要
まずは簡単に DataStore についておさらいします。DataStore は AWS AppSync に接続するための新たなインターフェースです。DataStore は以下のような特徴を持ちます。
1. クラウドへのデータ同期、及び競合検知
端末のネットワーク状況(オンライン or オフライン)を検知して、端末がオンラインになったら自動的にローカルデータをクラウドに同期し、データの競合をいい感じに解決してくれます。
2. デベロッパーフレンドリーなインターフェース
デベロッパーフレンドリーなインターフェースでGraphQLに詳しくなくてもAppSyncからデータを操作できます。GraphQLの操作を抽象化した関数を組み合わせることでAppSyncに対してクエリを発行できるようになりました。
クラウドへのデータ同期、及び競合検知
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を選択するのが良いかと思われます。
デベロッパーフレンドリーなインターフェース
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の補完機能を用いながら実装を行うことができるようになります。
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の方が補完は強力です。
まとめ
いかがでしょうか。DataStoreを用いればオフライン時のアプリの実装が非常に簡単になります。また、AppSyncへのデータ操作が関数で記述できることにより、非常に効率的に開発を行うことができるようになりました。Amplifyを使って開発をされている方は、是非DataStoreの導入も検討いただけると良いと思います!
おわり