なのログ

だから見てて下さい・・・俺の・・・変身

ポケモンと一緒にアプリの設計をかんがえました

自分もCleanArchitectureさんに入門をしました。
MVVMも名乗っていますが、Bindingをしているだけという説もあるとかないとか。


久しぶりに数日間芝生を連ねましたが充足感がすごい。


こんにちは。こんばんは。わたしです。
お久しぶりです。5月はサボってしまいました。

今月は四半期更新もあるはずなんですけどね。
このタイミングで更新パワーをつかってしまって大丈夫でしょうか。

まあ書きたかったので書きます。
というか書きたくなってたのは2週間前くらいからで、やっとこさ準備が出来たという図なので書くほかねえ状態です。オラッやってくぞ!!

前説

経緯

話は仕事から始まるんですが、開発対象アプリにリニューアルの予定がありまして、
それに際して内部のソフトウェアアーキテクチャも作り変えようぜという機運がございました。

この流れからいくらか勉強をしたので、
個人的にも1リポジトリ組んで、それについて喋ろうという回です。

というわけで組んだやつです。↓
記事公開時点で力尽きていてREADMEが空とかlayoutが必要最低限とかあるんですが、もうちょっとメンテを続ける予定です。

https://github.com/nredjawp/android-CleanArchitecture-MVVM-with-PokeAPIgithub.com

android-CleanArchitecture-MVVM-with-PokeAPIとかいう名前にしてしまっており、
「重くないか? その名前」と言われかねないものを感じております。

仕事サイドのチーム全体(サーバーを含む)にCleanArchitectureへの傾倒がありつつ、
その上でAndroid側にMVVMへの意識があったので、両方組み込むのがほとんど前提にありました。

ぶっちゃけ自分は着手の時点で「何すか 併用? 可能なモンなんすか? え?」くらいの感想だったんですが、
実際に例を学んで手元で学んでみたところ、それぞれ意味のある形に落とし込めたかなと思っています。

ちょっとでもイメージが湧けと思って、動かしているところも載せておきます。 リストをコロコロやってエラー表示を見ているだけのTwitterに投げてた動画なんですが。

参考・参照

設計については主にコレです。超有名なやつですね。

fernandocejas.com

着手時点ではほとんどトレースしていましたが、なんだかんだ色々手を加えています。
あと、春頃までに手を付けていた開発で蓄積したノウハウをいくらか詰め込んでいるので、
個別の部分の実装の参考になる箇所もあると思っています。混ぜてしまいました。

コンテンツについては、PokeAPIというポケモンの情報をメチャクチャ整えて持ってきてくれるフリーのAPIを利用して開発しました。
これまたぶっちゃけると、同APIを使ってiOSアプリで似たことをしているリポジトリがあり、そこから「オッじゃあ自分もAndroidで似たやつやるか〜」みたいになったのが採用のキッカケです。悪く言うとパクっています。

概要

設計全体の構成について

あんまり図とかを用意していないので、テキストで軽く書きます。

[Presentation layer]
    <View> : 
        Activity, Fragment(&layout.xml)が該当。  
        「onCreateが済んだらあとは勝手に動いた」くらいまで持ち物を減らしたい。
    <ViewModel> : 
        androidx.ViewModelが該当。値を持つ。xmlファイル経由(DataBinding)でVIEWと繋がる。
        BindingAdapterを重用して<View>への記載を減らしたいので、実質的にはBindingAdapterの定義もここに含むかも。
        [Domain layer]とつながる部分。(<UseCase>を呼び出す)

[Domain layer]
    <UseCase> : 
        常に基底UseCaseクラス(後述)を利用して実装。
        1クラス1タスク。正直CleanArchitectureっぽいのはここだけなんじゃないかと思います。(暴言)
        だいたい<Repository>から値をもらうだけです。  
        前提としては、画面向けの値の操作とかもやります。
        中で何かをするというより、こいつの定義が[Presentation layer]へのインターフェースになるのが大事なんだと思います。
    <Repository Interface>
        CleanArchitecture自体にドメイン駆動設計さんの文脈があるはずなので、[Data layer]へもインターフェースを開示しておきます。自分はいきあたりばったりにやったんですけど(小声)(あとたぶんテストに便利なのであるとよい)

[Data Layer]
    <Repository> : 
        <Repository Interface>の実装です。
        リポジトリパターン自体はAndroidDevelopersのサンプルなどでも推奨されていますが、だいたいそれと同じです。
        値をネットワークから・ローカルファイルから・もしかしてシステムリソースから拾うのか、みたいな振り分けをします。キャッシュ処理もここに含みます。
    <Resource> : 
        <Repository>が使う道具たちです。アプリ的には最下層になります。抽象化ゼロ。

こんなかんじでこざいます。
「UI部分にDataBindingを使ったClean Architecture」とか「UseCase構造を考慮したLayered Architecture」とか言えるんですかねこれ。わからん。
実際のところ、自分個人の感想としては、言葉だけだとレイヤードアーキテクチャのほうがしっくりきています。重ね重ねわからんけど。

既存の手法のルールに沿っているのかどうかとかまるでわからんのですが、
それなりにキレイにまとまったんじゃねーの、というのが本人の感想です。

MVVMとかMVPとかMVCとか、今思えばフロント側に文字数を割いてるじゃないですか。(文字数?)
モバイルアプリでもフロントを含むわけですが、おそらくWebのフロントあたりと比べるとバックグラウンドのことは増えるんですよね。それに対応した細分化が必要という話なんじゃないかなと思います。

Androidx以降のAndroidあるあるとして、AACを使うことだけを意識しているとViewModelの仕事がどんどん増えてデブになるとか聞いたこともあるし身に覚えもあるので、それぞれの行き先がはっきりしてUseCaseにまとまるのは効果を感じました。

ディレクトリ構成(パッケージ戦略)

いわゆるpackage by layer(レイヤ別)かと思います。

参考先を含めた複数の箇所(なんなら調査当初の自分自身も)が「package by feature(機能別)がええで」としているのを見ていますが、
個人的にはレイヤ別にまとたほうが扱いやすく感じました。

機能別にしたいモチベーションは、このあたりのことだと思っています。

  • 開発は機能別に行われる。その時の変更対象の分布範囲が狭くなる。
  • 新規開発者は画面・機能からソースを分割して読み始める。その時に読み取りやすくなる。

一方でレイヤ別にしたいモチベーションです。

  • 部品の再利用がしやすくなる(Data層にある値Aを取得する処理を画面BでもCでも使いたい)
  • 各レイヤのパッケージ内に入れば、画面・機能別の分類がある(Presentationパッケージに入ってしまえば画面基準でソースを探すことに全く困らない)
  • そもそもこの設計の思想として、レイヤ別の独立性が大切なのでは
  • データ側がアプリに対して専用な前提でなければ、そもそもPresentation層での機能とData層での機能が同期しない

先述の通り、自分が「レイヤードアーキテクチャと言ったほうがしっくりくる」という状態なのが大きく影響している可能性がありますが、こんな考えです。
ちなみに、想定している開発チーム体制はレイヤ別にも機能別にも分かれていない少人数の構成です。むしろ1人。

中身

要所1・UseCase基底クラス

参考先に加えてRunCatchingで処理するようにしました。
RunCatchingがKotlin1.3からの実装なので、おそらく参考先も今実装するならRunCatchingを使うんじゃないかと思います。

それから、小手先ですがfun execute()operator fun invoke()とすることで、利用先でほんの少し利用しやすくなっています。UseCaseの命名(getとかpostとかsetとかつけるやつ)との相性も良いと思います。

参考と共通する部分も書いておくと、そもそもこれはパラメータとコールバックを渡してバックグラウンドで特定の処理をさせるクラスを定義するためのものです。
UIの外の処理を常にバックグラウンドに移す効果と、パラメータを1クラスにまとめる効果が見込めます。 ぶっちゃけ初めて見た時はもう拍手が出ました。今となっては単純な中身に感じますが、こういう抽象化は本当にかっこいい・・・。

要所2・androidx.paging

なんか要所と言うには違うかもしれないんですが。
pagingさんを使う関係上、「常にUseCaseを経由して下位レイヤにアクセスする」という構成を侵略しています。
だいたいBoundaryCallbackのことです。名前からしてコールバックだよ。

もしかしたらうまいことやる方法があるかもしれないんですが、いまのところここだけコルーチンが独自に生えています。

他にネタになりそうな要素

レアかも順。のつもり。

  • Androidx.paging
    • pagingで出しているリストのエラーハンドリング(実はUseCaseを侵している)
    • RecyclerView.AdapterからPagedListAdapterへの移行(このへん
  • Androidx.room
    • 実質「多対多」のリレーションを貼ったりそれらのDAOを作ったり、今回は少し色々やりました。
  • Androidx.navigation
    • 変わった使い方はありません。遷移のアニメーションとか。
    • リスト表示の要素から遷移したい時に、親Fragmentからnavigationを渡すあたりは個人的常套手段。
  • Koin(DI)
    • 特に変わった使い方は無いと思います。しかも自分はScopeまわりの理解が足りませんでした。
  • Android DataBinding Library
    • <include>とあわせて使う
    • @BindingAdapterとあわせて使う

おわり

さて、長々と文字を書いたことに意味があったのかなかったのかわかりませんが、
初心者開発者の方にはそこそこ参考になるリポジトリを作れている気がしていて、
独りよがりですがぜひともコードを見てくれと思っています。

あと、実は今回の公開の段階で、多少のやり残しがあったりします。
(個体詳細の画面とかの)機能面もそうなんですが、多少手探りだったのもあって、特に(また)テストを後回しにしてしまいました・・・。
今回はそこそこ整えてコードを起こすことができたと思っているので、テストの後追い実装の練習にもしたいところです。

正直設計とかちゃんとやったことがなかったんですが、
まあまあのことが学べたんじゃないかなあと思っています。

そいではまた。次回。たぶん数日後。