ロコガイド テックブログ

「地域のくらしを、かしこく、たのしく」する、株式会社ロコガイドの社員がいろいろな記事を書いています。

「地域のくらしを、かしこく、たのしく」する、株式会社ロコガイドの社員がいろいろな記事を書いています。

トクバイiOSアプリのアーキテクチャ改善

トクバイのiOSアプリの開発を担当している松田です。
iOSアプリ開発では現在アーキテクチャの改善を行っており、それについてご紹介したいと思います。

なお、この内容はアーキテクチャの概念を説明するものではなく、私達がどのように取り組んでいるかのご紹介になります。
また、アーキテクチャの考えや記事は多数存在しますが、それらを参考にわかりやすさや既存のコードへの親和性、導入のしやすさなども加味しながらトクバイのiOSアプリの開発に組み込んでいます。

iOSアプリ開発の現状

トクバイのiOSアプリはアプリ開発のアーキテクチャとしてMVVMを採用しています。
ViewControllerはViewModelで管理しているデータや状態をViewへ反映する、またユーザーアクションなどViewで発火されたイベントをViewModelへ通知する処理を担っています。
したがってiOSアプリ開発でCocoaMVCを採用しているとよく問題とされる、ViewControllerの肥大化についてはそれほど問題とはなっていません。
(なお、実際は画面によっては複雑なレイアウト構築や画面遷移をViewControllerが一手に引き受けているためそれなりにボリュームはあります...こちらはまた次の課題だと感じています。)

一方で、その代わり、というのは正しくないですがよろしくないことにViewModelが肥大化しています。
その原因となっているのがビジネスロジックやデータアクセス処理の一部がViewModelに書かれてしまっているためです。

それにより、プレゼンテーションに関わらない機能を他の画面でも再利用したい場合(例えばトクバイでは店舗をフォローするという機能や端末の位置情報を取得するという機能があり、これらを複数の画面で利用したいケースがあります)に、ViewModelに実装されていることにより再利用できず、同じコードをコピペする、もしくは似たようなコードを再記述する、ということをしてしまっています。

結果として仕様変更によるビジネルロジックの改修やデータの取得先の変更といったデータアクセスの変更時に、改修範囲が広範囲にまたがるなどバグを生みやすい環境になってしまっており、確認コストの増加と生産性の低下といった問題が生じています。
またビジネスロジックのテストを書くためにはViewModelを経由する必要があるため、テストコードの実装コストも高くなり非常にテストが書きづらくもなっています。

新アーキテクチャ

これらの問題を解決するためにGUIアーキテクチャとしてはこれまでのMVVMをそのままに、システムアーキテクチャとしてレイヤードアーキテクチャをベースとして簡易的なクリーンアーキテクチャの概念を取り入れることを検討しました。
レイヤー構成は次のように定義してます。なお概念としては4層を軸としていますが、実際に具体化していく上でより細分化することは制限しません。

レイヤー構成

  • UIレイヤー
    • いわゆるView。Viewのレイアウトやレンダリング
    • ユーザからのイベントを受け付け、Presentationレイヤーにハンドリングを依頼する
    • (例)View及びViewController
  • Presentation(プレゼンテーション)レイヤー
    • ViewレイヤーからのイベントをハンドリングしてDomainレイヤーに処理を依頼する
    • Domainレイヤーの処理結果をViewに適した形に加工してViewに伝える
    • Viewの状態を管理する。
    • (例)ViewModel(Presenter)
  • Domain(ドメイン)レイヤー
    • ビジネスロジック(ドメインロジック)を処理する
    • ビジネス固有のデータ
    • (例)UseCase、Entity
  • Data(データ)レイヤー
    • 外部IFとのやりとり、データアクセスを処理する
    • APIサーバやDBなどのインフラ
    • リポジトリパターン
    • (例)Repository、APIクライアント、DBアクセスクライアント

MVVM+CleanArchitecture

対応内容

このレイヤー構成に倣うと、これまで課題となっていのはViewModelがプレゼンテーション、ドメイン、データレイヤーのほぼ全てを一手に引き受けていたことでした。
それを今回の改善方法ではViewModelはプレゼンテーションレイヤーとしての責務にのみ特化させ、ビジネスロジックやデータアクセス処理をModel(MVVMのM)として抽出し、ビジネスロジックをドメインレイヤーに、データアクセス処理をデータレイヤーに分離させました。
なお、ドメインレイヤーのビジネスロジックを担うオブジェクトはUseCase、データレイヤーのデータアクセスを担うオブジェクトはRepositoryという名前にし、データアクセスについては名前からも想像できるようにリポジトリパターンを取り入れました。

効果

この改善方法を導入したことで実際に次のような効果を得ることができました。

  • コードが簡潔

それが目的なので当たり前ですがViewModelがスッキリしてきました。
トクバイのiOSアプリは基本的にはWebAPIで取得した値を表示するアプリなので、ViewModelにはAPIで取得したデータがどういう状態で表示されているかを管理するための必要な処理だけになりました。
それによりコードの見通しがよくなり処理を追いやすくなりました。
もちろんUseCase、Repositoryについても共に目的を明確にしていますのでViewModel同様にコードが簡潔に書かれています。

  • 再利用可能

例えば位置情報取得処理など複数の画面で利用される機能は今までViewModelに書いてしまっていたものをUseCaseに移動しました。
それによりこの機能が必要な画面のViewModelにUseCaseから呼び出すだけのコードを書くだけで再利用できるようになりました。

副次効果

主目的ではありませんが、次のような副次的な効果も得ることができました。

  • アトミックコミット

例えばAPIに新しく追加されたエンドポイントのレスポンスを表示する画面を新規に追加するような場合、依存性の低いRepository、すなわちデータアクセス機能からUseCase、ViewModel、Viewという順番で実装し、レイヤー単位でgitにコミットしていくことで必然的にコミットをよりアトミックにすることが容易になりました。
つまり変更履歴を追いやすくなったり、revertやbisectをしやすくなるといった効果を期待できそうです。

  • コードレビューがしやすい

各オブジェクト、すなわち一つのファイルには最小限の責務のコードが実装されている状態になることで、ステップ数も少なくなり個人的にコードレビューがしやすくなったと感じています。
GitHubにはコードレビューする時にファイル毎に非表示にできる機能がありますが、各レイヤーの依存度の低いファイルからファイル単位でレビューし、終わったら非表示にしていくことでレビュアー側もレビューしながら処理を把握しやすい、またレビュー対象ファイルが多いと「うっ...」となりがちですが、少しずつレビューできるので負担が少し減少される、という感想を私は持ちました。

課題

今回の改善ではまだ解決できていない課題がいくつかあります。

  • APIレスポンスをViewまで利用している

これまで4つのレイヤーに分割しレイヤー間を疎結合にすることで変更に強い構成に改善しています、と述べてきました。
しかし、実情はAPIレスポンスのオブジェクトをViewまで利用している状態になっており、ドメインレイヤー以下、すなわちUIレイヤーまでがデータレイヤーに依存してしまっています。

これによりドメインレイヤー以下がAPIの変更による影響を受けやすかったり、テストコードを書く時や特にSwiftUIのXcode Previews機能を利用したい時にテストデータやモックデータの準備が大変、という問題が生じています。
こうなってしまっている理由としては、既にAPIレスポンスのオブジェクトがアプリのコアとなるところまでかなり侵食してしまっていて影響範囲が大きいため、なかなかまだ改善に手を付けられていないというのが正直なところです。

しかしながら今回の改善でデータレイヤーのデータアクセス処理にリポジトリパターンを採用していますので、今後Repositoryに腐敗防止機能を持たせてAPIレスポンスをドメインオブジェクトに変換するといった処理を組み込んでいきたいと考えています。

  • 画面遷移処理

冒頭でMVVMを採用しているのでCocoaMVCにおけるViewControllerの肥大化は抑えられているものの、実際はViewControllerのコード量は多く課題と感じていると書きましたが、その原因の一つが画面遷移になります。
画面遷移処理については、まだ必要なところ(各ViewController)に必要な分だけ実装されてしまっている状態のため、今後RouterパターンやApplication Coordinatorといったものを取り入れながら改善していきたいと考えています。

  • ファイル数が増える

クリーンアーキテクチャを導入すると必要なファイル(≒オブジェクト)数が増えるという話をよく耳にするかと思いますが、今回の改善方法で少なくともUseCaseとRepository分だけ必要なファイル数が増えることになりました。
そしてprotocolやそれを適用する具象クラスを実装し始める際に、名前だけが異なるスケルトンコードを毎回書くことになります。
実際、コードを実装し始める前に他のファイルからコードをコピペしてくる、オブジェクト名などをリネームする、もともと書かれていた実装コードを削除する、といった作業を毎回行っていてそこそこ手間に感じます。

そこでXcode Templates機能を利用したUseCaseやRepository、ViewModelのテンプレートを作成しました。(Sourceryの利用も考えましたが今回は見送りました)
ご存知の方も多いと思いますが、これは例えばUIViewControllerを新しくファイル追加した時のテンプレート、といったものを自作できるカスタムテンプレート機能になります。

今回Xcode Templatesを利用して作成したテンプレートには、対象のオブジェクト(UseCaseなど)のファイルを新規に追加した時点で必要最低限のスケルトンコードが記述されているので、すぐに具体的なコードを実装し始めることができるようになり、多少の手間は軽減されたかなと思っています。さらには命名規則の揺らぎが抑制される利点もあります。
このXcode Templatesについてはまた別の機会にこちらに書ければと思っています。

まとめ

トクバイのiOSアプリの開発における現在の課題とそれらを解決するためにアーキテクチャを見直して改善に取り組んでいることをご紹介しました。
iOS開発界隈ではReduxThe Composable Architectureといったアーキテクチャが採用されることも多いようですが、私達はまずは現状の課題を少しずつ解決していけそうなものとして、GUIアーキテクチャについてはこれまで通りMVVMを踏襲し、システムアーキテクチャとしてレイヤードアーキテクチャをベースに簡易的なクリーンアーキテクチャを導入しました。
その結果、肥大化していたViewModelはシンプルになり、ビジネスロジックが再利用可能になるなど課題と感じていた部分が改善に向かっているのを実感しています。

一方でiOSエンジニアはもともと2名体制(現在は他に業務委託の方に参画して頂いています)で、また事業として迅速にリリースが必要な新規機能開発も当然ありますので、アーキテクチャ改善というものに全力を注ぐことができません。
そのため、この改善内容の導入は足回りから、もしくは既存の画面、実装コードをドラスティックに一気に改修するのではなく、新規画面の追加時や既存画面の改修時にそれぞれの画面毎に少しずつ進めています。
レイヤーが分割されて依存関係が疎結合になり、また責務も明確に分かれることでオブジェクトも特定の責務を持つ小さな単位になるので他への展開がしやすくなり、このような小範囲からの導入が可能になっています。

取り組み始めて間もないため未対応の箇所がまだまだ90%ぐらい残っていますので、これら残りの箇所に導入していくのと同時に、さらに最適なアーキテクチャ改善を模索していきたいと思っています。
そしてトクバイアプリを利用して頂いているユーザーさんにいち早く多くの価値を届けられるように、より変更に強く生産性の高いiOSアプリ開発を目指していきたいと思っています。

参考