目次

まえがき
本書の題名について
本書の構成
本書の記述対象
リポジトリーについて
表記関係について
免責事項
底本について
プロローグ: モバイル/エッジデータベース選択を巡る対話
第1章 なぜ、Couchbase Mobileなのか?
1.1 モバイル/エッジコンピューティングデータプラットフォームCouchbase Mobile
1.2 ドキュメント指向組み込みデータベースCouchbase Lite
第2章 Couchbase Liteデータベース
2.1 ドキュメント構造
2.2 データベース操作
2.3 ドキュメント操作
2.4 添付ファイル操作
2.5 JSON文字列との変換
第3章 Couchbase Liteクエリ
3.1 概要
3.2 定義と実行
3.3 ライブクエリ
第4章 Couchbase Liteを使ってみる
4.1 はじめに
4.2 Android Java
4.3 Kotlin
4.4 Swift
4.5 Objective-C
4.6 C#.NET
4.7 C/C++
第5章 Couchbase LiteクエリビルダーAPI
5.1 SELECT
5.2 WHERE
5.3 JOIN
5.4 GROUP BY
5.5 ORDER BY
5.6 LIMIT
5.7 日付データ
第6章 Couchbase Lite SQL++/N1QLクエリAPI
6.1 ステートメント
6.2 リテラル
6.3 識別子
6.4 式
6.5 二項演算子
6.6 単項演算子
6.7 COLLATE演算子
6.8 CASE演算子
6.9 関数
第7章 Couchbase Liteインデックス
7.1 概要
7.2 インデックス操作
7.3 インデックス利用条件
7.4 インデックス最適化
第8章 Couchbase Lite全文検索
8.1 概要
8.2 FTSインデックス
8.3 FTSクエリ
8.4 パターンマッチングフォーマット
8.5 ランキング
第9章 Couchbase Lite C言語サポート
9.1 概要
9.2 利点
第10章 Couchbase Liteロギング
10.1 概要
10.2 カスタムロギング
10.3 バイナリログのデコード
第11章 Couchbase Liteツール
11.1 cblite
11.2 cbliteサブコマンド
11.3 クエリ調査
11.4 Visual Studio Codeプラグイン
第12章 クロスプラットフォーム開発
12.1 概要
12.2 Xamarin
12.3 Flutter
12.4 React Native
12.5 Ionic
第13章 Sync Gateway概要
13.1 データ同期
13.2 アクセス制御モデル
13.3 機能と操作
13.4 Sync Gateway間レプリケーション
第14章 Sync関数
14.1 概要
14.2 アクセス制御設定API
14.3 ドキュメント属性設定API
14.4 権限検証API
14.5 例外API
14.6 Sync関数定義における考慮点
14.7 Sync関数実装
第15章 Sync Gateway管理
15.1 Sync Gateway構成
15.2 管理REST APIによる構成情報登録
15.3 管理REST APIによる構成情報確認
第16章 Sync Gatewayセキュリティー
16.1 概要
16.2 ユーザー認証
16.3 TLS証明書認証
16.4 REST APIアクセス
16.5 Couchbase Server接続
第17章 Sync Gatewayシステム設計
17.1 エンティティー設計
17.2 ドキュメント設計
17.3 性能設計
17.4 OSレベルチューニング
第18章 Sync Gatewayシステム連携
18.1 ロードバランサー/リバースプロキシ
18.2 メトリクスREST APIによるシステム監視
18.3 パブリックREST APIによるアプリケーション連携
第19章 Sync Gateway運用
19.1 オフライン/オンライン制御
19.2 再同期
第20章 Sync Gatewayロギング
20.1 ログファイル
20.2 コンソールログ
20.3 ロギング構成方法
第21章 Couchbase Serverクライアントとの共存
21.1 概要
21.2 共有バケットアクセス
21.3 インポート処理
21.4 留意事項
第22章 Couchbase Liteレプリケーション
22.1 レプリケーター
22.2 レプリケーションフィルター
22.3 モニタリング
22.4 ネットワークエラー対応
22.5 ドキュメント自動パージ
第23章 Couchbase Mobile内部機構
23.1 リビジョン
23.2 レプリケーションプロトコル
第24章 Couchbase Mobile競合解決
24.1 概要
24.2 競合発生シナリオ
24.3 データベース更新時の競合解決
24.4 プッシュレプリケーション時の競合解決
24.5 プルレプリケーション時の競合解決
第25章 Couchbase Mobile設計パターン
25.1 ユーザーエクスペリエンス向上
25.2 同期データの最適化
25.3 ローカルデータの最適化
第26章 Couchbase Mobile環境構築
26.1 Sync Gatewayインストール
26.2 Sync Gateway実行
26.3 Couchbase Server環境
26.4 コンテナ利用
第27章 Couchbase Mobileを体験する
27.1 はじめに
27.2 環境概要
27.3 環境構築
27.4 モバイルアプリセットアップ
27.5 モバイルアプリ設計
27.6 データベース操作
27.7 セキュリティー
27.8 クエリ
27.9 全文検索
27.10 レプリケーション
27.11 環境利用終了
第28章 開発の実践に向けて
28.1 コミュニティー
28.2 無償オンライントレーニング
28.3 ブログ
28.4 ドキュメント、APIリファレンス
28.5 チュートリアル、サンプルアプリケーション
28.6 ソースコード
付録A ピアツーピア同期
A.1 背景
A.2 概要
A.3 アーキテクチャー
付録B 予測クエリ
B.1 背景
B.2 機能
付録C Couchbase Capella App Services
C.1 Couchbase Capella
C.2 App Services

まえがき

本書の題名について

 「Couchbase Mobile」とは、単体のソフトウェアを指す言葉ではなく、Couchbase LiteとSync Gatewayを包含する呼称です。Couchbase Liteは、NoSQLドキュメント指向モバイル組み込みデータベースです。Sync Gatewayは、Couchbase LiteとCouchbase Serverとのデータ同期を行うサーバーソフトウェアです。

 また、「エッジコンピューティングデータプラットフォーム」については、エッジ端末におけるデータ管理に加え、エッジ上のデータとクラウド/中央データセンターのデータベースとの間の双方向のデータ同期機能を提供するプラットフォームという意味で使われています。

 なお、書名としては語句が重複するため避けましたが、「モバイル/エッジコンピューティング〜」とする方が実情をうまく表しているため、本文中ではこの表現を用いています。

 最後に、「エッジ」という言葉について補足します。IoT(Internet of Things)隆盛の中、「エッジ」は、IoTデバイスの同義としても使われていますが、「エッジコンピューティング」というとき、それは「クラウドコンピューティング」への対比としての意味を持ちます。その意味で「エッジ」という言葉は、モバイルやIoTデバイスのみならず、クラウド/中央データセンターに対する、「エッジ」データセンターという文脈でも用いられます。

本書の構成

 本書は表現や内容、あるいは直接的に想定されている読者層について、傾向の異なるいくつかの部分からなります。

 この「まえがき」に続く「プロローグ」は、二人の登場人物の対話の形式で書かれています。ここでは、Couchbase Mobileについて、はじめてその名前を聞くという人を想定し、他のデータベースとの比較を行いながら紹介しています。

 プロローグに続き、第1章はCouchbase Mobileの存在意義を伝えることを目的としています。

 第2章から第12章は、Couchbase Liteを組み込みデータベースとして利用する際の基本的な情報を提供しています。第2章と第3章でCouchbase Liteの基本機能について解説した上で、第4章にて各種プログラミング言語でCouchbase Liteを利用する方法を説明しています。その後、Couchbase Liteを使ってアプリケーションを開発するための情報を提供するいくつかの章が続きます。なお、第4章や第9章、第12章のように、個別のプログラミング言語に関する内容を扱っている章以外では、機能の解説やサンプルコードのためにAndroid Javaを用いています。

 第13章から第20章は、Sync Gatewayについての解説に充てられています。

 第21章は、Couchbase Mobileを、Couchbase Serverクライアントアプリケーションと共に利用するケースについて解説しています。

 第22章から第27章は、Couchbase LiteとSync Gatewayのデータ同期について、機能、内部機構から、設計や環境構築まで横断的な内容を扱っています。特に第27章では、Couchbase Mobileの全体を体験することができるよう、サンプルアプリケーションを用いて、環境構築から操作までを、演習形式で解説しています。

 最後の第28章では、それまでの内容を踏まえて、実際の開発に進む際に参考とすることのできる情報を紹介しています。

本書の記述対象

 本書の記述内容は、Couchbase Mobile 3.0を対象としています。1

 Couchbase Mobileは、エンタープライズエディションとコミュニティーエディションの、ふたつの形態でバイナリが提供されており、その基盤としてオープンソースプロジェクト2が存在しています。本書の記述は、基本的にコミュニティーエディションに基づきます。加えて、エンタープライズエディション独自の機能についても適宜紹介しています。3

リポジトリーについて

 本書中に掲載しているサンプルコード等を以下のURLで公開しています。また、本書に関する情報共有のために、必要に応じ、このリポジトリーを更新する予定です。


 https://github.com/YoshiyukiKono/Couchbase_Mobile_First_Step_Guide.git

表記関係について

 本書に記載されている会社名、製品名などは、一般に各社の登録商標または商標、商品名です。会社名、製品名については、本文中では©、®、™マークなどは表示していません。

免責事項

 本書の文責は著者にあり、所属する組織とは関係ありません。

 また、本書に記載された内容は、情報の提供のみを目的としています。正確かつ適切な情報を届けるため配慮を尽くしていますが、本書の内容の正確性、有用性等に関しては、一切保証するものではありません。したがって、本書の情報を用いた開発、運用、コンサルティング等、いかなる実践も必ずご自身の責任と判断によって行ってください。本書の情報を参照した行為の結果について、著者はいかなる責任も負いません。

底本について

 本書籍は、2022年1月に開催された技術系同人誌即売会「技術書典12」で頒布された「エッジコンピューティングプラットフォームCouchbase Mobileファーストステップガイド」を底本とし、2022年2月にリリースされたCouchbase Mobile 3.0にあわせた内容の更新を行った他、全面的に改訂しています。

1. 3.0リリース以前に発表されたブログから、現在でも有益と考えられる情報を紹介している箇所があります。

2. https://developer.couchbase.com/open-source-projects/

3. また、第27章で紹介しているサンプルアプリケーションでは、エンタープライズエディション環境を利用しています。

プロローグ: モバイル/エッジデータベース選択を巡る対話

 「先輩、すみません。少しお時間いただけますか?相談に乗ってもらいたいことがあるのですが」

 「もちろん。どんな相談ですか?」

 「実は、今後のモバイルアプリケーション開発で使う、組み込みデータベースの選択肢を整理しようとしているんですが…」

 「AndroidやiOSアプリ用のデータベースという理解でよいのかな?」

 「はい。それから、モバイルアプリだけでなくエッジデバイスも視野に入れて整理できればと思っています」

 「了解。調査は、どの程度まで進んでいますか?」

 「調べ始めたばかりで、まだ資料など作っていないため、お見せしながら話すことはできないんですが…」

 「それは大丈夫。どの程度調べたのか聞かせてもらえますか?」

 「ありがとうございます。まず、SQLiteについては外せないと思っています」

 「うん。それから?」

 「後は、iOSのCore DataやAndroidのRoom、それから、こうしたプラットフォーム固有の技術以外の選択肢として、Realmを取り上げようと思っています。モバイル用データベースは他にもあると思うんですが、その点で知恵を貸していただければと思って、相談させていただきました」

 「わかりました。今後、整理するにあたって気にかけている部分はありますか?」

 「MBaaS (Mobile Backend as a Service)について、別途主要クラウドプロバイダーのサービスをまとめようと思っているのですが、組み込みデータベースについても、クラウドやデータセンターのデータベースと同期する方法について整理したいと思っています」

 「Realmについてはある程度調べていそうですね」

 「まだ調べきれてはいませんが、RealmがMongoDBと同期できるということまではわかりました。MongoDBはNoSQLデータベースですよね。それで、他のNoSQLデータベースでも同じような機能を持ったものがあるかもしれないと思ったんです」

 「なるほど、それで私に声をかけたということですね」

 「はい、先輩はNoSQLデータベースについてご経験があると聞いたので」

 「では、今聞いた点を踏まえて話しますね。まず、データベースの種類については、どの程度意識されていますか?一口にNoSQLといっても色々な種類がありますが」

 「MongoDBは、ドキュメント指向データベースですよね。RealmもリレーショナルDBのようなテーブル形式でないデータを扱うという意味で、同じNoSQL同士という関係だと思っています」

 「そういう理解ですか。では、NoSQLについて話す前に、そもそもCore DataやRoomを利用する意味はどこにあると思いますか?」

 「漠然とした言い方かもしれませんが、SQLiteの制約を乗り越える、より進んだ選択肢ということだと思います」

 「それでは、SQLiteの制約とは何ですか?」

 「一面的な見方かもしれませんが、SQLクエリの結果をアプリケーションのデータモデルに変換するためのコードを自分で書かなければならないところだと思います」

 「いわゆるオブジェクトとリレーショナルのインピーダンスミスマッチ(Object-relational impedance mismatch1)と呼ばれるものですね。モバイルに限らず、データベース一般において、ORM(Object-relational mapping2)フレームワークが解決する課題ともいえますね」

 「はい。Javaでリレーショナルデータベースを使った開発の経験があるので、その点については理解しています」

 「SQLクエリは実行時に評価されるので、コンパイル時には検証されないのに対して、コンパイル時チェックによるタイプセーフなコードを開発することができる、という面から捉えることもできますね」

 「わかります」

 「そこからRealmについて考えてみると、どういう共通点、または違いがあるでしょう?」

 「RealmはNoSQLだから、SQLクエリの結果からオブジェクトに変換する必要がないですよね」

 「間違ってはいませんが、NoSQLだからというのは不正確です。そもそもNoSQLという言葉が、技術的な特徴について具体的に語るためには不十分です」

 「この場合は、ドキュメント指向データベース、ということですよね」

 「そう言うのも理解できますが、ここには誤解があります」

 「そうなんですか?」

 「そもそも、ドキュメント指向データベースとは何でしょうか?」

 「JSONデータを扱うデータベースと理解しています」

 「はい、広い意味ではドキュメント指向という言葉は、JSONだけでなくXMLのようなデータを含みますが、MongoDBや多くのドキュメント指向データベースはJSONデータを扱っています」

 「じゃあ、やっぱりドキュメント指向データベースという整理でよいのではないですか?」

 「どこから話そうか。まず、歴史的に言うと、Realmは2019年にMongoDBに買収されましたが、それ以前にも十分なシェアを持っていたといえます。日本でもRealmに関する書籍が出版されていたり、モバイルアプリ開発用データベースとしてSQLiteと共に紹介されていたりしました。だから、その頃から使っている人には誤解はないと思いますが、Realmはドキュメント指向ではなくオブジェクト指向のデータベースです。公式ページでも、そのようにはっきり記されています」

 「そうなんですね。違う種類というのはわかりましたが、実際どういう違いなんでしょうか?」

 「Javaの経験があるということだから、オブジェクト指向はわかりますよね?」

 「はい。クラスを中心としたアプリケーション開発、というと色々端折りすぎかもしれませんが…」

 「ここでの話としては、それで十分です。つまり、Realmを使う際にはクラス、つまりデータスキーマをあらかじめ定義する、ということです。そのスキーマは、RDBのテーブルスキーマより構造的に柔軟といえますが、それでも格納されるデータの定義が事前に必要であることに変わりはありません。一方、ドキュメント指向、つまりJSONデータベースはどうかというと…」

 「そうか、データの構造は、JSONデータそのもので定義されるから、事前のスキーマ定義は不要」

 「そう、それがスキーマレスとも言われるドキュメント指向データベースの特徴ですね。それはMongoDBについても当てはまりますが、Realと同期する際には、事前にデータスキーマを定義することになります」

 「そういうことなんですね。MongoDBはドキュメント指向データベースだけど、Realmはそうではなく、オブジェクト指向データベースということですね」

 「混乱するのも仕方がないかとも思います。データ構造において、SQLやRDBが扱うテーブルにはない柔軟性がある、というところまでは共通していますし、だからこそRealmとMongoDBとのデータ同期が成立する訳ですが、事前のデータ定義の必要性の有無という面で、ドキュメント指向とオブジェクト指向の違いは理解しておく意味があると思います」

 「はい」

 「この違いそれ自体は、優劣ではなく、性質の違いといえるでしょう。RealmはそもそもMongoDBのフロントエンドとして開発されたわけではなく、単体の組み込みデータベースとして開発されたものなので、アプリケーション内で定義されたデータを保存するという観点からは、データモデルの定義を前提とするオブジェクト指向という性質は自然、という見方もできると思います」

 「事後的にMongoDBとの連携が行われたことから、私のようなよく知らない者にとって、誤解の余地が生じているということですね」

 「そこで、本来私に期待されていた、データベースの紹介ということで言えば、ドキュメント指向の組み込みデータベースも存在しています」

 「あ、そうなんですね」

 「それは、Couchbase Liteというデータベースです」

 「JSON形式のデータを扱う組み込みデータベース、という理解でよいですか?」

 「その通りです」

 「なるほど。それは是非、将来のプロジェクトの選択肢として資料に加えたいと思います。自分でも調べてみますが、少し教えていただいてよいですか?」

 「もちろん。背景や位置づけについて把握してから調べた方が効果的だと思うので、説明しますね」

 「お願いします」

 「まず、データベースの名前はCouchbase Liteと言いましたよね。Liteがついているからには、LiteではないCouchbaseもあると考えるのが自然だと思いますが、実際Couchbase Serverというデータベースがあります。Couchbase Serverは、ドキュメント指向NoSQLデータベースで、JSONデータを扱います」

 「Couchbase Serverのライト版がCouchbase Liteということですね」

 「あくまで名前の由来という意味でね。NoSQLの特徴のひとつである分散アーキテクチャーを持つサーバーソフトウェアと、組み込みデータベースという根本の違いがあるので、言葉通りに受け取るのは危険ですが」

 「そうか、それはそうですよね」

 「当然、実装上コードを共有しているわけでもありません。言うなれば、どちらともドキュメント指向であることに始まり、多くの機能上の共通点を持つファミリーであるとは言えるかな」

 「ドキュメント指向という他に、どんな特徴がありますか?」

 「Couchbase ServerとCouchbase Liteに共通した特徴としては、SQLを使えることがあります」

 「え、何でここでSQLの話が出てくるんですか?NoSQLですよね」

 「厳密に言えば、SQLをJSONデータへのクエリのために拡張した言語です。SQL++とも呼ばれます」

 「SELECT…FROMみたいなクエリを使うんですか?」

 「クエリ文字列を使う方法と、クエリビルダーAPIという、クラスを使って式を構築する方法の二通りがあります。誤解のないようにいうと、ここからはCouchbase Liteにフォーカスしてお話ししますね。Couchbase Liteでタイプセーフなコードを優先する場合は、クエリビルダーAPIを使うことができます。もともと、クエリビルダーAPIのみが提供されていたところ、クエリ文字列を使う方法は後から追加されたという背景があります。Couchbase Liteは、C言語でも使うことができるのですが、C APIではクエリ文字列を使う方法のみが提供されています。C言語がサポートされたバージョンで、他のプログラミング言語でもクエリ文字列を使う方法が提供された、という経緯があります」

 「C言語での開発でもCouchbase Liteが使えるんですね」

 「はい。Couchbase Liteは、もともとAndroid Javaだけでなく、通常のJavaにも対応していたので、スマートフォン向けのモバイルアプリに限らず、JVMが利用できる環境で使うことができました。加えて、C言語に対応したことによって、より幅広い実行環境で、かつJavaを利用するよりも軽量なフットプリントで使うことができるようになっています」

 「なるほど、エッジデバイス向けデータベースの選択肢にもなるということですね」

 「そうだね。エッジでのユースケースとして、たとえばセンサーデータを収集するアプリケーションを考えてみると、センサーの扱うデータ項目が変更された場合にも、ドキュメント指向データベースであれば対応が容易という面があります」

 「なるほど、スキーマレスの利点は、そういう点に見つけることができるんですね」

 「さらに、データセンターやクラウドとのデータ同期についても、Couchbaseに委ねることができます」

 「RealmとMongoDBの組み合わせのようなことでしょうか?もう少し、説明してもらってもいいですか?」

 「Couchbase Liteは、単体の組み込みデータベースとして使うことももちろんできますが、Couchbase Serverとの双方向のデータ同期を行うための機能を内蔵しています」

 「元々、連携するように設計されているということですね」

 「その通り。これによって、クライアントとサーバーとのデータのやり取りという典型的かつ定型的な処理をデータプラットフォームに任せることができます。つまり、プロジェクトは本来のビジネスのための開発にフォーカスすることができる、ということですね」

 「MBaaSのニーズともクロスする部分ですね」

 「さらにいえば、モバイルアプリとWebアプリのようなマルチチャネルで共通のサービスを展開するニーズにもつながってきます」

 「なるほど。押さえておくべきことがわかってきました。自分でも調べてみますが、何か気をつけておくべきことなどありますか?」

 「クラウドやデータセンターとの同期やMBaaSまで視野に入れているのであれば、サーバーサイドのデータベースについてもそれなりに把握しておくことが重要になるかと思います。たとえば、ドキュメント指向データベースについては、MongoDB、Couchbaseだけでなくクラウドプロバイダーが提供しているものもありますし、それぞれで格納できるドキュメントのサイズ等、仕様も異なっています。この辺りは、まだ単純なところですが」

 「はい、調べてみます」

 「よりシビアなところでいうと、性能面が焦点になるかと思います。一口にモバイルないしエッジと言っても、業務用専用端末や工場に置かれたデバイスのようなインハウスのシステムなのか、それともコンシューマー向けの展開なのか、といったサービスの性質にもよりますが、拡張性との関係も考慮する必要があります。おそらく元々想定されていた調査の範囲を超えているでしょうが、意識しておく価値はあるはずです」

 「確かに。でも、どこから手を付ければいいのか…」

 「私が知っている範囲で、NoSQLの性能比較について、三つのリサーチペーパーが公開されています。ひとつは、Couchbase ServerとMongoDB、そしてDataStax Enterpriseを比較したもので、環境としてはAWSがIaaSとして使われています。3もうひとつは、DBaaSとしてのNoSQLを比較したもので、Couchbase ServerのDBaaSであるCouchbase Capellaと、MongoDBのDBaaSであるMongoDB Atlas、そしてAmazon DynamoDBとを比較しています。4最後は、やはりDBaaS同士を比較したもので、Couchbase CapellaとAzure Cosmos DBとを比較しています。5後でそれぞれのURLを送っておきます」

 「ありがとうございます!」

 「ひとまず、こんなところでしょうか?」

 「はい。後は、こちらで整理してみます」

 「お伝えしたことが、プロジェクトの適性に応じたデータベース選択の参考になれば嬉しいです」

1. https://en.wikipedia.org/wiki/Object%E2%80%93relational_impedance_mismatch

2. https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping

3. https://www.altoros.com/research-papers/performance-evaluation-of-nosql-databases-with-ycsb-couchbase-server-datastax-enterprise-cassandra-and-mongodb/

4. https://www.altoros.com/research-papers/performance-evaluation-of-nosql-databases-as-a-service-couchbase-capella-mongodb-atlas-amazon-dynamodb/

5. https://www.altoros.com/research-papers/performance-evaluation-of-nosql-databases-as-a-service-2021-couchbase-capella-and-azure-cosmosdb/

第1章 なぜ、Couchbase Mobileなのか?

 この章では、Couchbase Mobileの存在意義と、その技術的位置づけについて解説します。

1.1 モバイル/エッジコンピューティングデータプラットフォームCouchbase Mobile

はじめに

 Couchbaseという名前を持つデータベースには、Couchbase ServerとCouchbase Liteのふたつが存在します。モバイル/エッジコンピューティングデータプラットフォームという観点において、これらCouchbase ServerとCouchbase Liteの両方が重要な意味を持ちます。

 「Couchbase Mobile」とは、単体のデータベースないし何らかのソフトウェアを指す言葉ではなく、Couchbase LiteとSync Gatewayを包含する呼称です。

 Couchbase Liteは、AndroidやiOS、またはエッジデバイス上で実行されるアプリケーション用の組込データベース(Embeded Database1)です。Sync Gatewayは、Couchbase LiteとCouchbase Serverとのデータ同期を行う役割を担います。具体的には、Couchbase Serverと共に用いられるサーバーソフトウェアです。

 Couchbase Liteは、それ単体のみで利用することもできますが、ここでは、モバイル/エッジコンピューティングデータプラットフォームとしてのCouchbase Mobile総体について、「なぜCouchbase Mobileなのか?」という問いに答える観点から整理します。

モバイルアプリケーションにおけるCouchbase Mobileの価値

 モバイルアプリケーションで利用されるデータについては、モバイル端末上で作成・保存・利用されれば十分ということはむしろ稀であり、サーバーからのデータ取得やサーバーへのデータ登録・更新が行われることは珍しくありません。そのため、モバイルアプリケーション開発においては、多くの場合、アプリケーション内部のデータ管理と、サーバーとのデータ通信、というふたつの技術的要件が存在します。Couchbase Mobileは、ローカルデータベース(Couchbase Lite)とリモートデータベース(Couchbase Server)間の双方向の自動データ同期機能の提供という形で、この必要性に応えます。

 Couchbase Mobileでは、データ同期処理のことをレプリケーション、その処理を実行するモジュールをレプリケーターと呼びます。「レプリケーション(Replication)」という語自体は一般用語であり、コンピューティングの分野で用いられている場合も文脈に応じて多様な意味を持ちますが、Couchbase Mobileの文脈で単にレプリケーションという場合には、上述の内容を指しています。

 「サーバーとのデータ通信」という観点において、ローカルデータベースとリモートデータベースとの間のデータ同期(レプリケーション)という手法は、選択肢のひとつということは言えますが、決して王道とはいえないでしょう。クライアントアプリケーションとサーバーとの間でデータ通信が行われる場合、ローカルにデータベースを持つことは冗長ないし不要、さらにいえば、サーバーにあるデータこそがシングル・ソース・オブ・トゥルース(Single Source of Truth)であり、ローカルデータベースの存在は、データの一元管理という原則に反する、という観点もあり得るかと思います。これは、特にWebアプリケーション開発に馴染んだ立場からは自然な発想ともいえるでしょう。このような見方を踏まえた上でなお、ローカルデータベースを持つ理由・利点として、以下があります。


 ・ネットワーク依存の分離

 ・通信ロジックの分離


 以下、それぞれについて具体的に見ていきます。

ネットワーク依存の分離

 ローカルデータベースを持つことによって、ネットワーク通信が行えない環境下ではローカルに保存してあるデータを利用しつつ、ネットワークが回復した際にデータの同期を行うことができます。たとえば、想像しやすいところでは、飛行機の搭乗員用のアプリケーションを考えてみることができます。2このような設計を指して、オフラインファーストという言葉が使われます。

 一方で、昨今では「ネットワーク通信が行えない環境」と言われても、非常に特殊なケースという印象を持つ方も多いかもしれません。その意味では、オフラインファーストアプリケーションは、ユーザーエクスペリエンス向上の一形態と考えた方がわかりやすいかもしれません。

 ローカルデータベースを持たないアプリケーションが、サーバーからデータを取得して表示する場合、ユーザーがアプリケーションを立ち上げてから、データが画面に表示されるまでの時間は、たとえそれが短い時間であっても、ユーザー体験としては意味のない単なる待ち時間でしかありません。これは旧来のWebアプリケーションに典型的に見られる状況ともいえます。

 ローカルデータベースは、このような形での「ネットワーク依存」をアプリケーションの操作性から分離するために役立ちます。ローカルデータベースを用いることによって、アプリケーションの起動直後はローカルに保存されているデータを利用しながら、データの最新化については、バックグラウンドでユーザーが気がつかないうちに行うという設計が容易に実現可能になります。当然ながら、これはローカルデータベースを導入するだけで自動的に解決するものではなく、開発者が適切にユーザー体験をデザインする必要があります。

通信ロジックの分離

 ここまで、ローカルデータベースの意義について、ユーザーの利便性の観点から見てきました。「オフラインファースト」にしろ「ユーザーエクスペリエンス向上」にしろ、必要なのはデータの扱い方を適切に設計することであり、ローカルデータベースの導入は、その手段でしかないといえます。

 ここで、「ローカル」データベースに関する考察を離れて、より広い視野からデータベース一般に視点を移動すると、そもそもデータベースは、システムにおけるデータ(管理)と(ビジネス)ロジックの分離のために存在しているといえます。つまり、データ管理に必要な汎用的な処理を信頼性のある既存のテクノロジーに任せることによって、開発者は、それ以外の部分の開発に注力することができるようになります。ローカルデータとリモートデータの同期を、データベース(データプラットフォーム)に任せることによって、開発者はさらに、通信ロジックの開発についても、信頼性のある既存のテクノロジーに委ねることが可能になります。

 これには、次のような副産物が伴います。

データ操作性の一元化

 ローカルデータとリモートデータが暗黙理に同期される状況において、開発者は、ローカルで生成されるデータとリモートから入手するデータについて、それぞれ異なる取り扱い方をする必要がなくなります。アプリケーションにおけるデータ操作は、データがローカルで生成・保存されたのか、リモートから同期されたのかどうかに関わらず、ローカルデータベースに対するAPIコールやクエリという形でいわば一元化されます。

データへのアプローチのパラダイムシフト

 この場合、開発者は、リモートデータの入手や保存のための「操作」を設計・開発するのではなく、同期されるデータの範囲や利用できるユーザーやロールを、プラットフォームの提供する方法によって、「構成」することになります。これは、命令的プログラミング(Imperative programming3)から宣言的プログラミング(Declarative programming4)へのパラダイムシフトに比して考えることができます。あるいは、Kubernetes5における宣言的構成管理との類比で捉えることもできるでしょう。

マルチチャネルにおけるテクノロジー基盤

 また現在では、ひとつのサービスが、モバイルアプリケーションとWebアプリケーションの両方の形態で提供されることも珍しくありません。このような場合には、クラウドやデータセンターに存在するデータベースが、ふたつの異なる形態で実装されたアプリケーションのための共通のデータソースとなります。共通のデータソースとなるデータベースに、ローカルデータベースとの同期をサポートしているデータベースを採用することは、サービスを構成するシステム全体にとって、統一されたテクノロジー基盤を持つことに繋がります。

開発コスト削減と先進的技術導入促進

 通信ロジックを個々のアプリケーションのために、新たに開発しなくても済むことが開発コストの削減につながることは見やすいところです。また、既存の技術を活用することは、自ら開発するよりも、すでに多くの環境で実績のある、より信頼性の高いコードを利用することにつながります。もっとも、学習コストやメンテナンス性の違いなど考慮すべき要素があり、単に楽観的にコストが削減できると考えるのは短絡的であるという見方もあるかもしれません。

 一方、既存の技術を活用することによって、先進的な技術の導入のハードルを下げることができるという利点があります。以下、この観点から、技術的な内容に及びますが、例を挙げて説明してみたいと思います。

 Couchbase Mobileのレプリケーションは、WebSocket上のメッセージングプロトコルとして実装されています。WebSocketプロトコルは、単一のTCPソケット接続を介してリモートホスト間で全二重メッセージを渡すことを可能にします。Couchbase Mobileのメッセージングプロトコルは、WebSocketレイヤーを用いた階層化アーキテクチャにより、レプリケーションロジックとその基盤となるメッセージングトランスポートの間の「関心の分離」を実現しています。

 WebSocketプロトコルは、REST APIのようなHTTPをベースとしたプロトコルよりも高速で、必要となる帯域幅とソケットリソースを削減することができます。さらに、ソケットリソースの節約により、サーバー側での同時接続数を増やすことができます。このようなWebSocketによるメッセージプロトコルを、自ら開発するのは相当に高いハードルだといえます。多くの場合、WebSocketを用いることは絶対条件ではないかもしれませんが、その利点は、既存技術を利用することによる導入の容易さと併せて、検討する価値があると考えられます。

 上記のような要素技術の面だけでなく、より一般的にいって、特にエンタープライズでの利用において求められるレベルのセキュリティーを考慮して設計・実装され、多くの運用実績を持つ環境を利用できるという利点は、付け加えておく意味があるでしょう。

 これらは一部の例ですが、コスト最適化は、目先の開発コスト削減に関してのみではなく、サービスの先進性や品質等に関する競合との差別化に要するコストと効果のバランスの面からも見る必要があることがわかります。

エッジコンピューティングにおけるCouchbase Mobileの価値

 エッジコンピューティングにおける重要な要素として、エッジデータセンターの活用があります。各パブリッククラウドから、下記のようなエッジデータセンターを実現するサービスが提供されています。


 ・AWS Local Zones

 ・AWS Wavelength

 ・AWS Outpost

 ・Azure Edge Services

 ・Google Distributed Cloud Powered by Anthos


 エッジデータセンターの活用、すなわち複数の異なるデータセンターからなる多階層でのデータ同期においては、きめ細かいアクセス制御を実施しながら、中央集中型データセンターと多数のエッジデータセンター間でのデータ同期を制御する必要があります。上述のようなサービスが各社から提供され利用できる現在の状況を鑑みて、このような要件は、ますます重要になっているといえるでしょう。

 Couchbase Mobileは、サーバーに対する複数のクライアントからなる古典的なスタートポロジー構成に留まらず、マルチティアのネットワーク構成に対応することができます。

 Sync Gatewayには、Sync Gateway間レプリケーション(Inter Sync Gateway Replication)機能が備わっています。これによって、複数の異なるデータセンターに存在するSync Gateway間の同期を実現します。

 Sync Gateway間レプリケーションを用いることによって、クラウドや中央データセンターにあるCouchbase Serverと同期する、ネットワークトポロジーのハブとしてCouchbase ServerとSync Gatewayをエッジデータセンターに配置することができます。これにより、Couchbase Liteを使ったアプリケーションは、高速・低遅延といった、エッジデータセンターの提供するデータローカリティーによる恩恵を受けることができます。

エッジデバイスにおけるCouchbase Mobileの価値

 センサー等のIoT/エッジデバイスで発生した情報を収集するための技術として、データストリーミングを実現する様々な技術が存在します。エッジデバイスからデータ収集を行う際の構成要素として、オープンソーステクノロジーとしては、たとえば以下のようなものがあります。


 ・Apache NiFi/MiNiFi

 ・Apache Flink

 ・Apache Kafka

 ・Apache Spark Streaming


 このようなデータストリーミングの技術を組み合わせ、下流と上流でデータの入出力を構成する代わりに、エッジデバイス上のデータベースとしてCouchbase Liteを配した上で、通信に関わる処理をCouchbase Mobileに委ねることが考えられます。

 Kafkaのようなソフトウェアは、ストリーミングデータをキューで管理することによって内部にデータを持つため、ネットワークの信頼性の影響を抑えることができますが、ローカルデータベースを活用することによって、オフライン状況でのデータハンドリングがより直接的に容易になる、という捉え方もできます。

 Couchbase Liteは、AndroidやiOSのようなモバイル端末で利用することができるだけではありません。Android JavaのみでなくJVMが実行できる環境で利用することができる他、C言語サポートが提供されており、Raspberry Piや、Debian、UbuntuのようなLinux OSが用いられているエッジデバイス上で、ネイティブアプリケーションに組み込んで利用することができます。

1.2 ドキュメント指向組み込みデータベースCouchbase Lite

はじめに

 ここでは、Couchbase Liteデータベースが、他のモバイル組み込みデータベース技術の中で、どのように位置づけられるかを見ていきます。

 モバイルアプリケーション用の組み込みデータベースの中でも、よく知られたものとして、SQLite6があります。あるいは、iOS開発者であればCore Data7を、Android開発者の場合はRoom8のことを考えるかもしれません。また、それらのようなプラットフォーム固有ではない技術として、Realm9が知られています。ここでは、これらについて順に見ていきます。その際には、データベースの特徴を最も端的に表す要素として、データモデリングの観点に特に注目して解説します。

SQLite

 SQLiteは、「SQLデータベースエンジンを実装するC言語ライブラリーであり、世界で最も利用されているデータベースエンジン」です(括弧内は、公式サイトからの抄訳)。

 以下に、Android developerサイトの公式ドキュメント10からのコードを引用しながら、基本的な使い方を紹介します。

 まずは、定数定義(Contract Class)を見てみます。

public final class FeedReaderContract {
    // To prevent someone from accidentally instantiating the contract class,
    // make the constructor private.
    private FeedReaderContract() {}

    /* Inner class that defines the table contents */
    public static class FeedEntry implements BaseColumns {
        public static final String TABLE_NAME = "entry";
        public static final String COLUMN_NAME_TITLE = "title";
        public static final String COLUMN_NAME_SUBTITLE = "subtitle";
    }
}

 データを格納するためには、テーブル定義を行います。以下は、そのためのクエリの例です。

private static final String SQL_CREATE_ENTRIES =
    "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +
    FeedEntry._ID + " INTEGER PRIMARY KEY," +
    FeedEntry.COLUMN_NAME_TITLE + " TEXT," +
    FeedEntry.COLUMN_NAME_SUBTITLE + " TEXT)";

private static final String SQL_DELETE_ENTRIES =
    "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;

 以下が、データベースにデータを挿入するためのコードです。先に確認したテーブル定義に従って、インサートするレコードの構造が決定されます。

// Gets the data repository in write mode
SQLiteDatabase db = dbHelper.getWritableDatabase();

// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle);

// Insert the new row, returning the primary key value of the new row
long newRowId = db.insert(FeedEntry.TABLE_NAME, null, values);

 以上、SQLiteの利用方法を見てきました。リレーショナルデータベースを用いたアプリケーション開発経験者にとっては、特に多くを説明する必要のない内容であると思います。

 SQLiteについて、同じくAndroidの公式ドキュメントでは、以下のように問題点が指摘されています。

RAW SQLクエリはコンパイル時に検証されません。

SQLクエリとデータオブジェクトを変換するには、大量のボイラープレートコードを記述する必要があります。

 そして、下記のように注意されています。

SQLiteデータベース内の情報にアクセスするための抽象化レイヤとしてRoom永続ライブラリーを使用することを強くおすすめします。

Room

 Roomは、Android SDKが提供するORM(Object Relational Mapping11)フレームワークです。

 Roomについても、SQLiteと同じく、Android developerサイトの公式ドキュメント12からコードを引用しながら、解説します。

 以下は、Roomにおけるエンティティー定義の例です。

@Entity
public class User {
    @PrimaryKey
    public int uid;

    @ColumnInfo(name = "first_name")
    public String firstName;

    @ColumnInfo(name = "last_name")
    public String lastName;
}

 以下は、上記エンティティーに対応するDAO(Data Access Object)定義です。内部で、SQLクエリが用いられているのがわかります。

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
           "last_name LIKE :last LIMIT 1")
    User findByName(String first, String last);

    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);
}

 以下は、上記のDAOを扱うRoomDatabase継承クラスです。

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

 次のコードでは、RoomDatabase継承クラスのインスタンスを取得しています。

AppDatabase db = Room.databaseBuilder(getApplicationContext(),
        AppDatabase.class, "database-name").build();

 以下では、DAOを介してエンティティーのリストを取得しています。

UserDao userDao = db.userDao();
List<User> users = userDao.getAll();

 上記の流れは、ORM(Object Relational Mapping)フレームワークに典型的なものといえるでしょう。

Core Data

 Core Dataは、「アプリケーションでモデルレイヤーオブジェクトを管理するためのフレームワーク(a framework that you use to manage the model layer objects in your application)」です(括弧内の引用は、Apple公式ドキュメント「What Is Core Data?」13より)。

 Core Dateにおいて、データモデル定義には、Xcode IDEのモデルエディターUIを用います。

 定義したモデルは、プロジェクト内で、マネージドオブジェクトとして利用することができます。

 Core Dataについて、単にORM(Object Relational Mapping)フレームワークであるとする説明は誤りになるでしょうが、目的や開発された背景にはORMと共通のものを認めることができます。

Realm

 Realmは、オブジェクト指向データベースです。

 Realmでは以下のように、データモデルをRealmObjectを継承したクラスとして定義します(コードは、公式ドキュメントQuick Start14から引用)。ここでは、Taskクラスを定義しています。

import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import io.realm.annotations.Required;
public class Task extends RealmObject {
    @PrimaryKey private String name;
    @Required private String status = TaskStatus.Open.name();
    public void setStatus(TaskStatus status) { this.status = status.name(); }
    public String getStatus() { return this.status; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Task(String _name) { this.name = _name; }
    public Task() {}
}

 以下は、データ挿入の例です。Taskオブジェクトを直接、データベースに挿入します。

Task Task = new Task("New Task");
backgroundThreadRealm.executeTransaction (transactionRealm -> {
    transactionRealm.insert(Task);
});

 以下は、データ検索の例です。検索結果は、Taskとして型付けされています。

// all Tasks in the realm
RealmResults<Task> Tasks = backgroundThreadRealm.where(Task.class).findAll();

 以上からわかる通り、Realmは、あらかじめデータモデルの定義が必要なオブジェクト指向データベースであって、MongoDBやCouchbase Serverのようなドキュメント指向データベースとは異なります。MongoDB Atlasは、Realとのデータ同期に対応していますが、Realmとの同期の場合には、MongoDBクライアントと同等の柔軟性が提供されるわけではないことに注意が必要です。

 Realmがオブジェクト指向データベースであることは単なる特徴であって、ドキュメント指向データベースよりも劣る欠点とは言えませんが、MongoDBとの関わりにおいて、それぞれの違いを意識する必要があります。

Couchbase Lite

 Couchbase Liteは、ドキュメント指向データベースです。

 Couchbase Liteでは、以下のようにドキュメントオブジェクト(MutableDocument)に対して、プロパティーとして値を設定します。事前にデータモデルを定義する必要はありません。

MutableDocument newTask = new MutableDocument();
newTask.setString("type", "task");
newTask.setString("name", "データベース設計");
newTask.setString("owner", "TBD");
newTask.setString("status", "NEW");
newTask.setDate("createdAt", new Date());
try {
    database.save(newTask);
} catch (CouchbaseLiteException e) {
    Log.e(TAG, e.toString());
}

 このように、Couchbase Liteには、特定のデータモデルを表すテーブルスキーマやクラスといったものが存在しません。15

 Couchbase Liteでは、データ(ドキュメント)の種類を表す以下のふたつの方法があります。これらは時に併用されます。


 ・種類を表す特定のプロパティーを用います。典型的には「type」というプロパティー名が用いられます。

 ・種類を表す名称をドキュメントを一意に特定するキー(ドキュメントID)の一部に用います。たとえば、「user::001」のように、種類を表す接頭辞の後、任意のセパレーターが置かれ、後続の値によりキーがデータベース内で一意となるように設計します。


 Couchbase Liteは、SQL準拠のクエリが利用できるため、たとえば以下のようなWHERE句を用いて、特定の種類のドキュメントを検索することができます。16

WHERE type = 'task'

まとめ

 データモデリングの観点から各データベースについて見てきました。

 データモデリングにおけるそれぞれの違いは、一次的にはそれらの特徴であって、優劣ではないということができます。一方で各OSが提供する独自の技術は、その存在意義を示すために、SQLiteと比べて優位性を強調しています。

 各OSが提供する独自の技術は、そのOS専用にアプリケーションを構築する場合には、固有の能力を発揮します。一方で、同じ目的を持ったアプリケーションをそれぞれのOSのために作成する際の合理化のために、RealmやCouchbase Liteのような、中立のテクノロジーを活用することが考えられます。

 ここでは詳細に及ぶことはできませんでしたが、ここで紹介したデータベースには、それぞれ特色があり、データベースへのクエリの方法などは違いのわかりやすい部分でしょう。

 「なぜ、Couchbase Liteなのか?」という問いへの答えについて、他のデータベースとの比較という形は取りませんが、後続の章についても参考としていただければ幸いです。

データ同期機能

 リモートデータベースとデータを同期することによって、異なる端末でも同じデータが扱えるようにすることは、特殊な要件ではありません。様々なデータベースがそれぞれのやり方で、このような要件に対する配慮を示しています。

 ここでは、これまで紹介したそれぞれのデータベースについて、リモートデータベースとのデータ同期機能について簡単に触れます。

 MongoDB Atlasは、Realmはとのデータ同期機能17を提供しています。

 Core Dataは、CloudKit18による、iCloudとの同期をサポートしています。

 Roomでは、ネットワークとデータベースからページングする(Page from network and database19)ためのRemoteMediatorというコンポーネントが提供されています。

 以上は、あくまでそれぞれのデータベースにおいて、リモートデータベースとのデータ同期という要件が考慮されていることを示すための概観に過ぎません。それぞれについて注目すべき特徴や、あるいは、ここに挙げた以上の機能の存在、あるいは今後の機能拡張があるかもしれませんが、それらについては是非それぞれのデータベースに関する情報へ直接当たっていただきますようお願いします。


組み込みデータベースの観点から見たMBaaS

 各パブリッククラウドから、以下のようなMobile Backend as a Serviceが提供されています。


・Google Firesbase

・AWS Amplify

・Azure Mobile Apps


 それぞれ、iOSやAndroidといったモバイルプラットフォームの差を吸収する(両方に対応する)という意味において、RealmやCouchbase Liteと似た位置づけを持っているとも言えますが、MBaaSは、本質的にはバックエンドサービスをクラウドとして提供するものであり、組み込みデータベースと単純に比較を行えるものではありません。

 一方、それぞれのサービスからオフライン時を想定した機能が提供されています。ここでは、ローカルデータベースとしての機能という観点から、それぞれについて簡単に触れます。

 AWS Amplifyは、ローカル永続データストア機能として、Amplify DataStore20を提供しています。Amplify DataStoreでは、データモデルの定義としてGraphQLスキーマを使用します。リモートバックエンドと同期する場合には、AWS AppSyncと連携します。その際、GraphQLをデータプロトコルとして使用します。同期対象のリモートデータベースとして、AWSが提供するフルマネージドNoSQLドキュメント指向データベースDynamoDBがあります。

 Firebaseの提供するローカルデータサポートは、一時的なオフラインのためのキャッシュとしてのものであるといえます。Firesbaseでは、リモートデータベースとしてリレーショナルデータベースを利用することもできますが、NoSQLデータベースとしてはFirestore21を利用できます。

 Azure Mobile Appsは、オフラインデータ同期(Offline Data Sync22)機能としてローカルストアを提供します。これは、クライアントSDKの機能(MobileServiceSyncTable)として位置付けられ、オンライン操作時における、MobileServiceTableと同等の操作性を提供しています。同期先のリモートデータベースとして、Azure SQL DatabaseとAzure SQL Serverを使うことができます。

 クラウドプロバイダーのサービスは、常に進歩しています。ここでの記述内容については、著者の把握できていない部分や、今後変わっていく、あるいはすでに変わっている部分も存在することでしょう。是非、それぞれのサービスの一次情報を確認いただきますようお願いします。

1. https://en.wikipedia.org/wiki/Embedded_database

2. https://www.couchbase.com/customers/united-airlines

3. https://en.wikipedia.org/wiki/Imperative_programming

4. https://en.wikipedia.org/wiki/Declarative_programming

5. https://en.wikipedia.org/wiki/Kubernetes

6. https://www.sqlite.org/index.html

7. https://developer.apple.com/documentation/coredata

8. https://developer.android.com/jetpack/androidx/releases/room

9. https://realm.io/

10. https://developer.android.com/training/data-storage/sqlite

11. https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping

12. https://developer.android.com/training/data-storage/room?hl=ja#java

13. https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/index.html

14. https://docs.mongodb.com/realm/sdk/android/quick-start-local/

15. Couchbase Serverでは、リレーショナルデータベースにおけるテーブルに対応するコレクションというキースペース(名前空間)が存在します。

16. Couchbase Serverでは、SELECT * FROM taskのようなクエリ表現が可能です。

17. https://www.mongodb.com/docs/atlas/app-services/sync/learn/overview/

18. https://developer.apple.com/documentation/coredata/mirroring_a_core_data_store_with_cloudkit/setting_up_core_data_with_cloudkit

19. https://developer.android.com/topic/libraries/architecture/paging/v3-network-db

20. https://docs.amplify.aws/lib/datastore/getting-started/q/platform/js/

21. https://firebase.google.com/products/firestore

22. https://docs.microsoft.com/ja-jp/azure/developer/mobile-apps/azure-mobile-apps/howto/data-sync

試し読みはここまでです。
この続きは、製品版でお楽しみください。