OutSystemsにおけるデータの同期処理について
※当記事は2023年1月に執筆した記事で、情報は当時のものになります。
こんにちは!株式会社メンバーズの野村です。
前回に引き続きOutSystemsネタです。
今回はOutSystemsにおけるデータの同期処理について書いていこうと思います。
なぜ同期処理を考えるか
モバイル(スマートフォン)アプリケーションの利用を想定して考えます。
Webアプリケーションは常時インターネット接続が確立されている状態を想定しています。
ただしスマートフォンアプリの場合、その前提だと不便さを感じる場面があります。
スマートフォンは持ち運びにおける利便性が高いデバイスです。
時に電波が届かない場所で、アプリに対して何かを入力したいというシチュエーションがあるかと思います。
「インターネット接続がないと使えない」だとこの願望は叶いません。
オフラインであっても、スマートフォンのローカルストレージに対してデータを保持しておき、ネットワークに接続した後、良きタイミングでサーバーにデータを同期できれば利用上の問題はないわけです。
このようなシチュエーションの場合に同期処理を考慮する必要が出てきます。
OutSystemsにおけるデータ同期処理とそれを構成する要素
OutSystemsにはデータの同期処理を考慮した仕組みが用意されています。
サンプルとしてタスク管理のモバイルアプリケーションを作成しました。
アプリケーションの詳細は割愛しますが、
・タスクの登録・更新・削除の機能
・タスクにカテゴリーを設定でき、そのカテゴリーを編集できる機能
上記のシンプルな機能を持ちます。
このアプリケーションはオフラインでも利用可能で、以下のタイミングでデータ同期を行うようになっています。
- ログイン時
- オフラインからオンラインに復帰した時
- 各データの変更時
同期対象のデータの判定や、実装方法については後続の内容で触れていきます。
ローカルストレージエンティティ
デバイス本体のストレージを利用するために、「ローカルストレージエンティティ」と呼ばれるエンティティを利用します。エンティティのローカル版です。
モバイルアプリケーションはこのエンティティとやりとりを行うことで、ローカルでのデータ更新を実現しています。
ローカルデータエンティティへは、ServiceStudioのDataタブから添付画像のようにアクセスできます。
右クリックから「Add Entity」ローカルストレージエンティティを追加したり、「Add Entity from Databass…」より通常のエンティティを元にしてクイックに追加することもできます。
また、ローカルストレージエンティティは「読み取り専用」か「読み取り/書き込みのどちらも行う」の2通りの利用の仕方があります。
それに合わせて自動でアクションを作成してくれるアクセラレーター機能も存在します。
アクセラレーター機能ではクライアントアクションとサーバーアクションがそれぞれ作成されます。
「Create Action to Sync Data(Read-Only)」か「Create Action to Sync Data(Read/Write)」どちらを選ぶかで生成されるアクションが異なります。後者の方だと、データ同期がローカルとサーバーで双方向で行われる関係から少し複雑ですが、後に触れます。
OutSytemsにおけるモバイルアプリケーション開発においては、デフォルトでデータ同期を想定したアクションが用意されています。
それらのアクションの中に、アクセラレーター機能で生成されたアクションを組み込んでロジックを作り込むイメージです。
次にデフォルトで用意されているデータ同期用アクションについて触れていきます。
データ同期アクション
デフォルトで用意されている3つのクライアントアクション、1つのサーバーアクションの内訳は以下です。
【クライアントアクション】
- TriggerOfflineDataSync
後続で説明する「OfflineDataSync」を呼び出します。
データ同期処理を明示的に呼び出す際に利用します。
このクライアントアクションを経由して呼び出すことで非同期処理となり、バックグラウンドでデータ同期を行うことができます。
逆に言えばこれを利用しないで呼び出すと非同期処理にならず、不用意に待ち時間が発生する(≒アプリケーションの動作が重くなる)ので注意が必要です。
このアクションは「SyncUnit」「DiscardPendingSyncUnits」という2つの引数を取ります。- SyncUnit:ビジネスロジックから同期アクションに渡される文字列入力パラメータです。後述のOfflineDataSyncで利用しますが、シーンによって同期対象のデータを絞り込んで一度に大量データのやり取りをしないように制御したりできます。
- DiscardPendingSyncUnits:Trueに設定されている場合、同期待ち行列を空にします。保留中の同期を中止してすぐに新しい同期を開始する場合に有効です。
- OfflineDataSync
TriggerOfflineDataSyncから呼び出されることを想定したデータ同期処理のクライアント側実行処理です。
単純な利用ケースだと、ローカルストレージエンティティからアクセラレーター機能を用いて自動生成したアクションをここで呼び出してデータ同期を行う、といったイメージです。
添付の画像ではSwitchにてSyncUnitに応じた同期の制御を行っています。
例えば、タスクの登録を行った際は「TriggerOfflineDataSyncを引数SyncUnitに”Task”を指定して呼び出し、ローカルのタスクデータからサーバー側DBのタスクデータに同期を行う」ということをしています。
- OfflineDataSyncConfiguration
TriggerOfflineDataSyncはデータの同期処理を明示的に呼び出すものでしたが、こちらは「アプリケーションが特定の状態になった場合にデータ同期処理を呼び出す」ためのものです。
OfflineDataSyncConfigurationでは「特定の状態になったらデータ同期処理をするか否か」を表現するためのBoolean型のフラグ等を管理しています。
フラグ類はデフォルトは全てFalseになっており、意図せずにデータ同期処理が走らないようになっています。
対象の要素は以下です。- SyncOnOnline:Trueに設定されている場合、デバイスがオンラインになったときに同期をトリガーします
- SyncOnLogin::Trueに設定されている場合、ログイン後に同期をトリガーします
- SyncOnResume::Trueに設定されている場合、アプリケーションがバックグラウンドから復旧したら同期をトリガーします
- RetryOnError:Trueに設定されている場合、エラー発生時に新しい同期を試みます
- RetryIntervalInSeconds:同期の再試行間隔を秒単位で設定します
【サーバーアクション】
- ServerDataSync
サーバー側のデータ同期ロジックを記載する箇所です。
デフォルトだとユーザーの権限チェックとログの記載を行います。
同期処理の流れのイメージ
TriggerOfflineDataSyncによる明示的な呼び出しかOfflineDataSyncConfigurationにより指定した特定条件をトリガーとし、OfflineDataSyncクライアントアクションからServerDataSync、並びに同期用のアクションを呼び出し、ローカルストレージとサーバーDBの同期を行います。
TriggerOfflineDataSyncから明示的にOfflineDataSyncを呼び出すケースですが、例えば今回のサンプルアプリにおける「タスク登録機能」を例にとると、タスク登録後にTriggerOfflineDataSyncを用いてタスクデータの同期処理をしています。
基本的にオフライン状態でなければ、データ変更のトランザクション毎にデータの同期処理は行うというポリシーで設計しました。
データの同期はいくつかパターンがありますが.、大きく分けると、読み取り専用か or 読み取り/書き込みのどちらも行うパターンに分かれます。
どちらも同期用のアクション生成のアクセラレーター機能がありますが、読み取り/書き込みのパターンの場合はローカルストレージエンティティに3つの状態管理用のアトリビュートが作成されます。
これらは同期処理を行うにあたり、ローカルデータの情報管理を行うためのものです。
次はこの3つのアトリビュートについてご説明します。
ローカルエンティティにおけるデータ同期用アトリビュート
前述の通り、アクセラレーター機能を用いて読み取り/書き込みのための同期用のアクションを生成すると、サーバー上のDBのデータとローカルストレージエンティティを双方向で矛盾なく同期させるために、ローカルストレージエンティティに以下の3つの状態管理用のアトリビュートが作成されます。
- IsFromServer:Trueの場合、レコードがサーバーに存在しています。
- IsModified: Trueの場合、レコードがローカルで変更されています。
- IsActive: Falseの場合、レコードはローカルで削除されていますが、サーバーからは削除されていない可能性があります。
これら3つのアトリビュートを組み合わせることで、ローカルで追加 / 更新 / 削除 されたデータなのかを判定することができます。
同期の必要のあるデータなのかが判定でき、アクセラレーターで生成される同期用ロジックもこれらのアトリビュートを用いて対象データのみ同期可能という仕組みです。
このあたりは実際にサンプルアプリケーションを参考にされると理解の解像度があがるかと思います。
データの同期処理のパターンや、これらアトリビュートがロジックにどう関わっていくのかは以下のリンクから飛ぶ記事が参考になります。(サンプルアプリも参照できます)
同期処理を伴うアプリケーションを作成する際の注意点
同期処理を利用するアプリケーションを作成し、改めて思ったのですがいくつか気を付けるべき点があります。
公式のベストプラクティスで明言されているようなものもあれば、自分で感じた部分もありました。
いずれもアプリケーションの規模やデータの件数が大きく・多くなるほどに問題が顕在化するはずなので最初から意識すべき。
- 同期処理は極力対象を絞る
- 常に全データの同期を行う必要があるわけではないはずなので、OfflineDataSyncのSyncUnitを用いるなどして、同期対象のデータを小分けにし、トランザクションの不可を軽減すべき。
- ローカルストレージエンティティには利用する項目のみを保持し、結合は避ける
- ローカルの領域を確保するため、不要な項目は避けるべき。
そのため、サーバー側DBのエンティティ定義をそのまま定義するのではなく、非正規化してなるべく1エンティティにまとめ、複数のローカルストレージエンティティへの書き込みは避けるよう設計します。(個別にトランザクションを貼るから非推奨とのことです) - これは注意点というか、不便に感じたことなのですがローカルストレージエンティティとサーバー側DBのエンティティは結合不可です(ローカルストレージエンティティ同士はOK)
この制約により、例えば「ローカルストレージエンティティと静的エンティティを結合して、固定文言などの定数を取得したい」のようなシーンで、この二つが結合できない為に少し悩みました。
結局、ローカルストレージエンティティに静的エンティティの定義を持ち、ログイン時にデータ同期するようにしてデータを持たせ、ローカルストレージエンティティ同士の結合で固定文言の取得を行いました。
(これがベストかはわかりませんがこういう方法で回避もできるよ、という一案)
- ローカルの領域を確保するため、不要な項目は避けるべき。
- ローカルストレージエンティティを利用する場合は、同期先のエンティティを直接操作できるような仕組みは入れない
- これはベストプラクティスのような記述を見つけられなかったので場合によるのかもしれませんが、ローカルストレージエンティティを利用する場合はローカルストレージを介してのみデータ操作を行うべきと思っています。
そうでないとローカルデータとの同期状態が管理できず、意図した同期処理ができないケースが生じ得るためです。知恵と工夫で対応したとしても、本来想定していなような状態管理を行うことになり実装上のアンチパターンになり得るのではないかと感じました。
- これはベストプラクティスのような記述を見つけられなかったので場合によるのかもしれませんが、ローカルストレージエンティティを利用する場合はローカルストレージを介してのみデータ操作を行うべきと思っています。
まとめ
OutSystemsにおけるデータ同期処理を構成する要素について簡単にまとめてみました。
ローカルストレージの設計やデータ同期のタイミングは、ユースケースが肝になっていきます。
また、ローカルストレージの設計に関してはアプリケーションの都合を起点にして考えるという点で、RDBのモデリングとも少し視点が違うので、もう少しサンプルアプリケーションを触りながら勘所を身に着けていきたいと思います。
ここまで読んでいただきありがとうございました!
ローコード開発のご相談はラピッドスケールカンパニーまで!
私の所属しているラピッドスケールカンパニーではローコード開発を中心とた開発のご支援をさせていただいております。
ご興味のある方は以下のサイトよりお問合せください!
株式会社メンバーズ ラピッドスケールカンパニー
この記事を書いた人
関連記事
- Astroをフロントエンドフレームワークとして利用する
Hideki Ikemoto
- React Redux: 毎回新しい参照を返す select...
Daisuke Yamamura