こんにちは。日本版あすけんのエンジニアリング・マネージャの藤原です。
僕はasken入社当時はAndroidエンジニアでした。
アーキテクチャ改善にも携わってきましたが、今回は、その変容の歴史を振り返ってみようと思います。
- 意図
- 過去の偉人たちへの尊敬
- 補足:アーキテクチャの図について
- アーキテクチャの歴史
- お知らせ
意図
僕はここ1−2年かけて、エンジニアリングをする比重を徐々に減らし、最近では全くプログラミングはしない状態になっています。
いよいよ完全に手を離してから数ヶ月経ちますが、これまで社内Androidエンジニアのみなさまと協力して推し進めてきた技術的な背景等々の全てを、果たして社内に共有できたのかと懐疑的に思い始めました。
それは、後任の方々からすると意図不明の構造やモデルが残される事であり、苦労を残してしまうということになります。
そういった事態を避けるべく、これまでの取り組みと意図を掃き出そうと思い立ちました。
(あわよくば、社外の方々への「あすけんエンジニアも色々やっているんだな」と温度感が伝われば良いなと思います)
過去の偉人たちへの尊敬
念の為ですが、何か特定のアーキテクチャを批判する意図はありませんし、結果として技術負債になってしまった取り組みを批判するものではありません。
その時々において、ビジネスの激流の渦中で出来うる限りの全力を尽くした結果、ユーザーの皆様の健康をサポートできる今日があります。
時が流れればビジネスも変容し、新しい技術も生まれ、当時の努力は自然と負債へと変わっていきます。
負債が生まれるのは、みんな頑張ったし、みんな凄いという証明だと思っています。
補足:アーキテクチャの図について
以下の本文には「日本版あすけん」と「米国版Asken Diet」が登場します。
説明を少しでも簡素にするために、どちらに、どのアーキテクチャがどの程度、適用されているかは明言していません。
各Stepにおける図は、「その時々の、最新の方針」という前提でご覧ください。
また、図は mermaid で描いて(そのキャプチャを掲載して)います。
アーキテクチャの歴史
Step1: 日本版あすけんの立ち上げと、No architecture
あすけんのモバイルアプリの立ち上げは協力会社様とタッグを組みiOSが先行しました。
その後、Androidへの展開の際、最初のエンジニアが採用されました。
彼曰く、「Androidアプリは未経験だったので、開発勉強本と先行するiOSアプリを見ながら、最初のローンチまで5ヶ月で開発した」のだそうです(凄すぎる)。
その後、当時は「何がユーザーにとって価値なのか?」が全く分からず、手探り状態。
スタートアップのための仮説検証を、とにかく回し続ける日々。
作っては変え、作っては変えの日々だったと伺っています。
Step2: 米国版Asken Diet立ち上げと、MVP
日本版サービスが軌道に乗り始めた頃、更なる事業の拡大路線として、米国版Asken Dietが立ち上がりました。
立ち上げに際しては、(仕方ない状況でしたが)勢い任せで作り続けた日本版あすけん開発の反省から、とにかく勉強会に行っては情報仕入れ、実戦投入にあけくれた日々だったと伺っています。
その中で "AndroidアプリはMVPだ! ってみんな言っていた" そうなので 「そうなのかぁ、んじゃぁやってみるか!」 という事で取り込んでいったそうです。
著名なライブラリを積極的に導入(Retrofit, Butterknife、RxJavaなど)するなど技術革新も劇的に進みましたが
一方で、ボイラープレートも大量発生し、数年後に除去が大変になってしまいました。(みなさんごめんなさい by 当時の実装者)
ただ、アプリの安定化はライブラリ未使用の日本版あすけんより向上する事ができました。
Step3: 新メンバージョイン、課題の残るMulti-Module & Layered architecture
前任者が組織拡大のためマネジメントへ移行したタイミングで、新メンバー(というか僕)が、Androidエンジニアとしてジョインしました。
入社後の最初の担当は米国版Asken Dietでしたが、スクラムチームに入って第一に思ったのは開発の柔軟性の高さでした。
僕自身、ガチガチのSIer請負開発プレイングマネージャをしていての転職だったので、適応的なスクラムプロセスと"まずは動くプロダクトを!" という価値観に追随するには、自身の開発スキルの圧倒的な不足を突きつけられました。
そこからは、改めてAndroid開発1本に向き合う日々で、技術カンファレンスにも積極的に参加しました。
そこで出会ったのがMulti-Module構成です。
当時の自分は「ビルド時間が短くなる!」という点に惹かれ、社内にやや強引に説得をし、結局、1ヶ月くらいでリリースに至ったと思います。 (すみませんでした。。)
その後、ビルド時間を計測しましたが当時の開発Macで「-51s(79s→28s)」と、目的としていたビルド時間短縮に成功しました。
しかし、無理やりレイヤー化しただけなので、
- 依存関係の逆転の状態になっていない箇所が沢山残っていた
- 一見、整頓されている風だがレイヤー内は無理な移行で余計に複雑度が増した
- DDDの文脈のドメイン層も、単にデータクラスを入れているだけ
など課題もあり、本当の意味で責務の分離/構造の秩序が実現できたとはいえない状態でした。
とはいえ、今後につながる一歩になりました。
Step4: 新メンバーのジョインを控え、焦って間違ってしまったMVVM
その後、暫くは米国版Asken Dietの技術改善にひたすら打ち込む日々が続きました。
- 前述のアーキテクチャの進化の続き
- 新技術の導入
- Kotlin, Coroutine, Jetpack, AndroidX, DataBinding, Google Play Billing Library, Dagger2, Mockito, ... etc
そんなある日、新しくAndroidエンジニアがジョインすることが決まりました。
目下、技術改善に燃えていた僕は、「新しい方が来る前に、絶対により良いコードにするんだ」と完全に手段と目的が逆転していました。
そんな中、アーキテクチャの恐ろしい間違いをしてしまいました。
当時、ViewのinterfaceのimplementによるボイラープレートとCallback地獄(前任の方、すみません汗)によりPresenterに課題を感じていた僕はViewModelに目をつけていました。これならば、もっとスマートにコードが書ける、と。
ただ、その時にトライした手段が、なんとViewModelにViewの参照を持たせ、操作をするというアンチパターンでした。。
しかもViewModelはAndroid SDKが提供するViewModelを継承せず、独自で作成するという恐ろしい状況に。
完全に間違っていることに気づかず、良かれと思ってゴリゴリにリファクタリングを進めた結果
そのソースコードは、そのまま技術負債になってしまいました...。
技術負債(ごめんなさい by 藤原)
- ViewModelを独自実装
- 抽象度が高すぎる、かつ、責務を詰め込みすぎた親ViewModel
- 内部でView操作をする事により、ロジックとViewの密結合
Step5: 社員Androidエンジニアが2名体制に、改善されたMVVM
これを書いている今も申し訳ない気持ちで一杯なのですが、新メンバーがジョインしてすぐ、正しいMVVM+LiveDataの作法に切り替わりました。
一気に技術導入をしまくったのもあり、暫くは安定した開発が続きました。
(いや、後任の方は新旧入り乱れたアーキテクチャに翻弄され、大変に苦労されたと思います。。)
そんな中、僕は日本版あすけん担当に異動が決まりました。
前述の通り、日本版あすけんは「No architecture+Full Java」の状態でした。
通信周りの品質も不安定で老朽化が進んでいたというのもあり、まず着手したのはマルチモジュール+レイヤードアーキテクチャの導入でした。
米国版Asken Dietでの試行錯誤の結果もあり、導入自体はさほど苦にはなりませんでした。
ただ、Retrofit初導入だったので、既存API仕様を網羅し切れずにリリース直後に不具合が発生してしまうなど、苦い経験は色々としました。。
(ちなみに、この苦い経験の中で、通信周りは徹底的にテストコードを記述し、その後は安定しました)
後任の方へ大きな負債を残してしまった事を後悔しつつ、日本版あすけんの進化へと挑戦を始めました。
Step6: 社員Androidエンジニア3名、腐敗防止層とRepository
日本版あすけんの開発では、パートナー様と密に連携しながら新規入会の画面フローを刷新したり
筋肉ムキムキのサーバーエンジニアが入社して沸き立つ中、あす筋ボディメイクコースをリリースしたりと
大掛かりな開発が続いていました。この頃は、社内レビュー体制も強化されて以前より腰を据えて開発できるようになりました。
そんな中、3人目のAndroidエンジニアが入社しました。
入社後の既存コードやアーキの説明会をしている中で彼は「これ、suspendのIFにしないんですか?」と率直な疑問を呈していただきました。
当時、いまいちsuspendが分かって居なかった僕は、改めて勉強し直しました。
(android/architecture-samplesには、相当お世話になりました。。)
新メンバーの新しい観点の元、Androidエンジニアは勿論、iOSエンジニア、パートナー様ともレビューしながら、最終的に以下の進化を遂げました。
- KTXの導入
- viewModelScope内で実行するsuspend interfaceを持ったRepositoryが完成
- サーバーAPIからJsonをパースする際に、直接ドメインモデルへ変換せずに腐敗防止層を経由する構造を採用
- Fat ViewModel対策として、UiModelを採用
- ドメインモデルが拡充された
- UiModel / ドメインモデルのUnit testが拡充された
- 一部のViewModelにUnit testが実装された
- LocalDataSourceとしてRoomを導入、抽象化に成功しinfra層に完全に閉じ込めた
- 公式アーキテクチャに近づけることで、キャッチアップの負荷や属人化を軽減
この構成は非常に開発がしやすく、劇的に生産性が高まったと感じます。
実は炎上が必至だった開発プロジェクトが1回だけありましたが、この構成にしたことによって
利用品質や内部品質を落とさず、期日通りのリリースができたなど大きな効果を発揮しました。
改善の補足
ポイント1:suspend IF Repositoryで通信コールバック地獄から解放された
この構成になる前は、通信はCallbackで受け取っていました。
実は、Home画面を起動する時などは多くのAPIを実行しており、かつ、状態に応じて呼び出す順序が変わったりと「コールバック地獄」に陥っていました。
現在、完全に解消された訳ではないのですが、suspendでコールバックから解放されたことで、随分と可読性と変更容易性が高まりました。
ポイント2:UiModelにUI状態を分離して変更容易でテスタブルになった
これまでは、複雑なUIだとViewModelがFatになる傾向がありました。
原因は「ビジネスルールや、UIの状態変更のロジックをViewModelにベタ書き」でしたので、対策としてドメインモデルにビジネスルールをカプセル化をする事で多少の効果はありました。
しかし、「Viewの状態」はドメインモデルに閉じ込めるわけにもいかず、困っていました。
そこで新たにUiModelを導入し、UIのパーツの状態をイミュータブルオブジェクトとして表現してLiveDataでActivity/Fragmentへ通知する様にしました。
結果、UIのテストコードもかける様になり、自己ドキュメント化により仕様も明確になりました。
テストコードのおかげで変更も容易になりました。
参考:github: DroidKaigi/conference-app-2020#viewmodel
Step?: アーキテクチャの明日を今日より健康にする為に
こうやって振り返ると、アーキテクチャの進化の歴史は、組織やプロダクトの成長の歴史と非常に重なるなと思いました。
人が増えれば観点も変わり、その多様性によってプロダクトもアーキテクチャも強くなる。
ユーザーの皆様が健康になるために、askenは全社一丸でプロダクトの持つ価値を高め続けますが、その土台を磨き続けるのがエンジニアリングの責務だと思います。
その時々において、何が正しいのか誰も正解がわからない中、自ら責任を持って一歩を踏み出し、そして失敗する。その苦い経験は学びとなり、その学びはチームメンバーを巡り、組織の学びとなります。
アーキテクチャの明日を今日より健康にし、ユーザーの皆様へ安心と信頼のサービスを提供し続けるために、これからも日々、精進してまいります。
リファレンス
- mermaid
- 社内でnotionにドキュメントを残す場合や、Githubでの説明の際によく使っています
- android/architecture-samples
- Androidエンジニア同士で議論する際のリファレンスとして当時活用しました。
- github: DroidKaigi/conference-app-2020#viewmodel
- UiModelの参考にしました。
- エリック・エヴァンスのドメイン駆動設計
- 社内の「DDD検討会」という学習会でリファレンスにしました。
- 個人的にも読みましたが、特に「第4部 戦略的設計」は、俯瞰の視野と視座を得る上で大変参考になりました。
- 現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法
- 社内のモデリング検討会の講師である増田さんの本です。
- この本単体でも役に立ちましたし、エヴァンス本が抽象度高いので、その具体としての見方もでき、大変参考になりました。
お知らせ
askenでは、一緒に働いてくれるエンジニアを募集しています!