クリーンアーキテクチャに基づいた設計で以下のような、TwitchTV配信のコメントを拾い上げて棒読みちゃんで読み上げてくれるアプリケーションを作りました。
ソースコード
設計
設計は、おおむね以下の図のような構造としました。
以下、今回のアプリケーションにおける各要素の役割について書いていきます。
Domain層
アプリケーションが達成するビジネスロジックの核となるレイヤー。
今回でいうと、Repositoryからデータ(Twitch配信のコメントや必要となる認証トークン等)を取得し、それをApplication層向けに加工して伝達する役割を担っている。
このレイヤーはApplication層にもData層にも依存しておらず、それぞれの修正がDomain層に直接波及しないように設計しています。
- Interactor
- いわゆるUsecaseとも呼ばれるクラス。『Twitchのコメントを取得し、それをApplication層に伝える』といったような単一のユースケースを達成するためのコードをここに書いてます。
- Translator
- Interactorが取得したEntityをApplication層向けのModelに変換するクラス。なんでこんな変換を挟むかというと、Data層やDomain層での修正が直接Application側に波及してしまうのを防ぐため。
- Entity
- ビジネスロジックでの操作対象となる要素というイメージです。今回のアプリでいうとTwitchのコメントそのものだったりチャンネル情報だったりします。
- Model
- Application層が扱うデータ。Entityと分けている理由は、Entityはビジネスロジックそのものを意識し、Modelはアプリケーションを意識している感じです。今回のアプリでは具体的にいうと、『Twitch配信のコメントリスト』という概念はEntityに属し、『コメントビューアーにおけるコメントリスト』という概念はModelに属します。こういう分け方をすることで、例えば後日Entityに『Youtube配信のコメントリスト』を追加しても、間に変換が挟まっているのでApplication側はTwitchもYoutubeも『コメントビューアーにおけるコメント表示モデル』として画一的に扱えて機能追加がスムーズになるかもしれません。
Application層
Domain層が受け取ったデータをもとにUIを描画したり、ユーザーのインタラクションをController経由でDomainに伝達するレイヤー。ビジネスロジックを包含したアプリケーションを構築する層。Presentation層とも呼ばれるかもしれない。
Application層はDomainのビジネスロジックを包含する形で依存しており、基本的には分離できない構造となっています(アプリケーションロジックの達成のためにドメインロジックが必要)。
- Controller
- UI(View)から伝えられた入力情報をもとにInteractorに入力し、その出力をPresenterに伝達するクラス。
- Presenter
- Controllerから伝達されたモデルをViewModelに反映させるクラス。ViewModelにあまりアプリケーションのロジックを書きたくなかったので、複雑な処理はなるべくこちらに書いています。Viewの入力をControllerに伝達するためのCommandをViewModelにバインドする役割も負っています。
- ViewModel
- Viewのデータバインディングを行うためのクラス。単純に、Viewが要素を表示するための情報を保持するクラスというイメージで今回は設計しています。View及びViewModelにロジックが入り込んでくるとUIがらみの動作確認が難しくなると考えて、ロジックはPresenterに書いていき、ViewModelには単純な値入力のみを行うような形の設計にしました。
- View
- このクラスはWPFにおけるUI描画を担当するクラスで、ロジックは基本的に一切書きません。XAMLで各要素を書いてViewModelをバインドし、ViewModelの値が変化したらデータバインディングによってそれを反映します。
Data層
Domain層がビジネスロジックを果たす上で必要となるデータの取得や保持に関わる機能を受け持つレイヤー。今回はTwitch配信のコメントを読み上げるソフトですので、読み上げに必要となるコメントの取得やそのための認証等の機能を担当する部分です。
この層はDomainが定義するRepositoryインターフェースやEntityに依存しています。
- Repository
- ビジネスロジックで扱うデータの取得や永続化を担当するクラス。RepositoryのインターフェースはDomain側にあるので、DataとDomain間の架け橋になるのがこのクラスです。
- DataStore
- Network上やLocalに存在するデータの取得を実際に行うクラス。今回はTwitchLibというライブラリを使ってTwitchTVのデータにアクセスしているので、そのライブラリを利用する処理をこのあたりのクラスに書いてます。また、オフラインでもアプリケーションの動作確認ができるようにローカルで用意したデータを取得・伝達するクラスも用意しています。
その他
- ServiceCollection
- 各レイヤーの依存性注入を行うクラス。実際のAPIにアクセスする本番用のReleaseモードとオフラインで動作確認するためのDebugモードで注入する内容を切り替える役割を負っています。
クリーンアーキテクチャで実際に開発してみた感想
- 感じたメリット
- レイヤーを定義することで各クラス間の責務と依存方向が明確になり、コード変更の影響がレイヤー内にとどまるため修正がしやすいと思いました。
- 先にドメインレイヤーでインターフェースを定義することで、詳細の決定を遅らせられる。ローカルで動くスタブだけ用意してアプリケーションの開発をしたり、扱うデータベースの種類を後から変えたりといった柔軟な開発が可能になりました。
- ビジネスロジックを持つドメインを他に依存しない設計にすることでドメイン単体でのテストが行いやすく、品質の担保が容易になりました。
- 各レイヤーを切り分けたことで、複数人での開発がしやすいと思いました。例えばApplication層のViewやViewModelはほとんどDomainに依存しない仕組みになっているのでDomain部分担当、View担当のようにチームで分担しての並行開発が容易だと思います。
- 規模が大きくなることが予想されてる、長期間に及ぶ開発では採用するメリットは大いにあり。
- デメリット
- レイヤーをわけていく都合上コードが冗長になるため、開発初期段階では単純に手間が増えるかと思いました。今回は比較的シンプルなTwitchTV配信のコメント読み上げアプリケーションだったので、そこまで複雑なビジネスロジックもなく、やや恩恵を感じづらかったかもしれません。
- 少数チームでの短期間の開発とかなら、採用するメリットはあまりないと思います。
作ってて大変だったところ
Twitchの認証処理を書くのが大変だった
認証トークンを取得してからの処理はライブラリのTwitchLibにほぼ丸投げだったので楽だったんですが、取得までの道のりが長かったです。
C#側でサーバーを立ち上げてブラウザのリダイレクトからアクセストークンをアプリケーションで取得するまで色々試行錯誤しました。
これについてはまた後日別記事を書きます。
WPFとクリーンアーキテクチャを織り交ぜた設計が難しかった
普段はUnityで開発をしていたのですが違うフレームワークでアプリケーションを作ってみたいと思い、WPFを選んだんですがデータバインディングのような独自のルールが結構理解が難しく、それらとクリーンアーキテクチャを混ぜて破綻ないように設計するのはかなり骨が折れました。
そもそもWPFに関する記事があまりなかった
QiitaでWPFタグで検索すると見つかる記事数が1370(2023年6月8日現在)と、Unityの14958より大分記事数が少ないため参考にできる情報があまりなく、実現したい機能をどう実装するか考えるうえで大変でした。
参考
実装クリーンアーキテクチャ
https://qiita.com/nrslib/items/a5f902c4defc83bd46b8
RxSwift+CleanArchitectureで構成をキレイにしよう
https://qiita.com/tamayuru/items/618ba3cf20be373f7455