はじめまして、Vitantonio Nagauzzi (@tonionagauzzi)です。本書を執筆している2022年6月現在、株式会社ACCESSにて、Android™とiOSのモバイルアプリを開発しています。
2019年から新規開発が始まった受託案件に、クリーンアーキテクチャーを導入しました。本章では、その経験を通して学んだ諸々をご紹介します。
クリーンアーキテクチャー自体の解説もある程度含まれますが、ページ数の都合上、歴史背景や細かい部分までは説明していませんのでご容赦ください。
簡単ですが、案件の情報を下記に記載します。
プラットフォーム | Android/iOS |
言語 | Kotlin/Swift |
開発チーム人数 | 3〜6人 |
なぜアーキテクチャーを選定するのか。それは、求められるシステムの構築・保守に必要な人材を、最小限に抑えたいからです。
アーキテクチャーは上位レベル、設計は下位レベルといわれることもありますが、両者の間に明確な境界はなく、上位から下位に至るまでが決定の連続です。
モバイルアプリ開発で代表的なアーキテクチャーとして、GoogleがAndroidに推奨しているMVVM、AppleがiOSのCocoa Applicationに採用しているMVCがあります。
では、Android、iOS両方で同じアプリを作る場合はどうでしょうか。並行作業を楽にするため、少なくともビジネスロジック部分は共通化したいと多くの人が考えます。
Xamarin、Flutter、Kotlin Multiplatform Mobileのようなクロスプラットフォーム開発手法が有名ですが、これらは採用しない場合と比較して保守できるエンジニアが少ない、トラブルシューティング事例が少ない、サポート打ち切りの可能性が上がるなどのリスクが伴います。
そういったリスクを考慮して、私たちはクロスプラットフォーム開発手法を採用しませんでした。受託案件では、何か発生したとき迅速にアップデートできるほうがよいからです。
MVCを用いると、View、Modelのいずれにも属さないコードがControllerに実装されがちで、気がつけば膨大なControllerと、小さなView&Modelという構成になりがちです。
VIPERは、iOSでMVCに置き換わるとされているクリーンアーキテクチャーの一種で、単一責任の原則 (SRP)に基づいています。View、Interactor、Presenter、Entity、Routerの略語です。
AndroidはViewとRouterの分離が難しいので適用には不向きですが、VIPEで組み上げた後にVの一部をRouter化するという工夫も存在します。
私たちはVIPERではないですが、クリーンアーキテクチャーベースの設計を両OSに用いることで、OS間の実装差分を極力減らそうとしました。
クリーンアーキテクチャーは、設計の明確なテンプレートではなく、さまざまな設計のベースになる概念です。
ここで、先ほど出てきた単一責任の原則および他のSOLID原則を説明します。
・単一責任の原則 (SRP)
─ひとつのモジュールはたったひとつの役割に対して責務を負う
・オープン・クローズドの原則 (OCP)
─拡張に対して開かれており、修正に対して閉じている
─つまり変更が発生したら既存のコードは修正せず、新しくコードを追加して対応する
─オブジェクト指向設計の核心で、再利用、保守、柔軟性のメリットが受けられる
・リスコフの置換原則 (LCP)
─基底クラスから派生したサブクラスは、常に基底クラスと置き換え可能にする
・インターフェイス分離の原則 (ISP)
─クライアントが利用しないメソッドをインターフェイスに含めない
─クライアント毎に細かく分離する
・依存関係逆転の原則 (DIP)
─抽象モジュールを具象モジュールに依存させるのではなく、具象を抽象に依存させる
これらのうち、DIPがクリーンアーキテクチャーにおいてとくに重要な原則で、次いでSRP、OCPが重要と私たちは考えています。
レガシーな階層アーキテクチャーでは、図1.1のように、ドメイン層 (ビジネスルール)から永続化層 (DB)へと直接参照が行われていました。
この構成は、抽象 (BusinessRule)が具象 (SQLDatabase)に依存しており、明らかにDIPに違反しています。
では、どうすればよいでしょうか。違反を解決するには、図1.2のようにインターフェイスを挟んだ構成にします。
これだけでDIPに準拠します。インターフェイスが抽象モジュール内にあり、具象モジュールは抽象側が提供する仕様に合わせて組み立てられるからです。
では、依存関係が逆転すると柔軟なシステムになる、と言えるのはなぜでしょうか。
技術は変わりやすく廃れやすいものです。たとえば、いままで使っていたOSSの更新が止まったから別のOSSに乗り換えたい、あるいはDBフレームワークのパフォーマンスが悪いので別のものに乗り換えたいなど、さまざまな状況があるでしょう。
しかし、企業のビジネスルールは、そう簡単には変わりません。たとえば銀行のATMの使い方は、短期間で頻繁に変わったりしないですよね。
変わりにくいものを変わりやすいものに依存させると、システム全体が不安定になります。図1.3における企業のビジネスルールがOSSやDBに依存している状態で、OSSやDBの更新が発生すると、ビジネスルールも変える必要があります。変更が大きければ大きいほど、開発者の意図しないバグが混在したり、コストが上がります。
依存関係を逆転させれば、企業のビジネスルールを変えずに、あるいは影響を最小限に抑えつつ、OSSやDBを変更できるのです。
また、具象モジュールのモックを用意することで、単体テストの作成・実施も容易になり、より安定したシステムになります。