08
2019

マスタデータ実装の考察

CATEGORYDB
ソシャゲのキャラやらアイテムやらみたいなマスタデータの実装についての考察。ググるとベテラン勢によるもっと立派な記事もたくさんあるが、とはいえ毎回悩まされるので、どういう実装パターンがあるとかメリット/デメリットとか書いて自分なりの考えとかをまとめてみる。

なお、タイトルに「実装」と書いているように、運用なども含めてどうあるべきか…みたいな話よりも、サーバーサイドにどうやって実装していくかみたいな観点がメイン。
それとソシャゲを念頭に置いているけど、別にマスタデータを使うのはソシャゲに限らないので、一応それも考慮しつつまとめる。
(ソシャゲみたいに性能要件厳しくない場合は、あまり悩む必要は無いと思うが。)

マスタデータの要件

まず最初に、マスタデータや機能に求められる内容や性質を、一般的な話からゲームに特化した話まで適当にリストアップしてみる。
  1. 開発者以外の人が入力できること
  2. 随時更新ができること
  3. データを無効にできること
  4. 頻繁に参照されても問題ないこと
  5. 時間により有効/無効を切り替えられること
  6. 入力値のバリデーションができること
  7. 非公開のものがユーザーに見えないこと
  8. 複数バージョンの使い分けができること
実装方法に差異はあれど、多くの場合こういう機能が必要になってくる気がする。少なくとも1~3は確定。4は無くてもいけるけど、マスタはいろんなとこで使われるので出来ないと辛い。5はソシャゲなら必須。6は無いと運用が面倒。7は一般ユーザーが使うなら考慮しときたい。8はスマホアプリで審査とかある場合は欲しい。

以下、これを念頭に、どうやって実装するのが良いんだろう?っていうのを考察していく。

マスタの入力方法

これはまず「Excelとかで作成する」か「管理画面で入力する」かの2パターンがあると思う。で、前者の場合は「Excel」「Googleスプレッドシート」とかのソフトの選択肢があって、次いでそのデータをどうやってアプリに取り込むのか、という話に続いていく。それぞれ比較してみる。

Excel等で入力管理画面で入力
比較
  • 〇 表形式で分かりやすい。
  • 〇 作業者が使い慣れている。
  • 〇 一括登録が楽。
  • × 想定外のデータを入力されたり、シートが変更されることがある。
  • × JSONなど複雑なデータの入力は難しい。
  • 〇 バリデーションなどをかけやすい。
  • 〇 (対応すれば)どんな形式のデータも扱える。
  • × 開発コストがかかる。
  • × (画面構成によっては)分かり辛い。

自分の経験だと、管理画面での入力は、一昔前の業務系アプリやCMSでよく見た気がする。逆にゲーム系ではExcelしか見たことない。
そんな開発コストかけてらんないし、みんなExcel好きだし、まあ今はExcelだよね…。次に表計算ソフトの比較。

ExcelGoogleスプレッドシート
比較
  • 〇 みんな持ってる。
  • 〇 みんな慣れてる。
  • × 実は高い。
  • × バージョン管理や編集競合対策は別途頑張らないといけない。
  • 〇 みんなで編集できる。
  • △ みんな慣れてない。

これは、ExcelとGoogleスプレッドシートの比較というより、ローカルのアプリとクラウドの比較だと思う。個人的には別にどっちでもいいと思うけど、Excelの方が慣れてるという理由でExcelが選ばれる印象。

マスタの格納先

次いでマスタの格納先。入力されたマスタはインポートバッチやらを経て、DBやNoSQL、またはローカルファイルなんかに格納されて用いられる。

DBNoSQLファイル
比較
  • 〇 みんな知ってる。
  • 〇 フレームワーク等のサポートがある。
  • 〇 SQLでJOINしたりできる。
  • × 性能が不足しがち。
  • 〇 (種類によるが)性能の融通が利く。
  • × フレームワーク等のサポートが無い。
  • × SQLとの連携はできない。
  • 〇 性能を上げやすい。
  • 〇 実装が単純。
  • × フレームワーク等のサポートが無い。
  • × SQLとの連携はできない。
  • × 更新が面倒。

デフォルトがDBで、性能が必要な場合そこにキャッシュを足したりして、それでも足りない場合NoSQLを使ってみたり、ファイルにしてみたりと工夫されるイメージ。キャッシュがNoSQLやファイルな事も多々ある。
NoSQLと言っても色々種類があるし、またファイルの方もJSONにするとこもあれば、例えばPHP配列とかのソースコードに変換しちゃうケースもあったり千差万別。

個人的にはDB+キャッシュを進めたい。DBだとマスタ以外のデータと仕組みが統一できたりと扱いやすい。キャッシュもライブラリ等で対応できる事も多いし。
NoSQLやローカルファイルだと、多くの場合、自前の劣化DBライブラリもどきが出来て自分の世界を作り出すので、何かと苦労する羽目になる。

ただ、NoSQLやローカルファイルを使う、という決断に至る場合は、DB+キャッシュでも性能が足りないとか、NoSQLへの通信すらボトルネックになっちゃうとか、予算的にできる限り性能を出す仕組みが欲しいとか、そういう理由がある筈なので、1開発者ではどうしようもないケースも。その場合は、せめてDBライブラリとI/Fを合わせるとか、気を使いたい。
(しかし、今までそんな気を使った共通関数にお目にかかったことは無いorz)

ちなみに個人的には、ローカルファイルに置かざる得ないにしても、オンメモリのSQLiteとか使えば別にDB使えるんじゃね?とも思っているのだが、今のところ実例にお目にかかったことは無い。何か気付いてない問題がある?できればそのうち試してみたい。

マスタデータはクラス定義する?

次にマスタデータをプログラム中で扱う場合に、各マスタごとにモデルやエンティティを作るのか、それともMapや連想配列みたいなデータ構造として扱うかについて。
…なんだけど、これについては議論の余地はないと思う。ちゃんとモデルやエンティティ定義しましょう。

Map/連想配列クラス
比較
  • 〇 クラスを作る必要が無い。
  • × マスタの変化をクラス内で吸収できない。
  • × マスタのプロパティがソースだけ見て分からない。
  • × 文字列での参照だらけの分かり辛いコードになる。
  • 〇 型安全。
  • 〇 マスタに対して何らかの処理を入れられる。
  • × クラスを作る必要がある。

Mapや連想配列として扱うメリットは、クラス作るのが面倒くさい、意外に特にない。
新しいマスタやパラメータが増えたときにクラス直さなくてもよい、といったところで、結局そのマスタを使うにはソースを直さなければいけないので、だったら普通にクラスを定義するのが正攻法。
数が多くて面倒でも、後々を考えてちゃんと定義しましょう。

ただし、全部のクラスをバカ正直に定義する必要があるかというと、必ずしもそうでもない。例えば、同じデータ構造のマスタなら抽象クラス作って共通化するとか、そういう手抜きはできる。
また、後述のように自分ではマスタを使わないけどクライアントに返すために持っている、みたいな場合も別にいちいち定義する必要はない。
そういうのまで全部定義するとさすがに大変なので、そこは融通を効かせましょう。

ソースとExcelどちらが主?

次はExcel等でマスタ入力する場合の、ソースコードとExcelシートの関係のついて。
モデルやエンティティを定義するとなった場合、型なども当然ソース側に定義することになるが、Excel側でバリデーションしたい!という要望が出たりして、しばしば問題になる。

これに対する解決策としては、以下の3パターンが浮かぶ。
  1. プログラム側にのみ定義する。Excel側では最小限しかチェックしない。
  2. プログラムとExcel両方に定義する。
  3. Excel側にのみ定義する。プログラムのソースコードはExcelから自動生成する。
Excel側でいろいろやりたいならb,cという話になり、SIer/ソシャゲ問わず一番よく見かけたのがcなんだけど、個人的にはおススメしない。理由はcは「非開発者によりソースが破壊される」「自動生成によりモデルに手を入れられなくなる」から。

非開発者は予期せぬ操作をして割と頻繁にソースを破壊し、そのたびに開発者が時間を取られるとどんどんモチベが下がる。
また、モデルやエンティティはちゃんと活用している場合、それなりにメソッドが生えて来たり、ベースクラスを作ったりと成長するものなんだけど、自動生成にされるとそれがほぼ不可能になる。結果として、本来モデルやエンティティにあるべきメソッドが別のクラスに作られたり、自動生成を隠蔽するための余計なレイヤーが作られたり、または「このクラスは自動生成で上書きしないでください」となって破綻してしまう。
「マスタなんて全部同じ構成だから自動生成でOK」ってのは机上では良いアイデアに思えるんだけど、実際に開発が進むと全然そんなことはない。

自分としてはaで、そもそもExcel上でチェックしたいという要望を諦めてもらって、別途CIでプログラム側でバリデーション等を行うことで対応するのがおススメ。Excel側で複雑なことやると保守も面倒なので、そこは割り切りたい。

IDはint?string?

次はマスタデータのIDの型について。
といっても、intの方が勿論効率は良いけど、現代ではあんま拘る話でも無さそう。
分かりやすさ重視でstringとか好きに決めてよいと思う。
(64文字越えのIDが入ってた時にはさすがにちょっと待てとツッコんだが。16文字辺りなら常識的?)

なお、intにしようがstringにしようが、基本的にマスタIDにはなんかルールが(開発者の知らないところで)決められると思った方がよい。イベント用のアイテムは10000番台とか、EV_ITM_みたいな接頭辞が付けられるとか。
たまに「IDで区別できるからそれで区別して」って言われるけど、従ってはいけない。IDはあくまでIDなので、運用上たまたま区別できるようになっているからと言って、それを曲げてはいけない。区別したいときは別途typeだのcategoryだのclassだのの項目を作りましょう。

複数バージョンの取り扱い

スマホアプリ特有(?)の面倒くさい話。スマホアプリだと審査用のバージョンだけ新マスタにして、それ以外は旧バージョン使わせたい、とかいう要件が来ることがある。
(やり方はいろいろあるから、必ずしも新旧バージョンの保持が必要なわけじゃないけど。)

複数バージョン保持する場合、何にも考えずに実装すると、参照側で毎回明示的に取得バージョンを指定するメソッドが出来上がる。
が、勿論これでは実用に堪えないので、何かしらORMなり共通関数なりを拡張して、参照時に透過的に欲しいバージョンを取ってくる仕組みが必要になる。

また、DBやNoSQLなんかの格納先も、バージョンごとに別テーブルに分けたり、あるいはバージョン列を追加したり…とややこしいことになる。

何度か試みたことはあるが、例えばORMによっても、動的に別テーブルに変えるのはとても厳しかったり、あるいは逆に簡単に出来たりと差異があったので、あまり決定打は見いだせていない。ただ、こういう要件があるかも…ってことは念頭に置いといた方が良いと思う。

時限公開はどうやる?

これもソシャゲなんかでよくある奴。例えば、12:00からイベントが開始されて、新しいステージが公開されるとか。
これも何も考えずに実装すると、じゃあ12時になったら手動でマスタ更新してください、とかいう実用に堪えない運用になったりする。
(ただしソシャゲはともかく、それ以外のアプリでは更新頻度によっては必須ではない。手動でも足りる。)

対応方法はあんま選択肢無いけど、以下の2パターンを見たことがある。
  1. マスタデータに開始時間&終了時間を持たせる。
  2. 時間になったらマスタのバージョンを切り替える。
一番オーソドックスな対応はa。基本的にどのケースでもこれで足りると思う。ただ場合によっては、開始時間の前にユーザーにマスタが漏れてしまう危険性や、時限対象が多い場合に設定が面倒とかの欠点はある。

もう一つ見たことがあるのがb。これは、前述の複数バージョンと組み合わせて、時間になったらマスタ全体を切り替える。こっちは上記のような問題もなく、非常に有効だと思うが、仕組みが複雑そうだったので実装は大変そう。

またbをやるにしても、用途に応じてaも使うことは出来るので、これはマスタの種類ごとに使い分けるのがベストだと思う。

クライアントへの配信

これまではサーバー側での観点がメインだったわけだが、最後はクライアント側への配信について。
昨今のアプリだと、クライアント側でもマスタを見て処理を行うのが常識なので、両者は当然同じマスタを見ておく必要がある。
で、マスタは頻繁に更新されるから、クライアントのアップデートとは別に、配信する必要がある。

これも一応、以下の2パターンの方法が考えられる。
  1. サーバーが取り込んだマスタをクライアントに配信する。
  2. クライアント用のマスタは別途準備して、サーバーが配信する。
とはいえ、これはソシャゲでは基本的にbだと思う。理由は単純で、現在ではマスタだけじゃなくてAssetBundleとかも配信するから。マスタもそれとセットで扱われるので必然的にそうなる。
ただ、本当にマスタしか配信しないケースでは、新たにクライアント用の仕組みを作るの面倒だし、サーバーから配信しようっていうのも引き続きありだとは思う。
もっとも、最近のマスタにはクライアントしか使わない情報が非常に多いので、それを扱うの面倒だからやっぱ分けるってのもそれはそれでありかと。

なお、クライアントへの配信では、油断すると前述の時限公開のデータとかがユーザーに解析されると見えてしまう危険性があるので、そこだけ注意されたし。


以上、なんか結構長くなったけど、マスタデータ実装で考察したあれこれをまとめてみた。
マスタデータの使い勝手は、開発にも運用にも直結するので、作り始める前にいろいろ考えようm(__)m
スポンサーサイト



Tag: プログラミング

0 Comments

Leave a comment