この記事では、C#でフロントエンドWebアプリを開発できるフレームワーク、Blazor Webassembly (.NET 5.0) でのソフトウェア開発に、ソフトウェアアーキテクチャの1つであるクリーンアーキテクチャを適用するベストプラクティスを紹介します。
Simple Clean Architecture
クリーンアーキテクチャは、ソフトウェア・アーキテクチャの1つで、ソフトウェア開発でのコンポーネント間の疎結合/低依存を実現するための設計指針を示しているものです。クリーンアーキテクチャ自体は有益な概念ですが、実際に開発の現場で厳密に運用しようとすると、コードベースがとても重厚になり、ファイル数も大きくなりがちという課題があります。この課題を解決し、現場での実装に使用できるようなデザインパターンに落とし込んだものが Simple Clean Architecture (SCA) です。SCAに関しての詳細記事は下記をご参照ください。
Blazor Webassembly
Blazor Webassemblyは、 .NET が提供するフロントエンドWebアプリの開発フレームワークです。C#でフロントエンド (とくに Progressive Web App) が開発でき、 React や vuejs などの従来の Javascript でのフロントエンド開発に対する新しい選択肢として、近年開発元のMicrosoftも力を入れているものです。
Blazor での Simple Clean Architecture
Blazor自体は ASP.NET, .NET Coreの系譜の技術なので、Microsoftが元々想定している基本のソフトウェアアーキテクチャは MVVM (Model View ViewModel) になります。Blazor の 拡張子.razor ファイルは HTMLとC#を混在して記載でき、C#の世界と HTML/DOMの世界は ViewModel を介した双方向データ・バインディングが行える。ということで、MVVMで基本的なアプリ開発は十分に行えます。
しかしながら、実務でBlazor上で大規模なPWAを開発してコードベースが大きくなってくると、Viewにあたる.razorファイルに大量のロジックが記載されたり、Model部分が肥大してModel間の循環参照や密結合が顕在化し、プロジェクトの全体感が分からなくなる / コード構成が把握できなくなる といった課題が顕在化してきます。
このような課題を解決するために、SCAの考え方をBlazor Webassemblyに適用した設計指針を用いる事ができます。
SCAのコンポーネント
SCAでは下記のような観点でソフトウェアをコンポーネントに分解します。
- コンポーネントを責務によって Entity/ Gateway/ Presenter/ Usecase/ View に分ける
- コンポーネント間の参照にルールをつける(例: PresenterはUsecaseを参照できるが、逆はできない)
クリーンアーキテクチャではたくさんのコンポーネントが定義されていますが、SCAではそのうちの5種類 (Entity/ Gateway/ Presenter/ Usecase/ View) を使用します。それぞれの役割の詳細は SCAの元記事 を参照してください。ここでは各コンポーネントの実装ルールのみ記します。
なお、以下で「依存」という言葉を使っていますが、これは あるクラスが他のクラスへの参照をメンバ変数としてもっていることを言っています。ソフトウェアの依存に関しては、こちらの記事を参照してみてください。
-
Entity
Entityは、「最も基本となるビジネスルールのカプセル化」を担当します- Entityは 他のどのコンポーネントにも依存してはいけません
-
Usecase
Usecaseは、Entityを操作して、UMLにおけるユースケース、すなわちシステムの何らかの機能/目標を実行するロジックを担当します- Usecaseは インタフェースを通じて Gatewayに依存できます
- Usecaseは View, Presenterに依存できません
-
Gateway
Gatewayは、アプリの外のシステム (DBやライブラリ, APIなど) とのやりとりを担当します。- Gatewayは View, Presenter, Usecaseに依存できません
-
Presenter
PresenterはViewからの入力をうけてUsecaseを駆動させ、結果をViewが受け入れられる形にする処理を担当します- Presenterは インタフェースを通じて Usecaseに依存できます
- Presenterは View, Gatewayに依存できません
-
View
ViewはUIの入出力を担当します- Viewは HTMLを記述することができます (.razor拡張子)
- Viewは Usecase, Gatewayに依存できません
- Viewは インタフェースを通じて Presenterに依存できます
- Viewは 他のViewのメソッドを呼ぶことはできません (Blazorの構造上、上位のViewが下位のViewを内包することは許容されます)
ここで次に考えなくてはいけないのが、どうやってこの依存関係を管理するのか?です。素晴らしいことに、Blazorは標準で依存関係の注入(Dependency Injection, DI)をサポートしています (公式ドキュメント)。DIはSCAに限らない一般的なソフトウェア設計の概念なので、検索してみてください(たとえばこの記事など)
SCAでのコンポーネント間の情報伝達
コンポーネント間の情報の伝達はイベント駆動型プログラミング (あるいは Reactive Programing) の考え方で実現します。Blazor (C#)では、このために Reactive Extensions (System.Reactive)が利用できます。Reactive Extensionsはイベント駆動のソフトウェアを実現するために多くの機能を提供してくれているツールですが、さしあたりは Subjectが使えればSCAでは十分です (Subjectの使い方)。
イベント駆動型プログラミングにより、クリーンアーキテクチャのレイヤー構成における外側のコンポーネントから内側のコンポーネントに "のみ" 依存する実装が実現できます (下図参照)。これにより相互参照を避け、システムを疎結合に保つことで、ソフトウェアの機能追加や仕様変更におけるコードの混乱を少なくすることができます。
実装サンプル
さて、これまで文章で Blazor Webassemblyへの SCAの適用方法を述べてきましたが、実装例を見るのが一番手っ取り早いと思いますので、下記の GitHubリポジトリで公開しております。
SCAに限らず、チームでソフトウェア設計ガイドラインを共有していると、新しくプロジェクトに参加した人でもドキュメントが少なくてもソフトウェアの全体構造が把握できたり、しばらく期間が過ぎたあとにコードを読み返してもキャッチアップしやすかったり、複数の人で開発していても「ここのUsecaseの書き方なんですけど...」といったように共通言語で設計が話せるようになったりと、便利な面が色々とあります。
SCAの考え方が、皆さんのチームが同じ方向を向いてプロジェクトを成功に導かせるための要因になれば幸いです。良いソフトウェア・アーキテクト・ライフを!