minne Android アプリ開発基盤における改善

Image for post
Image for post
Photo by Daniel McCullough on Unsplash

この記事は「GMOペパボエンジニア Advent Calendar 2020」の 4 日目です。3 日目は、あんちぽさんの「ある社会人学生の生活と意見(2020年版) | 栗林健太郎」でした。

普段は minne Android アプリに関わるところでがしがし開発しているので、僕からは開発基盤における改善をお送りします。

  1. MVP から MVVM パターンへの移行

2. Rx{Java/Android} を LiveData と Kotlin Coroutines へ

3. GitHub Actions

もともと minne では創成期の無秩序の状態 (UI クラスにビジネス/プレゼンテーションロジックが記述されている状態) からしばらくして、 MVP (Model-View-Presenter) パターンが導入され、責務をきちんと定義してユニットテストを記述していくことで堅牢性を高めるという方針ができました。ただ、近年の Android Architecture Components の登場により、そのコンポーネントを取り入れて作っていくのが標準となりつつあります。

後述しますが、minne では Presenter から UI へデータを反映するために Rx のストリームを利用しており、画面のライフサイクルに対応して処理をキャンセルするようにストリームへ作用させる処理機構を独自でメンテナンスしている、という事情もありました。そのため、ライフサイクルへのサポートがライブラリ側でされているコンポーネントを利用することでメンテナンスコストも減るだろうと見込み、ViewModel と LiveData の導入を行っています。

ViewModel のサンプル

minne では Web API として RESTful な構成を主に取っているので、1 画面を構成するのにあたり、複数の HTTP リクエストを行う場合があります。そのリクエスト毎に LiveData を用意すると UI 側での購読処理が複数に渡るため、複雑になることが予想できました。そのため、基本的に 1 UI が購読するのは 1 LiveData にしています。今後 GraphQL も拡大していく予定で、その際にも特に問題なく 1 LiveData で購読処理を行えると考えます。

複数リクエスト時にデータを束ねる実装は DroidKaigi/conference-app-2020 を参考にさせていただいてます。

minne では RxJava と RxAndroid にお世話になっていて、役割は以下です。

  • スレッド切り替え (UI/IO)
  • 主に HTTP リクエストのレスポンスハンドリングにおけるストリーム処理 (Observable/Single)

ViewModel を利用するときに、UI へのデータのバインドにおいて Rx のストリームをそのまま利用するか、LiveData を取り入れるか悩みました。先述した画面のライフサイクルの管理を始めとするメンテナンスコストや、Rx の機能をそこまで使い切れていないことを理由に、MVVM 構成にするにあたり、Kotlin Coroutines と LiveData を導入することでそれぞれの責務を担ってもらうようにしました。

Rx 自体は特に悪くなく、様々な言語実装を通じて今でも開発が活発にされており、個人的にも好きです。ですが、適切な責務が分かれているとバージョンアップにおける影響範囲も狭くなるため追従しやすいというのと、Rx 職人もしくはサーバーサイドや他事業部でも幅広く Rx が使われているという状況があれば後押しになったかなとふりかえります。

コードや文書の管理で用いている GitHub Enterprise (Server) でも GitHub Actions が利用できるようになったため、セルフホストマシン上で様々なワークフローを動かしています。minne の Android アプリを構成するプロジェクトでも同様に、日々様々なワークフローにお世話になっています。

セルフホストマシンによるランナー利用だと以下の方法が (少なくとも) あるでしょう。

  • マシン上でビルドできるように依存関係をインストールした上でワークフローを動かす
  • マシン上でワークフロー用のコンテナを用意し、コンテナ上でワークフローを動かす

minne では専用の Docker コンテナを用意して、その中でワークフローを動かすようにしています。

マシン上で apt-getなどで依存パッケージをセットアップするようにしてもいいんですが、そのマシン上で複数のワークフローが動くような環境である場合、その他のプロセスによって何かパッケージのアップデートやら何かが起こった場合に全体に影響が及びます。想定している環境で動くのを担保したかったので、Docker コンテナ上でワークフローを動かすようにしています。その元となるコンテナはざっと以下のようなものです。

マルチステージビルドを利用して、キャッシュ生成用コンテナを作り、CI で利用するコンテナではそのキャッシュが使える状態にしています。

./gradlew build{variantName}./gradlew test{variantName}タスクを空回しし、CI 用で使うコンテナにプロジェクトを構成する依存パッケージの gradle キャッシュだけコピーして持ってきています。単純に依存パッケージのダウンロード分稼げるので、3 分程ワークフローにかかる時間が短縮されます。

というのも、単純に actions/cache は GitHub Enterprise ではまだ使えない認識なので何かしらのキャッシュ機構を考える必要があります。どこか S3 等に置くならブランチごとに運用したいからハッシュで管理したり、キャッシュダウンロード時のネットワーク利用 (圧縮すればそんなに気にならないとは思います) など考えることが多かったため、Docker image に入れ込み定期更新する、という選択をしています。

github.com でホストされているならば、素直に actions/setup-javaactions/cache を使うのが無難でしょう。

上記のコンテナ上で動く Actions のワークフロー例 (.github/workflow/test.yml)

昨年までの様々な自動化の導入やマルチモジュール導入などに続いて、minne の Android アプリの開発基盤が 2020 年にどう変化していたのかを紹介しました。来年もまとまった情報をお届けできるように頑張っていきたいと思います。

Club kids never die

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store