やんざむ先生のこのツイートを見て考えたことをまとめます。
UseCase がわからない...
— Yuki Anzai (@yanzm) February 15, 2019
FizzBuzz で
「3の倍数のときは fizz が返る」
「5の倍数のときは buzz が返る」
「3の倍数かつ5の倍数のときは fizzbuzz が返る」
「3の倍数でも5の倍数でもないときはそのままの数字が返る」
これは
結論を先に書くと、「これはそもそも問い自体が不適切である」(しかし強いて言えばEntity)という立場をわたしは取ります。以下、まじめにFizzBuzzを設計しながら考えてみます。
ひとまず、出発点は上にあるツイートの疑問から出発するとしましょう。
まず前提として、ユースケースってどういう役割か
まず前提として、ぼくはユースケースを「ドメインモデル(このツイートで言うところのEntity)やインフラストラクチャのオーケストレーションをする部分」だと思っています。
たとえば、「あるボタンをクリックされたら、複数のドメインモデルを利用して生まれた結果を、データベースに保存する」というのは、複数のドメインモデルとインフラストラクチャ層にあるリポジトリを 組み合わせて 、システムのユーザーのアクションに1対1で対応する「ユースケース」を実現しています。
つまり、ユースケースというのは、
- (1). 外部からのアクションに1:1で対応するシステムの動きを
- (2). 複数のドメインモデルやインフラストラクチャ層のオブジェクトをオーケストレーションして実現する
ものだ、と考えています。
FizzBuzzについて考えてみる
さて、翻って、FizzBuzzについて考えてみましょう。まず、FizzBuzzを無理やりアプリケーション層とドメイン層に分けて考えるとしましょう。その場合、
というのは、FizzBuzzという問題領域を定式化、つまりまさにモデル化したものだと言えるでしょう。なので、これをドメインレイヤーのものであると考えるのは妥当だとすることにそれほどの無理はないでしょう。
ドメインレイヤーでこの定式化された知識をどのように分割するか、という点では、いくつかの判断がありえます。ぼくがぱっと思いついたのは以下のような感じです。
- 数をValueObjectとみなして、それを利用してDomainServiceがループで文字列を返す
- もう全部DomainServiceに書いちゃう
前者の発想だと、以下のような実装になります。
case class FizzBuzzNum(i: Int) { def asFizzBuzzFormat = i match { case i if i % 15 == 0 => "FizzBuzz" case i if i % 5 == 0 => "Buzz" case i if i % 3 == 0 => "Fizz" case i => i.toString } } object FizzBuzzService{ def doFizzBuzz(max: Int): Unit = { (1 to max) .map(i => FizzBuzzNum(i).asFizzBuzzFormat) .foreach(println) } }
一方で後者の発想だと以下のような実装になります。
object FizzBuzzService{ def doFizzBuzz(max: Int): Unit = { (1 to max) .map{ case i if i % 15 == 0 => "FizzBuzz" case i if i % 5 == 0 => "Buzz" case i if i % 3 == 0 => "Fizz" case i => i.toString } .foreach(println) } }
これは、どちらのほうが「良い設計」でしょうか。設計のレビューをするときに参考にすべきなのが各種設計原則です。そして、設計原則を適用した際の結果というのは、「問題がどのようなものか」に依存するのでした。このあたりの詳しい話は実況中継シリーズ 「開発現場で役立たせるための設計原則とパターン」 #builderscon 2018を参照してください。
さて、FizzBuzzについて、「どの部分に今後の変更がありえて、どの部分に今後の変更がないのか」を考えてみましょう。それを考えると、実は「FizzBuzzは完成された問題であり、今後の変更はない」という答えが見えてきます(!)。そうである以上、上のふたつの設計のどちらが正しいかなんてありません。問題が十分に小さいため、どちらのコードもそれなりに可読性があるし、あとはもう趣味の問題です。好きな方にすればいいと思います。
さて、ドメインレイヤーの設計が終わったところで、ユースケースのことを考えてみましょう。ユースケースは
- (1). 外部からのアクションに1:1で対応するシステムの動きを
- (2). 複数のドメインモデルやインフラストラクチャ層のオブジェクトをオーケストレーションして実現する
ものなのでした。しかし、今回の例に「複数のドメインモデルやインフラストラクチャ層のオブジェクトをオーケストレーション」する必要はあるでしょうか?「ない」というのがその答えではないでしょうか。そうなると、FizzBuzzという問題領域において、ユースケース層の役割は「無い」ということになります。プレゼンテーション層から直接このドメインサービスを叩くだけで良いでしょう。つまり、そもそもFizzBuzzを真面目に設計すると、多層からなる設計は不要というか、多層からなる設計をしようとしたら「役割のない層」が生まれてしまうという、考えてみれば当たり前のところに落ち着いてしまうわけです。
そして、ユースケース層の役割が「無い」ような問題領域を例にとって「ユースケースとはなにか?」を考えても、おそらく意味のある答えは期待できないでしょう。それを考えると、どうしても「これはそもそも問い自体が不適切である」という結論が出てきてしまうのです。
まとめ
このエントリの内容を要約すると、以下のようになります。
- ユースケース層は
- (1). 外部からのアクションに1:1で対応するシステムの動きを
- (2). 複数のドメインモデルやインフラストラクチャ層のオブジェクトをオーケストレーションして実現するものである
- そのため、複数のドメインモデルやインフラストラクチャ層のオブジェクトをオーケストレーションが不必要になるような十分に小さい問題領域ではユースケースの役割は存在しない
- ユースケースの役割が存在しない問題領域を例にとって「ユースケースとはなにか?」を考えるのはおそらく不毛である
追記:
アプリケーション全体のうち一部はユースケース層あった方がよいからユースケース層作った結果、別の部分ではなくても十分っていうかユースケース層見てみたらドメインサービス叩いてるだけ、みたいなことになることはあります
— しんぺい live at 荻窪alcafe 2/16昼 (@shinpei0213) February 15, 2019