お節介 MVP architecture をどうにかしている

Monday, April 29, 2019

original post: https://matakucom.medium.com

今担当している Android プロダクトでは紆余曲折あって Model-View-Presenter アーキテクチャを採用している。そこでは repository の層がなかったのもあり、model 側の実装を presenter 側及び UI 側 そして API のエラーハンドリングクラスが知りすぎているため、変更に追従する手間がある という問題があった。

元々は以下のような感じで、API client ではスレッド管理として Rx{Java,Android} を使っている。

アーキテクチャもなにもない状態で AsyncTask 利用かつユニットテストなし-> MVP (+ retrofit) でユニットテストあり に変えたような歴史を見ているし、その時々の事情もあるので別に悪いとは思わない。

この作りは view, presenter の層に retrofit ( = API client) の参照が漏れ出てしまっているので、retrofit をアップデートもしくはやめるなどした場合に変更箇所が多数出てしまうことになる。Presenter, 及び UI は表示に利用する要素に依存していればよいはずで、その要素をどうやってとかその要素をラップするものに関しては特に考える必要は必ずしもないはず。

その結果、以下のようにした。

  • UI が必要な要素もしくはエラーを格納する result クラスを作成し、成功と失敗の状態を扱えるようにする
  • データを取得する役割を担う repository を導入し、repository は UI が必要な要素を格納した result クラスを Rx (Single) のストリームで返す
  • Presenter は repository から来た result クラスをハンドリングするだけにして、どこからどうやって取ってくるかは repository にまかせる
  • エラー時は presenter 内でハンドリングせずに、来た throwable をそのままエラーハンドリング用クラスに投げる

必要な要素が入った Result クラスが返って来さえすれば presenter は機能するように、API client の変更に追従するべきところが、これでエラーハンドリングするクラスと repository に収まった。

API のエラーハンドリング

元々 API client のライブラリのレスポンスクラスを投げつけるようにしていたが、渡す UI 側がそれを意識してしまわないように、例外クラスのベースクラスを受け付けるように変更した。大体これかその子の例外クラスを継承した独自 exception クラスはライブラリにあると思ったので、ライブラリが変わっても Throwable を返す流れは変わらず、エラーハンドリングクラスを拡張していくイメージ。

Retrofit2 で通信時エラーであれば、 retrofit2.HttpException でレスポンスを包み Result.Failure(HttpException(response)) のようにして返せば、response を取り出してエラーボディをどうこうできる。

エラーハンドリングするクラスが膨らむ恐れがあるが、API のエラー形式が統一されていればそこまでパースする労力はないはず。

Result

kotlin.Result に関しては時期的に出る前に考えてたのと、この辺の問題があって一旦は独自 Result クラスを作成してそれを用いている。関数の返却値には今の所できないみたいな話もあるけど、こういったときに使う場合には Single や LiveData で包むような形になるだろうからそこは問題ない。元々少し違う形だったが、早く移行したいので大体似たような感じにしている。

Rx

API client で使っている、データを 1 回だけ流す Rx の Single によるストリームを、データの流れとしてそのまま採用した。実はこっそり1 回だけ値を流す LiveData を作ってそれに差し替えようとしたが、目的は別なので今回はやめた。LiveDataReactiveStreams のようなものもあるのでどうにでもなるだろうという展望を抱いている。

スレッド管理でも使っているけどその話であれば、新規なら言語レベルである程度サポートしてると言える Kotlin coroutines を採用するかなとも思うけど、技術選定はチームの方針もあるので。

おわりに

これはモジュール化の設計途中で不要な依存を剥がす工程の 1 つとして取り掛かったものだった。他は画面間のデータの受け渡しが複雑になっているところをリファクタリングしようとしたり、神ユーティリティクラスを壊したりしている。

MVP architecture でもまだ生きられないことはないなと思っているが、とはいえクリーンアーキテクチャ程ではなくてもアプリにあった要素を選びつつ、合ったものを継続的に選択していくのは体力が必要だと思った。

Android

minne とマルチモジュール

minne と Kotlin