Xamarinアプリケーション開発に、NoSQLドキュメント指向組み込みデータベースCouchbase Liteを使ってみる

## はじめに

本稿では、NoSQLドキュメント指向組み込みデータベースCouchbase Liteを使ったXamarinアプリケーションを紹介します。

Couchbase Liteをアプリケーション開発に利用する意義については、以下の記事をご参考ください。

https://qiita.com/yoshiyuki_kono/items/3abd5e32801060a449d2

下記の記事で、Couchbase LiteをC#アプリケーション開発で利用するためのセットアップと動作確認のプロセスを紹介しました。

https://qiita.com/yoshiyuki_kono/items/a0a9e319aa43e615e5aa

本稿では、Couchbase Labsで公開されているサンプルアプリケーションソースコードを使って、Xamarinアプリケーション開発にCouchbase Liteを使う方法を、具体的なコードによって解説します。

本稿の内容は、以下のチュートリアルの内容に基づきます。

https://docs.couchbase.com/userprofile-couchbase-mobile/standalone/userprofile/xamarin/userprofile_basic.html

ここでは扱っていませんが、Couchbase Liteをモバイルアプリ開発に利用する利点として、Couchbase Serverとの双方向データ同期が可能なことがあります。Couchbase Serverについては、日本語で読むことができるまとまった情報として、次の拙著を紹介させていただきます。

https://www.amazon.co.jp/-/en/%E6%B2%B3%E9%87%8E-%E6%B3%B0%E5%B9%B8/dp/429560030X/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=1636588872&sr=1-1

## サンプルアプリケーション解説


今回紹介するアプリケーションは、XamarinでCouchbase Lite 2.xを（Couchbase Serverと同期せずに）「スタンドアローン」で利用するものとなっています。

アプリケーションは、Xamarin.Forms、C＃、XAML（Extensible Application Markup Language）を使用しています。

iOS、Android、およびUWP用のコードが提供されています。

### 前提条件

- iOS（Xcode 10以降）
- Android（SDK 21+）
- UWP（Windows 10）


### アプリケーション仕様

ユーザープロファイル情報管理機能を実装したアプリケーション（Couchbase Liteの機能利用方法の説明に特化された、非常に簡易なものとなっています）。

ユーザーは、アプリ内で管理されているアカウント情報を使用して、アプリにログインします。
ログイン後、そのユーザーのプロファイル情報が表示され、編集することができます。
新規登録の場合も、ログイン画面にて情報したユーザーが未登録の場合は新規ユーザーとして追加され、入力した情報を引き継がれたプロファイル画面に遷移後、追加の情報を入力します。

プロファイル情報は、アプリ内のローカルデータベースであるCouchbase Liteに保存されます。

アプリケーション操作イメージを以下示します。

![image.png](https://docs.couchbase.com/userprofile-couchbase-mobile/standalone/userprofile/_images/xamarin/user_profile.gif)




### セットアップ

GitHubからソリューションの`standalone`ブランチをクローンします。

```BASH
git clone -b standalone https://github.com/couchbaselabs/userprofile-couchbase-mobile-xamarin.git
```

リポジトリの`/modules/userprofile/examples/src`の下に、ソリューションファイルUserProfileDemo.slnが存在します。


### ソリューション構造

ソリューションは、7つのプロジェクトで構成されています。

- UserProfileDemo：ビューレベルの機能を担当する.NET標準プロジェクト。
- UserProfileDemo.Core：ビューモデルレベルの機能を担当する.NET標準プロジェクト。
- UserProfileDemo.Models：単純なデータモデルで構成される.NET標準プロジェクト。
- UserProfileDemo.Repositories：Couchbase Liteデータベースの初期化および管理などを担当するリポジトリクラスで構成される.NET標準プロジェクト。
- UserProfileDemo.iOS：iOSプラットフォームプロジェクト。.ipaファイルのビルドを担当する
- UserProfileDemo.Android：Androidプラットフォームプロジェクト。.apkファイルのビルドを担当する
- UserProfileDemo.UWP：UWPプラットフォームプロジェクト。.exeファイルのビルドを担当する

このソリューションでは、MVVM、IoC、リポジトリパターンなどの、さまざまなデザインパターンと原則が利用されています。

### Couchbase Liteパッケージ

ソリューションにおけるCouchbase Liteの依存関係について解説します。
Couchbase.Lite Nugetパッケージは、このソリューションの4つのプロジェクト内で参照として含まれています。

- UserProfileDemo.Repositories
- UserProfileDemo.iOS
- UserProfileDemo.Android
- UserProfileDemo.UWP

以下は、UserProfileDemo.Repositoriesにおける定義の例です。

```xml:UserProfileDemo.Repositories.csproj
    <PackageReference Include="Couchbase.Lite" Version="2.6.0" />
```

`Couchbase.Lite`パッケージには、Couchbaseの Liteのコア機能が含まれています。これは、Couchbase Liteのコミュニティエディションにあたります。商業利用用のパッケージとして別に`Couchbase.Lite.Enterprise`があります。

## コードによる解説

### データモデル

Couchbase LiteはJSONドキュメントストアです。JSONドキュメントの論理構造は、名前付きフィールドと値のコレクションです。値は有効なJSONタイプです。標準のJSONタイプに加えて、Couchbase LiteはBlobおよびDateデータタイプをサポートしています。

このアプリケーションでは、ドキュメントの種類を特定するために、「type」プロパティを用います。これは必須ではありませんが、一つのCouchbase Liteデータベースに複数のデータモデルが混在する構成の場合の典型的な設計です。

このアプリは、「type」プロパティとして「user」の値を持つドキュメントを処理します。
また、ドキュメントIDの形式は「`user::<email>`」となっています。

#### ユーザープロファイルJSONデータ

ユーザープロファイル情報を表現するJSONドキュメントの例は次のようになります

```JSON
{
    "type":"user",
    "name":"Jane Doe",
    "email":"jane.doe@earth.org",
    "address":"101 Main Street",
    "image":CBLBlob (image/jpg) 
}
```

「`image`」プロパティは、プロファイル画像格納に用いられるBLOB型データを値として利用します。

#### ユーザープロファイルクラス

上述のデータモデルに対応するクラスとして、以下のような`UserProfile`クラスを用います。

これは、`ProfileDemo.Models`プロジェクトに含まれます。

```csharp
public class UserProfile
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string Address { get; set; }
    public byte[] ImageData { get; set; }
    public string Description { get; set; }
}
```

### Couchbase Liteアクティブ化

Xamarinアプリ内でCouchbase Liteを使用するには、Couchbase.Lite Nugetパッケージをプラットフォームごとにアクティブ化します。

#### iOS


```csharp:AppDelegate.cs
Couchbase.Lite.Support.iOS.Activate();
```

#### Android


```csharp:MainActivity.cs
Couchbase.Lite.Support.Droid.Activate(this);
```

#### UWP


```csharp:MainPage.xaml.cs
Couchbase.Lite.Support.UWP.Activate();
```

### データベース操作


#### データベース作成/オープン

ユーザーがアプリにログインすると、データベースが存在しない場合、空のCouchbase Liteデータベースが作成されます。

BaseRepository.csファイルを開き、`Database`プロパティを確認します。

```csharp:BaseRepository.cs
Database _database;
protected Database Database
{
    get
    {
        if (_database == null)
        {
            _database = new Database(DatabaseName, DatabaseConfig);
        }

        return _database;
    }
    private set => _database = value;
}
```

ここでは、`DatabaseConfiguration`のインスタンスを作成する際に、データベースのデフォルトパスを上書きしています。すべてのユーザーが、そのユーザーに対応するフォルダーの下に自身のデータベースを持つことになります（要件的には、１ユーザー＝１プロファイルとなるデータを格納するためには冗長に思われますが、ここでは機能の説明のためと理解してください）。


```csharp:UserProfileRepository.cs
DatabaseConfiguration _databaseConfig;
protected override DatabaseConfiguration DatabaseConfig
{
    get
    {
        if (_databaseConfig == null)
        {
            if (AppInstance.User?.Username == null)
            {
                throw new Exception($"Repository Exception: A valid user is required!");
            }

            _databaseConfig = new DatabaseConfiguration
            {
                Directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                                AppInstance.User.Username)
            };
        }

        return _databaseConfig;
    }
    set => _databaseConfig = value;
}
```

#### データベース変更イベントの活用

Couchbase Liteデータベースにリスナーを登録することによって、データベースの変更（追加、削除、更新）を非同期的に通知できます。

このアプリではデータベースへの変更をログに記録する以外のことは行っていません。より現実的なアプリでは、たとえばこの通知を使用してUIを更新します。

BaseRepository.csファイルを開き、`Database.AddChangeListener`コンストラクター内で関数の使用法を確認します。

リスナーをデータベースに登録するには、デリゲートメソッド`OnDatabaseChangeEvent`を追加します。`AddChangeListener`メソッドは、`ListenerToken`を返します。`ListenerToken`は、データベースからリスナーを削除するために必要とされます。

```csharp:BaseRepository.cs
DatabaseListenerToken = Database.AddChangeListener(OnDatabaseChangeEvent);
```

上記で引数として利用されている、`OnDatabaseChangeEvent`の定義は、以下のようなものです。


```csharp:BaseRepository.cs
void OnDatabaseChangeEvent(object sender, DatabaseChangedEventArgs e)
{
    foreach (var documentId in e.DocumentIDs)
    {
        var document = Database?.GetDocument(documentId);

        string message = $"Document (id={documentId}) was ";

        if (document == null)
        {
            message += "deleted";
        }
        else
        {
            message += "added/updaAted";
        }

        Console.WriteLine(message);
    }
}
```

#### データベースのクローズ
ユーザーがログアウトすると、ユーザーに関連付けられているCouchbase Liteデータベースをクローズし、リスナーの登録を解除し、メモリ割り当てを解放します。

BaseRepository.csファイルを開き、`Dispose`メソッドを確認します。
このメソッドは、データベースリスナーの削除、メモリからのさまざまなオブジェクトの削除、およびデータベースのクローズを処理します。 `Dispose`メソッドは、ユーザーがログアウトすると呼び出されます。

```csharp:BaseRepository.cs
public void Dispose()
{
    DatabaseConfig = null;

    Database.RemoveChangeListener(DatabaseListenerToken);
    Database.Close();
    Database = null;
}
```



### ドキュメント操作


#### ドキュメント読み込み

ユーザーがログインすると、ユーザープロファイル画面が表示されます。この時、内部的に、そのユーザーの「ユーザープロファイル」ドキュメントが読み込まれます。
ユーザーが初めてログインするとき、ユーザーのユーザープロファイルドキュメントはありません。

UserProfileViewModel.csファイルを開き、`userProfileDocId`定義を確認します。このドキュメントIDは、ユーザーのユーザー名の前に「user::」という接頭辞を付けることで作成されます。

```csharp:UserProfileViewModel.cs
string UserProfileDocId => $"user::{AppInstance.User.Username}";
```

`UserProfileViewModel`は、ログインしているユーザーのプロファイルを取得する役割を持ちます。ユーザーのプロファイル情報取得は、`IUserProfileRepository`を実装するクラスを使用して行われます。

```csharp:UserProfileViewModel.cs
var up = UserProfileRepository?.Get(UserProfileDocId);
```

UserProfileRepository.csの`Get`メソッドを確認します。

```csharp:UserProfileRepository.cs
public override UserProfile Get(string userProfileId)
```

データベースから指定された`userProfileId`をキーに持つドキュメントを取得します。

```csharp:UserProfileRepository.cs
var document = Database.GetDocument(userProfileId);

if (document != null)
{
    userProfile = new UserProfile
    {
        Id = document.Id,
        Name = document.GetString("Name"),
        Email = document.GetString("Email"),
        Address = document.GetString("Address"),
        ImageData = document.GetBlob("ImageData")?.Content
    };
}
```

データベースからドキュメントを取得した上で、その値を用いて`UserProfile`クラスのインスタンスを作成します。

ここでは、ドキュメントのメンバーをフェッチするために、いくつかの方法が使用されています。特に、`Blob`タイプのプロパティの値をフェッチするための`GetBlob`メソッドのサポートに注意してください。

#### ドキュメント作成/更新

「ユーザープロファイル」ドキュメントは、ユーザーが「プロファイル画面」の「完了」ボタンをタップすると更新または新規作成（初回実行時）されます。

`UserProfileViewModel`は、データベースに設定されたプロファイルの値を保存する役割を担います。これは、`IUserProfileRepository`を実装するクラスを使用して行います。

```csharp:UserProfileViewModel.cs
bool? success = UserProfileRepository?.Save(userProfile);
```

UserProfileRepository.csファイルを開き、`Save`関数を確認します。

```csharp:UserProfileRepository.cs
public override bool Save(UserProfile userProfile)
```

ここれは、`Document`のミュータブル（可変）インスタンスを作成します。

デフォルトでは、Couchbase LiteのすべてのAPIはイミュータブル(非可変)オブジェクトを扱い、設計上、スレッドセーフになっています。

オブジェクトを変更するには、オブジェクトの変更可能なコピーを明示的に取得する必要があります。適切なセッターメソッドを使用して、`Document`のさまざまなプロパティを設定します。Blobタイプのプロパティ用に`SetBlob`メソッドが利用されます。

```csharp:UserProfileRepository.cs
var mutableDocument = new MutableDocument(userProfile.Id);
mutableDocument.SetString("Name", userProfile.Name);
mutableDocument.SetString("Email", userProfile.Email);
mutableDocument.SetString("Address", userProfile.Address);
mutableDocument.SetString("type", "user");
if (userProfile.ImageData != null)
{
    mutableDocument.SetBlob("ImageData", new Blob("image/jpeg", userProfile.ImageData));
}
```

#### ドキュメント保存

```csharp
Database.Save(mutableDocument);
```

#### ドキュメント削除

このサンプルアプリでは、ドキュメントを削除していません。
以下のコードは、ドキュメント削除する場合のコード例です。

```csharp
var document = Database.GetDocument(id);

if (document != null)
{
    Database.Delete(document);
}
```

### 動作確認

#### アプリ実行とログイン

アプリを実行します。

任意のユーザー名とパスワードでアプリにログインします。ここでは、ユーザー名とパスワードのフィールドにそれぞれ「demo@example.com」と「password」という値を使用します。

#### ログ確認（iOS）
コンソールログの出力に次のようなメッセージが表示されていることを確認します。この例では、「demo@example.com」というユーザー名でログインしています。

```
Will open/create DB  at path Will open/create DB  at path /Users/[user_name]/Library/Developer/CoreSimulator/Devices/[unique_device_id]/data/Containers/Data/Application/[unique_app_id]/Library/Application Support/demo@example.com
```

#### データベースファイル確認

上記のログメッセージは、ユーザーのデータベースの場所を示しています。

Finderでフォルダを開き、「userprofile.cblite2」という名前のデータベースがユーザーに存在することを確認します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/5178e0b5-99ba-46de-f3c7-025a6b390e35.png)


## 最後に

本稿では、iOS、Android、またはUWPアプリでローカルのみのスタンドアロンの埋め込みデータストアとしてCouchbase Liteを利用するアプリケーションのサンプルを紹介しました。

Xamarinアプリケーション開発に組み込みデータベースを使った経験のある方であれば、以降の応用は、比較的簡単に可能かと思います。
最後に、次のステップに進むための情報を示して、本稿の締め括りとしたいと思います。

Couchbase Liteについての記事を以下の投稿で整理していますので、ご関心に応じて、参照してみてください。

https://qiita.com/yoshiyuki_kono/items/6a5bbad1b02196a25417

公式ドキュメントは、以下になります。

https://docs.couchbase.com/couchbase-lite/current/csharp/quickstart.html

今回の記事の一次参考情報は、以下のチュートリアルです。

https://docs.couchbase.com/userprofile-couchbase-mobile/standalone/userprofile/xamarin/userprofile_basic.html

上記チュートリアルの発展として、データベースに対するクエリを扱ったものがあります。

https://docs.couchbase.com/userprofile-couchbase-mobile/query/userprofile/xamarin/userprofile_query.html

また、さらに、Couchbase Serverとのデータ同期を扱ったものもあります。

https://docs.couchbase.com/userprofile-couchbase-mobile/current/userprofile/xamarin/userprofile_sync.html

他にも、公式ドキュメントの一部として、各種チュートリアルが公開されています。


https://docs.couchbase.com/tutorials/mobile-travel-sample/csharp/installation/travel-mobile-app.html

https://docs.couchbase.com/tutorials/session-storage/aspnet.html