この記事は、2025-11-26(水) に行なわれた、OutSystems Users Meetup in Osaka のセッションで話した内容を、あらためて解説したものです。セッションで使用したスライドは、Speaker Deck で公開しています。
【OSUG*OJEC合同開催】OutSystems Users Meetup in Osaka - OutSystems User Groups
https://usergroups.outsystems.com/events/details/outsystems-inc-osaka-presents-osugojeche-tong-kai-cui-outsystems-users-meetup-in-osaka-1/
はじめに
OutSystems によるモバイルアプリの開発では、サーバー上の DB エンティティとは別に、クライアントつまりモバイルデバイス上に保持される、ローカルストレージ上のエンティティを扱うことができるようになっています。ローカルに保持したデータは、デバイスがオフラインでも使えます。
このローカルエンティティのデータを、サーバーエンティティのデータと同期したい、というユースケースは数多く存在します。下記はその例です。
- アプリ内で表示するメッセージは、サーバーのメッセージマスターで管理。このメッセージマスターを、ローカルにも同期しておく。そうすれば、メッセージを表示するときにサーバーリクエストを発行する必要がない。(片方向同期)
- オフラインでもアプリにレポートを入力できるようにしたい。かつ、サーバーに上がっている他の人のレポートをローカルでも閲覧できるようにしたい。(双方向同期)
こういう、サーバー/ローカル間のデータ同期を、アプリ開発者が少ない手間で実装できるように用意されているのが、Offline Data Sync フレームワークです。
-
モバイルパフォーマンス戦略とオフライン最適化 - OutSystems 11 ドキュメンテーション
https://success.outsystems.com/documentation/11/building_apps/data_management/mobile_performance_strategies_and_offline_optimization/
Offline Data Sync フレームワークの公式ドキュメントには、片方向・双方向といったデータ同期パターンごとのロジック(アクションフロー)の構築例などが、かなり詳しく解説されていますので、本稿では、データ同期については触れません。
代わりというか、むしろ別の話題として、本稿ではこのフレームワークで使われている非同期ジョブ管理の仕組みが、クライアントサイドにおける汎用の非同期ジョブ基盤として利用できる、という観点での解説を試みます。
Offline Data Sync フレームワークの構成要素
非同期ジョブ基盤としての話の前提として、まずは、Offline Data Sync フレームワークがどういう実装で構成されているのかについて、簡単に触れておきます。
以降の説明は、O11 の場合の話です。ODC の Offline Data Sync フレームワークは、2025 年 9 月に大幅に変更されて、O11 とはかなり異なる構成になっています。その点については、最後にすこしだけ触れます。
1)OutSystems UI
UI ブロックとクライアントアクションが用意されています。OutSystems UI は標準ライブラリなので、開発者は内容に手を入れません。ブロックは、UI を提供するものではなく、非同期イベントを取り扱うための実装のみです。また、これらのブロックやアクションを、開発者が自分の開発物に直接組み込むことも、通常はありません。
2)モバイルアプリテンプレート
Service Studio でモバイルアプリをスクラッチから作成するときに使われるアプリテンプレートに、Offline Data Sync フレームワークの実装があらかじめ組み込まれています。
OutSystems UI のブロックが組み込まれた Offline Data Sync フレームワーク用のブロックがあり、これがレイアウトブロックに組み込まれています。また、Data タブの Client Actions には、開発者が実装を追加する箇所としての OfflineDataSync アクションと、手動で処理をトリガーするための TriggerOfflineDataSync アクションが用意されています。
3)Service Studio での半自動実装
アクセラレーションとかスキャフォールディングと呼ばれる、Service Studio 上での開発者の操作で半自動実装する仕組みに、Offline Data Sync フレームワーク用のものが用意されています。サーバーエンティティからのローカルエンティティの作成や、同期パターンに応じたクライアントアクション実装があります。
これらの半自動実装は、サーバー/ローカル間のエンティティ同期に関するものばかりなので、本稿では触れません。
処理実行開始の流れ
Offline Data Sync フレームワークでは、開発者が組み込んだ処理を実行開始する、非同期のトリガーイベントが、3つ用意されています。
- アプリがオンラインになったとき
- アプリが最前面(フォアグラウンド)になったとき
- 専用のトリガーアクションが実行されたとき
上の2つのトリガーイベントについては、使うかどうかを、それぞれ独立に設定できます。
実は設定には、これら3つとは別に、ログイン時にも実行するかどうか、という項目も存在します。が、その実装は、上記3つで使う非同期のための処理アクションを、ログインアクションフローに直接組み込んで同期的に実行させる、という内容なので、ここでは触れません。
これら3つのイベントをトリガーとして、OutSystems UI の専用ブロックで OnSync イベントがバックグラウンドで発火し、アプリテンプレートの専用ブロックのハンドラーアクションが実行され、それが同じくテンプレートの OfflineDataSync アクションを呼び出します。
先ほども触れたように、開発者は、この OfflineDataSync アクションに、アプリ用の実装を組み込みます。ここに組み込む実装は、別に、サーバー/ローカル間のエンティティ同期に関する処理でなくても何も問題ありません。つまり、クライアントサイドでの非同期ジョブの構築に、この仕組みを汎用的に利用することが可能です。
サーバーにおける非同期処理
Offline Data Sync フレームワークを、クライアント非同期ジョブに汎用的に利用する話を深掘りする前に、OutSystems のサーバー処理で非同期ジョブを実行する方法をおさらいしておきます。
例として、ウェブフォームからサブミットすると、レコードを更新して帳票を作成する、という処理を考えます。帳票の内容や、帳票をどうやって作るかで変わりますが、1件の帳票の作成に何十秒もかかることは珍しくありません。これを、OutSystems のアクションフローで真っすぐに組むと、サブミットボタンを押してからひたすらグルグルで待たされるウェブフォームになりますし、複数ユーザーによる同時実行が見込まれる場合は、プラットフォームサーバーの負荷があっという間に膨れ上がって、困ったことになります。
こういうときに使うのが非同期処理です。
例えば、帳票作成を Timer 処理として用意しておき、ウェブフォームのサブミットを処理するアクションフローには、作成処理本体の代わりに、この Timer のウェイクアクション(Timer 項目を作ったら自動的に作られるサーバーアクション)を配置します。こうすることで、サブミットを処理するアクションフローは、Timer の実行登録だけを行なってすぐに終了し、実際の帳票作成は、Timer を管理しているスケジューラーサービスがバックグラウンドで(非同期で)実行することになります。
この仕組みであれば、ウェブフォームのリクエストレスポンスはすぐ終わるようになるので、「同時実行数がー」とか「サーバー負荷がー」みたいな心配を、あまりしなくて良くなります。
(その代わり、作成した帳票のダウンロードについて、別途工夫が必要になります)
サーバーにおける非同期ジョブの実装は、Timer の他に、O11 の Process と Light Process、ODC の Event を使うことでも可能です。
クライアントにおける非同期処理
ウェブフォームサブミットからの帳票作成に似たイメージの、モバイルクライアント処理として、スマホアプリで撮影した写真の縮小画像の作成を考えます。写真一覧ページの UX をキビキビしたものに保つため、一覧に表示するサムネ用の縮小画像を、撮影時にクライアントサイドで作っておく、というケースです。
このとき、撮影のアクションフローの中に、サムネ作成を直接組み込むのではなく、先ほどの帳票作成と同じように、サムネ作成処理の実行登録だけを行なって、実際のサムネ作成を非同期に逃がすという実装が、Offline Data Sync フレームワークを使うことで可能になります。具体的な手順は次の通り。
- 時間のかかる処理(今回の例ではサムネ用の縮小画像の作成)をクライアントアクションに切り出す
- モバイルテンプレートの
OfflineDataSyncアクションのフローに、作成したアクションを組み込む - 実行登録を行ないたい箇所で、モバイルテンプレートの
TriggerOfflineDataSyncアクションを呼び出す - 処理種別を示す情報を、トリガーアクションの入力パラメーター
SyncUnitに設定する
サーバー処理の場合の Timer のウェイクアクションとは異なり、実行したい処理の内容によらず、同じトリガーアクションを使うことになるので、代わりに、処理種別を示す入力パラメーター SyncUnit を設定しているのがポイントです。上の例では、サムネ作成を示す Thumb- に続けて、ローカルに保存した画像の Id を渡しています。
先に見たように、トリガーされた後、OfflineDataSync アクションが非同期で呼び出されます。このとき、トリガー時に設定された SyncUnit が渡されるので、OfflineDataSync では、この値を見て処理を振り分けるような実装をします。
帳票作成に似たイメージのクライアント処理として、ここではサムネ作成を例に出しましたが、最近のスマホはとても速いので、JavaScript で適当に組んだとしても、縮小画像作成処理が1秒を超えることは、まずありません(私の手元のスマホでは 0.1 秒前後でした)。ですので、実際にはサムネ用画像の作成 だけ であれば、撮影フローに真っすぐ組み込んでも問題ないことが多いと思います。
非同期処理をチェーンする
Offline Data Sync フレームワークでは、非同期で開始された OfflineDataSync アクションの実行が完了すると、OutSystems UI のブロックを通じて、レイアウトブロックに OnSyncComplete イベントが発火するようになっています。これを利用すると、1レコードの処理に時間がかかるジョブを、1レコードずつ順番に、非同期で実行させることが可能です。
下図は、画像解析で1枚の写真に写っている人数を計測する、という処理を、一覧表示しているすべての写真に適用するのに、この OnSyncComplete を使った非同期処理のチェーンで行なっている例。前項で紹介した方法で、最初の1枚の計数処理をトリガー。その処理終了時に、OnSyncComplete を処理するハンドラーアクションで、結果の画面反映を行なった後で未計数がないかを確認し、あれば次の1枚をトリガーしています。
この方法は、オフラインで撮りためた写真を、1枚ずつネットワーク送信するような場合にも使えます。
Offline Data Sync フレームワークの公式ドキュメントは、エンティティ間の同期をターゲットにしているため、複数のデータレコードをリストに詰めてサーバーアクションに渡す実装しか解説されていません。これを無批判にリファレンスにして、撮りためた写真(複数写真)をネットワーク経由で送ろうとすると、大きなバイナリのリストを構築して長大なリクエストで送信する、という無茶な実装になり、結果として、クライアント・サーバー双方のリソース枯渇や、頻繁なリクエストタイムアウトの原因につながります。
自動トリガーイベントを起点に独自処理を行なう
「処理実行開始の流れ」で見たように、Offline Data Sync フレームワークでは、「アプリがオンラインになったとき」と「アプリが最前面(フォアグラウンド)になったとき」という、2種類の自動トリガーイベントを利用できるようになっています。
この2種類の自動トリガーイベントを起点に呼び出される OfflineDataSync アクションでは、SyncUnit パラメーターが空文字列の状態でフローが実行されるので、自動トリガーイベントでも SyncUnit に紐づけた独自処理を行ないたい場合、例えば、アプリが最前面になったときに未処理が残っていたら残りを処理したいような場合は、そこであらためてトリガーアクションを呼ぶ実装をしておきます。
(このフロー図では、簡便のために空文字列判定を Otherwise にまとめていますが、それを推奨しているわけではありません。念のため)
エラー処理とリトライ
Offline Data Sync フレームワークを利用して実行されたバックグラウンド処理の最中にエラーが起きた場合、その例外を、モジュールの Global Exception Handler で拾うことはできません。代わりに、エラー発生時は、レイアウトブロックで OnSyncError というイベントが発火する実装がモバイルアプリテンプレートに組まれているので、エラー処理はこれを使って行ないます。
次は、エラー発生時のリトライについて。
例えば、ファイルを1枚ずつアップロードするような処理で、デバイスの電波が悪くてアップロードがこけたというケースでは、成功するまでリトライする必要があります。「成功するまで指定秒おきにひたすらリトライする」というシンプルな挙動で足りる場合は、設定を有効にするだけで、追加の実装は不要です。
この設定は、モバイルテンプレートに用意されている OfflineDataSyncConfiguration アクションで行ないます。このアクションは、実際の設定処理に渡すための値を、それぞれの出力パラメーターに設定する、というだけの内容で、自動リトライを利用するのであれば RetryOnError を True にし、RetryIntervalInSeconds にリトライ間隔秒を代入します。前の項で見た、どの自動トリガーイベントを利用するかも、このアクションで設定できるようになっています。
リトライ回数上限を設けたいとか、指数バックオフでリトライ間隔を調整したい、という場合は、自動リトライはオフに設定し、上述した OnSyncError を処理するハンドラーアクション内で、条件を見て再トリガーする実装を行なうことになります。
O11 と ODC の違い
ここまで見てきたように、O11 の Offline Data Sync フレームワークは、OutSystems UI とレイアウトブロックに依存した実装なのですが、ODC では、2025 年 9 月のアップデートで、OutSystems UI を利用しない仕組みになりました。より「製品ネイティブ」っぽい扱いに変わった、とも言えそうです。
-
ODCでビルトインのオフラインデータ同期を提供 - OutSystems 製品のリリースとアップデート情報
https://www.outsystems.com/product-updates/odc-offline-data-sync/
ODC Studio において、Logic タブ > Client Actions を右クリック > Add System Event > On Sync を選択することで、非同期実行したい処理を組むためのアクションフローが用意されます。また、ここで用意した OnSync イベントは、任意のクライアントアクションからトリガーできるようになっています。
O11 では、設定アクション内の代入式を書き換える、という方法だった、自動トリガーイベントやリトライの設定も、ODC Studio で開ける App の設定画面に、専用の UI が用意されました。
本稿で紹介した、モバイル非同期ジョブ基盤としての使い方自体は、ODC でもおおむね同様に行なうことが可能ですが、レイアウトブロックから独立してしまったため、画面に紐づけた実装を行なうのが、やや面倒になっています。
おわりに
OutSystems のモバイルアプリ開発で使うことのできる、サーバー/クライアント間のエンティティデータ同期を実装するための仕組み Offline Data Sync フレームワークが、データ同期以外の目的でも、汎用で使うことができる、という解説を試みました。
- Offline Data Sync フレームワークは、エンティティデータの同期以外の処理にも使用可能
- JavaScript コードを1行も書くことなく、ローコード・ノーコードでアクションフローを構築する方法で、クライアントサイドにおける非同期処理を組める
- 非同期で実行したい処理をクライアントアクションに切り分けておき、
OfflineDataSyncアクション内で、SyncUnit入力パラメーターの値で、切り分けたアクションを実行する - 非同期処理のトリガーは、
TriggerOfflineDataSyncアクションを呼び出すことで行なう。その際、SyncUnit入力パラメーターに処理内容を示す文字列を設定する - モバイルアプリがオンラインになったとき、およびアプリが前面になったとき、という2種類の自動トリガーや、自動リトライを、設定ベースで利用できる
- エラー処理は Global Exception Handler では行なうことができず、レイアウトブロックの
OnSyncErrorイベントを処理するハンドラーアクションで行なう必要がある - ODC では、フレームワークの実装がより製品ネイティブになり、利用方法や設定方法が変わったが、O11 同様に、汎用に利用することが可能
といった、Offline Data Sync フレームワークの特性を知っておくことは、OutSystems でのモバイルアプリ作成の幅を広げるのに役立つはずです。
本稿の内容が、楽しい OutSystems 開発の一助になれば幸いです。
(本稿で解説した内容は、2025 年 11 月時点の情報に基づいています)













