🎞️

Android - Architecture: The Domain Layer - MAD Skills に日本語字幕を付ける

2022/04/10に公開約16,500字

前回

https://zenn.dev/todayama_r/articles/f22a73245b758a

動画

https://www.youtube.com/watch?v=yqckluJ_zn4

スクリーンショット


こんにちは。「The MAD Skills」アーキテクチャ・シリーズの第4回目です。

今回は、データレイヤーとUIレイヤーの間に位置する

オプションレイヤーであるドメインレイヤーについてお話します。

アプリのアーキテクチャに別のレイヤーがあるなんて

なぜ別のレイヤーが必要なんだと思うでしょう?

しかし、ドメインレイヤーはアーキテクチャを単純化し

理解しやすくし、スケーラブルでテストしやすくすることができます。

では、さっそく見ていきましょう。

ドメインレイヤーはビジネスロジックを保持し

アプリの価値を高めるためのルールやアクションのセットを提供します。

ビジネスロジックは、UIロジックとは異なります。

UIロジックは画面上の表示方法を定義しますが

ビジネスロジックはイベントやデータの変更に対して何を行うかを定義します。

ビジネスロジックの例としては

ニュースアプリで最新のニュース記事を取得したり

インターネットに接続されていない場合にエラーメッセージを表示したりすることが挙げられます。

さて、小規模なアプリでは

このロジックはViewModelやデータレイヤーに保持されることが多いです。

しかし、アプリが大きくなるにつれ

すべてのロジックを新しいレイヤーである

ドメインレイヤーに統合することが理にかなっている場合があります。

ここで重要なのは、ドメインレイヤーはデータの表示方法について責任を負わないということです。

それはUIレイヤーの仕事です。

また、データの保存や取り出しにも責任を持ちません。

それはデータレイヤーの仕事です。

つまり、要約すると、ドメインレイヤーはビジネスロジックを保持するだけです。

ドメインレイヤーは Interactor またはUseCaseと呼ばれるクラスで構成されています

UseCaseは、ユーザーが行うことのできる

あるいはアプリがユーザーの代わりに実行する、1つのタスクを表します。

このエピソードでは

UseCaseクラスの命名規則として

「何をするのか」を表す動詞と

「何をするのか」を表すオブジェクト

そしてUseCaseのサフィックスを使用します。

すべてのクラスにUseCaseのようなサフィックスをつけるのは

やりすぎのように思えるかもしれませんが

特に大規模なコードベースではコードをより読みやすくすることができます。

とはいえ、この命名規則はUseCaseの命名方法のひとつに過ぎないので、

自分に合ったものを自由に使ってください。

UseCaseは単純で、軽量で、不変であるべきです。

もし、データをキャッシュする必要があるなら

このロジックはデータレイヤーに置いた方がいいかもしれません。

UseCaseは、リポジトリやデータレイヤーなどの下位レイヤーや

他のUseCaseに依存することができますが

ViewModelなどの上位レイヤーに依存するべきではありません。

リポジトリと同じように、データや操作をUIレイヤーに公開する必要があります。

Kotlinのsuspend funやFlow、またはKotlinを使用していない場合はコールバックを使用します。

もしあなたがKotlinを使っていないなら、とても残念なことです。

それでは、いくつかのユースケースを見てみましょう。

ニュースアプリで、記事を著者に関する情報と一緒に表示したい場合があります。

そのためには、NewsRepositoryから最新のニュース記事を取得し

AuthorsRepositoryからデータを組み合わせるユースケースを作成します。

また、記事の公開日をユーザーのローカルフォーマットと

タイムゾーンで表示したいとします。

これはFormatDateUseCaseで実装することができ

最初の(GetNewsWithAuthors)UseCaseで使用することができます。

ここで覚えておくべき重要なことは、UseCaseは再利用可能なロジックを含んでいるということです

そのため、他のUseCaseで使用することもできます。

そして、ドメインレイヤーに複数のレベルのUseCaseがあることは、完全に正常です。

UseCaseは1つのことしかしない(単一責任の原則)ので

Kotlinのinvoke演算子を利用して

UseCaseのインスタンスを通常の関数のように呼び出し可能に出来ます。

例えばViewModelを構築する際にUseCaseを依存関係として渡すと

通常の関数のようにUseCaseを呼び出すことができるようになります。

これにより、余分な冗長メソッド(execute等)が不要になるため

UseCaseの呼び出しが簡潔になります。

UseCaseのサフィックスを使う利点のひとつはここにあり、

通常の関数ではなくドメインレイヤークラスを呼び出していることが一目瞭然になることです。

UseCaseは独自のライフサイクルを持ちません。

その代わり、それを使用するクラスに対してスコープが設定されます。

UseCaseは変更可能なデータを含まないので

依存関係として渡すたびにUseCaseの新しいインスタンスを安全に作成することができます。

さて、スレッドについて説明しましょう。

UseCaseはメインセーフであるべきです。

つまり、メインスレッドまたはUIスレッドからUseCaseを呼び出しても安全であるべきです。

もしUseCaseが長時間実行されるブロック操作を行う場合、バックグラウンドスレッドに移します

しかし、そのブロック操作がデータレイヤーで処理され

結果がキャッシュされる方が良いのかどうかを検討してください。

ドメインレイヤーで一般的なタスクを実行する方法について、もう少し詳しく見てみましょう。

まず、再利用可能なビジネスロジックをカプセル化します。

複数のViewModelで使用されるロジックがある場合、このロジックをUseCase内に配置します

例えば、ニュースアプリでは

ユーザーの書式設定やタイムゾーンの設定に従って

日付をフォーマットするために使用される、いくつかの共通ロジックを持っています。

従来、このようなロジックは、UtilClassの静的メソッドに含まれていることが多くありました

これらのメソッドは通常見つけにくく

明確な目的を持たない雑多な関数のスペースになりかねません。

このようなロジックをUseCaseに移動することで

アプリのアーキテクチャにおける役割が明確になります。

さらに、UseCaseはベースクラスからスレッドや

エラー処理などの共通機能を共有することができ、大規模なチームにとって有益です。

UseCaseが実行できる2つ目の一般的なタスクは、複数のリポジトリからデータを結合することです。

先ほどのニュースアプリの例から発展させてみましょう。

NewsRepositoryとAuthorsRepositoryという2つのリポジトリがあり

それぞれニュースや著者のデータ操作を扱っていました。

各ニュース記事の横に、著者に関する詳細な情報を表示したいと思います。

しかし、NewsRepositoryでは著者のIDしか公開されていません。

著者情報はAuthorsRepositoryから取得することができます。

さて、このロジックを直接ViewModelに配置することができました。

しかし、リポジトリを組み合わせることは複雑なロジックを伴うことがあります。

このロジックをViewModelの中に入れると

ViewModelが不必要に大きく、複雑になり、テストが難しくなる可能性があります。

よりよい解決策は、データを結合するためのUseCaseを作成することです。

これによって、ViewModelをロジックで肥大化させることを避け

テストしやすいユニットを作成することができます。

では、その実装を見てみましょう。

このUseCaseは、リポジトリと

バックグラウンドで使用するディスパッチャを依存関係として持っています。

ここでは、UseCaseを呼び出し可能にするために Invoke 演算子を使用していますが

通常の関数でも構いませんし、どのような方法でも構いません。

この関数は、結合されたデータを含むモデルを返します。

そして、この関数の中で

NewsRepositoryから受け取った各記事を処理し、著者情報と結合しています。

これはバックグラウンドスレッドで行われます。

データレイヤーはメインセーフですが

どれだけの記事が処理されるか分からないので

呼び出し側のスレッドをブロックしてはいけないからです。

ドメインレイヤーを実装する際のもう一つの考慮点は

UIレイヤーからデータレイヤーへの直接アクセスをまだ許可するか、

ドメインレイヤーを通してすべてを強制するかということです。

この制限を設けることの利点は

UIがドメインレイヤーのロジックをバイパスするのを防ぐことができることです。

例えば

データレイヤーへのアクセスリクエストごとに分析ロギングを行っている場合などです。

また、ViewModelはリポジトリに依存するのではなく

UseCaseにのみ依存するので、ユニットテストを容易にすることができます。

ただし、潜在的に非常に重大な欠点は、

データレイヤーへの単純な関数呼び出しでも、UseCaseを追加しなければならなくなることです

これにより、複雑さが増し、メリットがほとんどなくなります。

ほとんどの場合、UseCaseは必要なときだけ追加し

UIレイヤーが必要に応じてデータレイヤーにアクセスできるようにするのが理にかなっています

しかし、いつものように、個々のコードベースや

厳格なルールが好きか、より柔軟なアプローチが好きかによって、それは変わってきます。

まとめると

ドメインレイヤーはビジネスロジックをカプセル化するために使用され

UIレイヤーの複雑さを軽減するために使用することができます。

また、複数のViewModelで使用されるロジックを1つのUseCaseに抽出することで

重複を避けるために使用することもできます。

そして最後に、テスト容易性を向上させることができます。

ロジックは、一つのことだけを行う小さなクラスに含まれていると、テストが非常に簡単になります。

ドメインレイヤーとは何か、どのように使うかを理解した上で

自分のコードベースに適しているかどうか判断してください。

この講演はMADアーキテクチャガイダンスの要約に過ぎませんので

より詳細な情報はこちらのガイドをご覧ください。

以上、今回はここまでです。

また、このようなビデオをもっと見るには、このチャンネルを購読してください。

ありがとうございました。

Discussion

ログインするとコメントできます