FizzBuzzを題材にユースケース層についてを考えるのはおそらく無意味な気がする

やんざむ先生のこのツイートを見て考えたことをまとめます。

結論を先に書くと、「これはそもそも問い自体が不適切である」(しかし強いて言えばEntity)という立場をわたしは取ります。以下、まじめにFizzBuzzを設計しながら考えてみます。

ひとまず、出発点は上にあるツイートの疑問から出発するとしましょう。

まず前提として、ユースケースってどういう役割か

まず前提として、ぼくはユースケースを「ドメインモデル(このツイートで言うところのEntity)やインフラストラクチャのオーケストレーションをする部分」だと思っています。

たとえば、「あるボタンをクリックされたら、複数のドメインモデルを利用して生まれた結果を、データベースに保存する」というのは、複数のドメインモデルとインフラストラクチャ層にあるリポジトリ組み合わせて 、システムのユーザーのアクションに1対1で対応する「ユースケース」を実現しています。

つまり、ユースケースというのは、

ものだ、と考えています。

FizzBuzzについて考えてみる

さて、翻って、FizzBuzzについて考えてみましょう。まず、FizzBuzzを無理やりアプリケーション層とドメイン層に分けて考えるとしましょう。その場合、

  • 3で割れる場合はFizz
  • 5で割れる場合はBuzz
  • 3でも5でも割れる場合はFizzBuzz
  • それ以外の場合は数字をそのまま表示する
  • これを1から指定の数字まで繰り返す

というのは、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は完成された問題であり、今後の変更はない」という答えが見えてきます(!)。そうである以上、上のふたつの設計のどちらが正しいかなんてありません。問題が十分に小さいため、どちらのコードもそれなりに可読性があるし、あとはもう趣味の問題です。好きな方にすればいいと思います。

さて、ドメインレイヤーの設計が終わったところで、ユースケースのことを考えてみましょう。ユースケース

ものなのでした。しかし、今回の例に「複数のドメインモデルやインフラストラクチャ層のオブジェクトをオーケストレーション」する必要はあるでしょうか?「ない」というのがその答えではないでしょうか。そうなると、FizzBuzzという問題領域において、ユースケース層の役割は「無い」ということになります。プレゼンテーション層から直接このドメインサービスを叩くだけで良いでしょう。つまり、そもそもFizzBuzzを真面目に設計すると、多層からなる設計は不要というか、多層からなる設計をしようとしたら「役割のない層」が生まれてしまうという、考えてみれば当たり前のところに落ち着いてしまうわけです。

そして、ユースケース層の役割が「無い」ような問題領域を例にとって「ユースケースとはなにか?」を考えても、おそらく意味のある答えは期待できないでしょう。それを考えると、どうしても「これはそもそも問い自体が不適切である」という結論が出てきてしまうのです。

まとめ

このエントリの内容を要約すると、以下のようになります。

追記: