オンライン時代を経たネイティブアプリのデータフロー(前半)

こんにちは。ネイティブエンジニアのszです。

今回はアプリ開発において、クライアント側で考えるべきデータフローについてお話します。

社内で勉強会を行った内容を前後半の記事に分けてお届けします。前半は導入部分になり、エンジニア以外の方にも伝えたい内容となっていますので、ぜひエンジニア以外の方にも一読いただけたらと思います。

データフローとは

image15

ここでいうデータフローとは、以下の3つのデータをプログラム上でどう扱うか、を指すものとします。

  • リソースデータ
    • 画像ファイルや音声ファイル
  • マスタデータ(ゲームデータ)
    • キャラクター名のようなゲームの設定値や、レベルデザイン等で入力するパラメータのように、どのユーザから参照しても同じ値になるデータ
  • ユーザデータ(セーブデータ)
    • ゲームの進捗情報など、ユーザ毎にパラメータの値が異なるデータ

image01

ではこのデータフローは何故重要なのでしょうか。

スマートフォンが世の中に普及しだした頃は、通信を行わないスタンドアローンと呼ばれるようなライトなアプリが主流でした。近年ではソーシャル要素の多い通信必須で大規模なゲームが主流になり、サーバとの連携が必要不可欠な要素になっています。

これはコンシューマゲームの業界から参入している企業にとっても同じ流れだと思います。スマホでもゲーム性が高く、クオリティの高いグラフィックスが求められるようになってきていますが、オンライン要素がないゲームはあまり見たことがないと思います。

一方でサーバサイドの開発から見た場合、スマホアプリより先にブラウザゲームという一大ブームがあります。サーバ(+ブラウザ)で完結する開発から、ゲームロジックがネイティブアプリ(クライアント)寄りになる開発が多くなってきています。

もちろん、ゲームロジックをサーバが持つのか、それともクライアント(ネイティブ)が持つのかという検討も大事ですが、今回はロジックではなくデータに絞って話を進めます。(サーバかクライアントかという話では、ロジックもデータもどちらも同じような考え方が必要なところもでてくるので、被る話もあります)

image06

上図のように、クライアント(左側から中央まで)、サーバ(右側から中央まで)それぞれで独立したアプリケーションの場合は、それぞれのリソースやデータベースの情報を画面上に表示するだけで、非常にシンプルなデータフローになります。

image14

しかし、サーバとネイティブの双方にデータをもつアプリの場合、サーバの情報をどうやって取得&更新するのかの検討が必要です。
例えば、都度http通信で必要な情報を取得するのか、アプリ起動時にまとめて取得&キャッシュするのか、などの方法から最適な選択肢を考えることになります。

では何故クライアントとサーバの双方に必要な情報があるのでしょうか?

例えばWebView主体のゲームのように、サーバ側にすべてのデータを置けば簡単になるのではないでしょうか?

image12

クライアントにデータがある理由は、ブラウザと違ってネイティブアプリは必ずしも通信が必須ではないため、通信がない箇所は高速でリッチな表現が可能になります。またサーバ側に負担をかけずに処理ができる点も大きなポイントです。

つまり、ゲーム性が高いゲームほど、ネイティブにデータやロジックを置く比率が高くなるということになります。(ゲームの内容次第なので一概には言えないかもしれませんが)

では逆に、スタンドアローンと呼ばれるアプリのように、クライアント側にすべてのデータを置けば簡単になるのではないでしょうか?

image05

サーバにデータがある1番の理由はやはり「ソーシャル機能の実現」が大きいです。自分だけで遊ぶゲームや、通信をしたとしてもその場限りの情報のみをやりとりする機能なら不要ですが。

image04

その他にも、ゲームのプレイヤーに対して、いかにストレスなくゲームを提供できるか、という点でサーバを利用することが必要になってきます。

例えばステージクリア型のゲームだった場合、新しいステージを追加する度にクライアントをバージョンアップさせるよりも、追加データのみダウンロードさせて動いたほうが、プレイヤーにとっても運用する側にとっても便利です。

また、例えばですが、アプリをダウンロードするときの容量が1GBだとします。

そのうち、900MBが全プレイヤーの1割しか必要のないデータだった場合、残りの9割のプレイヤーは無駄なデータをダウンロードしていることになります。

アプリダウンロード時は100MBにし、残り900MBは必要になったら都度ダウンロードするようにすれば、快適にアプリを遊べていると感じる人も増えるのではないでしょうか?

image02

サーバにデータやロジックを置けば置くほど、通信は必要になってきます。また、クライアントに置けば置くほどアプリバージョンアップ無しに機能を更新することが難しくなります。

このあたりのバランスを、つくるものによって最適な形にするのが重要です。

image00

このデータフローはクライアントの開発の中でも目に見えない部分になります。筆者の所感ですが、クライアントを担当するエンジニアの多くは、目に見える部分の実装にモチベーションを持っていることが多いです。また、コンシューマゲームも一昔前は通信をしないゲームが多く、そこまで考えた設計をした経験がある人はそれほど多くないのではないでしょうか。
そのため、エンジニアの中でこのデータフローについてこだわりをもってやってくれる人は少ないと感じています。

良い設計とは

image11

何故大事かというと、チーム開発を行う場合に各担当者が自分の分だけなんとかすれば良い、というレイヤ(領域)ではないからです。同じ情報を複数の担当者が利用する場合、機能の共通化やルールを設ける必要がでてきます。
また、UIのように目に見える部分ではないために、設計(品質)の悪さが問題になるのが開発後半やリリース後になってしまうことも少なくありません。
もちろん、開発規模によりOSなりミドルウェアが提供している機能を各自好きなように使っても問題ないケースはあります。それでもルールくらいは決める必要があると思いますが。
“取り返しがつかない”とか書くと大袈裟だと思われるかもしれませんが、一つ例を挙げます。例えば以下の様なアプリがあったとします。

  • アプリを初めて起動した場合、チュートリアルを実行した後にゲームを開始する
  • 初めての起動でない場合、チュートリアルは実行せずにゲームを開始する
  • いずれの場合も、条件によってお知らせなどの別画面をゲーム開始前に表示する
  • アプリの起動回数は、都度ローカルストレージにアクセスするのを避けて、メモリ上にロード(キャッシュ)しておく

これを実現しようとしてデータフローの設計を誤ると、以下の図のようなことが起きてしまいます。

image10

メモリ上にデータロードする前に割込み判定を行ってしまっています。そのため割込みがない左(No)のルートは問題なく動作しますが、右(Yes)のフローでは「③データロード」が実行されず正しいアプリ起動回数が取得できません。

image03

クライアント側の設計次第ですが、この場合はデータロード(紫点線)が実行されていないと、上図のメモリ上のデータ内でプレイ情報がないという扱いになり、④のチェックでアプリ初回起動と判定してしまいます。

では、どうすればよかったのでしょうか?image07身も蓋もない言い方をすれば、ちゃんと考えていたか、という点が一番重要です。

バグはどれだけ頑張っても潰せないものはでてきてしまいますが、予め考えていたかどうかでその数は大幅に増減するでしょう。

その上で解決方法を考えてみます。

image13

今回の件での対策案の一つは、アプリの起動からデータロードまでの間に割り込み処理を入れないこと、です。

当たり前のように思うかもしれませんが、「②割込」からYesのルートを作る人と、Noのルートを作る人が別の場合や実装タイミングに期間の空きがある場合、このミスが起こる可能性は大きくなります。

また、気をつけましょうという気持ちの問題と思えるかもしれませんが、アプリ起動時の処理の設計とその更新ルールがしっかりしていれば問題なく防げる範囲だと思います。

image09

もう一つの案は、プレイ情報を取得する際は、必ずデータロード済みかのチェックを行ってから取得するというものです。これも気をつけましょうという話ではなく、アクセスする口を統一したりしてアーキテクチャによって防ぐ、ということです。

後半の記事で少し触れますが、弊社リリース中のサービスである「グリモア~私立グリモワール魔法学園~ 」ではこの案1と案2の合わせ技のような形になっています。

社内の勉強会でも質問されたのですが、個人的には案2のように「誰が使っても誤りが発生しない」ような設計をオススメしています。

ルールが徹底されるのかに関しては、担当と役割が明確になっていれば自然と守られるようになります。(担当と役割については別の記事でちょっとだけ紹介してますので、興味のある方はそちらをご覧ください。

前半は以上で終了です。

後半の記事では、良いデータフロー(設計)を考える上でより具体的なアーキテクチャについての話をお届けします!


コメントを残す