継続的なアーキテクチャ改善の理想と現実

梅森翔氏(以下、梅森):こんばんは。Androidエンジニアをやっています。今テックリードと紹介いただきましたが、テックリードみたいなこともやりつつ、Androidエンジニアをやっています。

今日はテックリードっぽい話はしないんですが、Androidの開発についての話をしたいと思います。タイトルとしては「継続的なアーキテクチャ改善の理想と現実」というタイトルで発表します。

自分がdelyに入って1年と2ヶ月経ちました。入社時はテレビCMをやって一気にアプリがグロースした時期でした。今年のDroidKaigiで登壇したりしています。

今日発表する内容としては、今のdelyのAndroidのアプリケーションがどんなアーキテクチャであるかということと、自分が入った頃はどんな感じだったか。そしてそれをどうやって改善したかという話をしたいと思います。

2018年7月現在のAndroidアプリケーションのアーキテクチャはこんな感じになっています。Activity、Fragmentがあって、Viewの層があって、あとで細かく説明はしますが、Applicationがあって、Data層、Infra層があって、4層のレイヤードアーキテクチャになっています。

サービスもあるのでServiceが左のほうにありますが、Serviceがあり、それらがData層、Infra層に紐付いているという実装をしています。現在は一般的なアーキテクチャなのかなと思っていますが、こんな感じでやっています。

理想はこんな感じです。現実としては若干アーキテクチャのルールを守っていないコードがあったりするのですが、こんな感じでやっています。

現状のアーキテクチャ

先ほどざっくり説明したのですが、4層のアーキテクチャでやっています。View層、Application層、Data層、Infra層、Service。+1というのは、Serviceは厳密にいうとActivityやFragmentのライフサイクルとは違うものなので分けています。

ViewはMVVMを採用しています。だいぶ前にAndroid ArchitectureのComponentsが出てきて、ViewModelというのが出てきたんですけど、みなさんこれはどうしていますか? けっこう名前に困ってます?

Android Data Bindingを活用して実装しています。ViewModelでイベントハンドリングとプロパティバインディングをやっているというスタンダードな感じですね。

Application層はViewModelのイベントを受け取って、Data層を操作し、そのデータをViewModelに返す。ここはまだ実装中、未完成なんですけど。

Data層があります。右に書いてあるのは、クラス名に末尾にこんなことをつけたりする、という決まりになっていたりします。ローカルの永続化周りの操作やWeb APIの呼び出し、メモリ上の一時的なデータの取り扱いなどを行っているような層ですね。

Infra層、その他すべて。今話したことに関わらないところは全部ここに押し込めるみたいな感じで今はやっています。DI周りだけはパッケージを分けていますが、それ以外は一緒くたになっている感じですね。

最後にServiceですが、これはいわゆるAndroidのServiceなので、Activityのバックグラウンドで起動したりするものです。直接Data層にアクセスしてて、ほぼApplication層のようなものだから、Data層にアクセスしてるみたいな感じでやっています。ここは実装しながら悩んでいる感じですね。

いいところとしては、それぞれの層の役割が明確になっています。最悪Infra層に閉じ込めておけばなんとかなるだろう、みたいな感じで実装できるのがいいところです。複雑な機能もRepositoryとApplicationにうまく分割すれば整理しやすいのがいいところです。

現状のよくないところとしては、大規模にパッケージをリファクタリングしたところでヒストリが途切れているところがあったりして、そこはよくなかったり。先ほど見せたとおり、完全に設計どおりのつくりにはなっていないので、そういうところはよくないですね。

まだ移行しきれていない実装もところどころ残っていたりします。でも、悪くはないレベルになってきています。

アーキテクチャを改善するきっかけ

自分が入った頃のアーキテクチャはこんな感じでした。登場人物がとても少なくてシンプルですね。入った時に「このままだとまずそうだな」と思いました。とくに設計思想もなく、いろいろな人の手を渡ってきた感じのするコードでした。

その頃、ユーザー数はグロースしていたんですが、開発チームはスケールしなそうだと思ったので、その当時もう1人いたAndroidエンジニアに協力してもらってアーキテクチャ改善に取り組むことにしました。

アーキテクチャといえば、みなさんご存じだと思いますが、コンウェイの法則というものがあります。「システムを設計する組織は、その構造をそっくり真似た構造の設計を生み出してしまう」。つまり、システムのアーキテクチャは組織構造に従うという法則ですね。

逆に言えば、悪いアーキテクチャは組織構造を悪くするし、良いアーキテクチャは組織構造を良くする。これは逆コンウェイの法則と言ったりします。

というわけで、サービスと一緒に成長するためにアーキテクチャを改善しました。

改善するためにやったこと

まずやってみたことです。とりあえずActivity、Fragmentがここで一緒くたになっていたので、Viewを分離したいと。幸いなことに当時Data Bindingを使っていたので、Butter Knifeと同じような使い方をしてましたが、MVVMを導入しました。

ちょうどその頃まったく新しい機能の実装を任されたので、サンプル的に画面をいくつか作ってもう1人のAndroid開発者に共有をし、小さく始めました。

たしか、6〜7月ぐらいに第1段階が完了したと思います。9割ぐらいの画面を、実際にView層を作ってMVVM化することができました。

次に、View・Infra層を整理しました。

同じ画面を構成しているViewModel、あるいはActivity、Fragment。この画面は動画の一覧の画面なんですが、実際にはその画面を構成しているコンポーネントはもっといろいろあります。ListだったのでAdapterがあったり。そういったものを一緒くたに同じパッケージ構造に置いたり。

そして、9割ぐらいのAndroid SDK API呼び出しをInfra層に切り離しました。

その結果、若干整ってきました。でも、まだ層としては少ないので、ViewModelに複雑な処理が残っているという感じです。

次にData層を切り離しました。APIへの通信やローカルのSharedPreferenceの書き込み、呼び出しなどを〇〇Repositoryに切り出しました。ローカルとリモートを切り分けて、そこでざっくり分けたような感じでやりました。

もともとUseCaseという名前でAPIの通信が切り離されていたので、それのリネームがメインでしたが、いろいろと整理しました。

そういうことをやった結果、View層からIO関係の処理が消えました。ただ、新しく作ったData層に今度は複雑な処理が集中してきました。

というわけで、Application層を切り離すことにしました。Data層で扱っている複雑めな処理をApplication層に移動。Serviceも、これとは別なんですけど、Data層を参照するように切り替えました。

グロースに合わせて価値を届けるために

12月ごろにほぼ現在に近いかたちになって、今もすこしずつ、構造はできたので改善しているという感じです。

ほかにもいろいろやったんですが。Dagger2のライフスコープを切り分けたり、イベントハンドリング用のイベントバス作って導入したり、RxJavaを導入したり、いろいろやりました。

1年ぐらいやってみた感じですね。これがコントリビューションの結果なんですが、25万行足して17万行減らしています。

感想としては、なかなかつらかったですね。最初はあまりいいコードではなかったですし、今でも完全によくできてはないし、最初からきれいだったらもちろんよかったと思います。

でも、サービスの成長段階によって必要となるアーキテクチャは違うと思います。価値がすばやく届けられるのが大事だし、グロースする前にオーバーキルのアーキテクチャはいらない。なので、人が増えていくタイミングやアプリが複雑化するタイミングで変えちゃえばいいじゃないかと思います。

それはけっこうつらいんですが、そのときに苦しめばいいし、今日説明したようにちょっとずつレイヤーを増やして移行することもできます。

というわけで、クラシルはこれからも成長していくので、サービスの成長と一緒に成長するプロダクト、そしてアーキテクチャを育てるAndroidエンジニアを募集しています。ありがとうございました。

(会場拍手)