CA.apk #7
AWAのフルリニューアルを支えたアーキテクチャ2019/03/26 CA.apk #7Keita Kagurazaka
View Slide
AWAについて● 定額制音楽ストリーミングサービス● 競合にはSpotify, Apple Music, YouTubeMusic, LINE MUSICなどなど● Androidアプリは2019年1月24日にフルリニューアルをリリース
なぜリニューアルしたのか● リリースしてから3年以上にわたって機能を付け足し続けた結果、複雑でユーザの理解が難しいプロダクトに○ アプリを初めて使ったユーザが何をすればいいかわからない● 開発速度を優先した結果、メンテナンスが困難なソフトウェアに○ Fat Activity、上げられないライブラリのバージョン、作成時代によって違う設計 etc.
リニューアルの目的● すべての機能を再整理し、新規ユーザでもわかりやすいUIに変更、UXを最大化する● これまで溜め込んだ技術的負債を返済し、今後の開発速度を向上させる
リアーキテクチャ
そもそもアーキテクチャってなんのためにあるの?
アーキテクチャは何のためにあるか● すべてのアーキテクチャは本質的には制約● 開発者ができることを減らすことで、誤りを防ぐ● 間違えないなら制約は緩くてよい
アーキテクチャは何のためにあるか● すべてのアーキテクチャは本質的には制約● 開発者ができることを減らすことで、誤りを防ぐ● 間違えないなら制約は緩くてよいどのくらい自由度を犠牲にすべきかはチームとプロダクトに強く依存する
チームとアーキテクチャ (1)● チームの人数が多ければ多いほど、自由度は制限したほうが良い○ コミュニケーションコストは人数に対して指数関数的に増加する○ 小チームに分けるという選択肢は有力○ 採用も見据えて考える
チームとアーキテクチャ (2)● 技術習得度が高くないメンバーが含まれる場合、自由度は制限したほうが良い○ 設計方針を事前レビューしてから実装してもらうくらいなら最初から枠組みがあったほうが良い○ 技術習得度が高いメンバーとペア / モブプログラミングをする場合は自由度を高くできる
プロダクトとアーキテクチャ● プロダクトが巨大で複雑な場合、自由度は制限したほうが良い○ 人数の話と基本的には同じ○ 機能で分割するのは有力 (Androidならばmulti-module)○ ドメインの複雑さは向き合うしかない
AWAのAndroidチームとアーキテクチャ● チームは4人と小規模● 技術習得度は全員高く、必要な議論を躊躇わないタイプ● プロダクトは巨大で複雑 (167画面とかある)● 拠点が渋谷と福岡で分かれている
設計方針● レイヤードアーキテクチャを採用し、各処理をどの階層に置くかまで合意する● ルール化したほうが良さそうならば都度議論する● 重要度が高く、複雑な音楽再生とダウンロードについてはより制約をきつくする● 必要なら福岡にいく
設計方針● レイヤードアーキテクチャを採用し、各処理をどの階層に置くかまで合意する● ルール化したほうが良さそうならば都度議論する● 重要度が高く、複雑な音楽再生とダウンロードについてはより制約をきつくする● 必要なら福岡にいく ビデオ通話を常時接続する
方針は定まった
実現したいこと● ユーザに対してローディング表示をなるべく見せないようにするため、キャッシュを最大限に活用する● 音楽が再生できる画面では再生状況をリアクティブに表示したい
実現したいこと● ユーザに対してローディング表示をなるべく見せないようにするため、キャッシュを最大限に活用する● 音楽が再生できる画面では再生状況をリアクティブに表示したいCQRSアーキテクチャを採用
CQRS (コマンドクエリ責務分離)● システム全体を更新系と参照系に分離する● 更新系の操作は値を返さない● 参照系はシステムを更新しない (副作用なし)● 詳しくはこちら https://speakerdeck.com/kkagurazaka/cqrs-architecture-on-android
更新系View ViewModel UseCaseApiClientDBDataCommandApiClientRepositoryDataCommandワーカースレッドで実行
更新系View ViewModel UseCaseApiClientDBDataCommandApiClientRepositoryDataCommand● Viewからのイベントを受けてUseCaseをキックする
更新系View ViewModel UseCaseApiClientDBDataCommandApiClientRepositoryDataCommand● DataCommandをオーケストレーションして処理を行う● 戻り値はCompletable
更新系View ViewModel UseCaseApiClientDBDataCommandApiClientRepositoryDataCommand● APIからデータを取得してDBに書き込む● 扱うEntityごとにクラスが分かれる● 戻り値はCompletable
更新系View ViewModel UseCaseApiClientDBDataCommandApiClientRepositoryDataCommand● Entityを保存するだけ● トランザクションを管理する● 戻り値はUnit
更新系View ViewModel UseCaseApiClientDBDataCommandApiClientRepositoryDataCommand● 消えてもいいキャッシュはRealm● 重要なデータはSQLite (Room)● 設定系はSharedPreferences● プロセスを跨がせないデータはon-memory
参照系View ViewModel UseCase DBDataQueryRepositoryDataQueryRealmのためにUIスレッドで実行Repository
参照系View ViewModel UseCase DBDataQueryRepositoryDataQueryRepository● DBの変更を検知してRxJavaのストリームに変換する● 戻り値はFlowable● Realmの場合はRealmResults
参照系View ViewModel UseCase DBDataQueryRepositoryDataQueryRepository● 必要があればEntityを分解した値にしたり、distinctUntilChangedしたり● ごく一部のキャッシュしない参照のためにAPIを叩くこともある
参照系View ViewModel UseCase DBDataQueryRepositoryDataQueryRepository● DataQueryをオーケストレーションして、Viewに必要な情報を作る
参照系View ViewModel UseCase DBDataQueryRepositoryDataQueryRepository● UseCaseをobserveしてObservableFieldに詰め、DataBindingでViewをリアクティブに更新する
実現したいこと (再掲)● ユーザに対してローディング表示をなるべく見せないようにするため、キャッシュを最大限に活用する● 音楽が再生できる画面では再生状況をリアクティブに表示したい
実現したいこと (再掲)● ユーザに対してローディング表示をなるべく見せないようにするため、キャッシュを最大限に活用する● 音楽が再生できる画面では再生状況をリアクティブに表示したいキャッシュを書いてから画面に表示する作りバックグラウンドでデータが書き換わると画面もリアクティブに変化する
制限を緩めたところ● 本来CQRSでは更新系と参照系でEntityのクラスを分けるが、重要度が高い再生キューとダウンロード以外は分けなかった○ 参照系で更新操作をしないようにしよう、で事足りた● 細かいコーディング規約は定めず、コミット前にコードフォーマットすることだけにした○ たとえばapply派とalso派が混在しているが別に問題はなかった
実際やってみてどうだったか
リアーキテクチャしてみて● どこに何を書いたら良いか迷わなくなった● 責務が分かれたため、テストが書きやすくなった● データフローがわかりやすくなったことによってメンテナンス性が向上した● パフォーマンスには注意
まとめ● AndroidアプリをCQRSアーキテクチャでフルリニューアルした● 不具合をほとんど出さずにすばやく開発できるようになった● ベストなアーキテクチャはチームとプロダクトによって違う
Thanks!