設計ミスをふりかえる

良かれと思って進めていた設計が、とんでもなく悪かったのでメモ。

ここのところ取り組んでいる frasyr のモノリスサービス化について、ひどい設計ミスをしていた(下記は前回記事)。

懺悔

ちょうど contract test のしくみを導入したところだったので、その扱いにばかり注意が行ってしまっていたのも要因かもしれない。 そもそもペイロードの構造が完全に悪手だった。

前記事時点で設計したペイロードはこうだった:

example:
  data:
    caa_url: https://raw.githubusercontent.com/ichimomo/frasyr/dev/data-raw/ex2_caa.csv
    waa_url: https://raw.githubusercontent.com/ichimomo/frasyr/dev/data-raw/ex2_waa.csv
    maa_url: https://raw.githubusercontent.com/ichimomo/frasyr/dev/data-raw/ex2_maa.csv
  params:
    m: 0.5
    fc_year: [2015, 2016, 2017]
    tf_year: [2015, 2016]
    term_f: max
    stat_tf: mean
    pope: true
    tune: false
    p_init: 0.5

ここまでパラメータが多いと、API の境界における入力検証がいくらなんでも重すぎる。 frasyr の vpa() をラップしていたとしても、API は独立して設計できるはずだ。

本来あるべきペイロードは、こうではないか1:

example:
  dataSetVersion: <version>
  parameterSetVersion: <version>

計算に必要な入力一式を、あらかじめ登録されたリソースへの参照に畳んでおき、ペイロードはその参照だけを受け取る。 リソースの ID を参照してもいいが、資源評価の監査性・再現可能性を考えるとバージョンみたいなものを参照したほうがよい気がする。

気づいたこと

ペイロードがここまで内部処理と疎結合になると、contract test で叩くべき異常系がほとんど無いことに気づく。 まして前回やっていたような、ドメイン的な要件まで染み出してしまっていた contract test は、責務の整理ができていないことの表れだと思う。

ところで、dataSetVersionparameterSetVersion が手に入るまでには、当然ながら踏んでおくべきユースケースがいくつかあるはずだ。 caa や waa を登録し、それらを組み合わせてひとつの入力セットにする。 データの値を更新したら version が更新される。 これらは計算の再現性の確保には不可欠だが、計算サービスの責務ではない。

ということで、data service のような独立したサービスが必要に思われる。 現時点で考えているユースケースは例えば下記のような感じ:

リクエスト レスポンス 備考
データ要素(caa、waa、maa など)の登録 dataVersion エンドユーザーからみた更新処理も含む
複数の登録済みデータを組み合わせてデータセットを作る dataSetVersion
パラメータセットを登録する parameterSetVersion
パラメータセットのパラメータを更新する parameterSetVersion

データについては「データ要素」と呼べるようなものの単位で登録できてほしいが、一方パラメータセットについては「パラメータ」の単位で登録してもあまり意味がないような気がする。

ちなみに、ここで示したユースケースはあくまでも data service のクライアントから見たユースケースであって、資源評価アプリケーションのエンドユーザーの体験と一致するとは限らない。 実際、資源計算したいユーザーは、パラメータセットなるものを “登録” するように言われたら不満に思うだろう。 考えられるユーザーの体験の例としては、画面に各パラメータを入力して「VPA を実行」を押すと、ユーザーには見えない背後でパラメータセットが登録され、発行された parameterSetVersion を参照して VPA job がリクエストされる、といったものが考えられる。

もう一つ、最近 DOP(Data Oriented Programming)について知ったので、不変性の文脈でメモしておくと、エンドユーザーが登録済みデータの「更新」のつもりで実施した操作も、data service では新データの登録と新バージョン発行として扱いたい。 もちろん、エンドユーザーには更新処理に見せかけていい。

学んだこと

  • 内部的に利用している処理のシグネチャと、自分が設計する API は独立して設計してよい
  • ドメイン要件を API 契約に過結合させてはならない
  • API のユースケースとエンドユーザーの体験は必ずしも一致しない

  1. type: "vpa" のように、エンドポイントを分けずに計算タイプを受け取る設計もあり得る ↩︎