ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

Fizz Buzz と税率とタイムゾーンの話 (ドメインレイヤとアプリケーションレイヤの話、あるいは時間変化する値をモデリングする話)

Twitter で見かけて面白そうだったのでちょっと考えてみた。

Fizz Buzz のロジックをドメインレイヤ (Entities 層) とアプリケーションレイヤ (Use Case 層) のどちらに書くか

(実際に作るアプリケーションにおいてはいろいろ考慮して設計することになると思うけど) Fizz Buzz の問題を素直に捉えると 「3 の倍数のときは fizz が返る」 というのは問題領域に存在するロジックなので (例えば、ソフトウェアを実装せずに人が手で Fizz Buzz を書きだす場合でもこのロジックは使われる)、自分だったらドメイン層に書くなーと思った。

(文字列を返すドメインサービスにするのか、バリューオブジェクトを定義するのか、みたいなところは場合によって分かれそう。)

アプリケーション層 (Use Case 層) にはアプリケーションとしてどう使われるかに応じたロジックを書く。 例えば 「1 から指定された値までの Fizz Buzz をコンソールに表示する」 だったらコンソールにアウトプットするための処理を書く *1 し、例えば 「1 秒ごとに Epoch 秒に応じた Fizz Buzz の値をどこかに送る」 みたいなアプリケーションだったら Epoch 秒を取得してどこかに送るみたいな処理を書くことになる。 (個人の見解。)

『Clean Architecture』 によると

Clean Architecture 達人に学ぶソフトウェアの構造と設計 (アスキードワンゴ)

Clean Architecture 達人に学ぶソフトウェアの構造と設計 (アスキードワンゴ)

Uncle Bob によって書かれた書籍にはエンティティについて下記のように書かれている。

エンティティは、企業全体の最重要ビジネスルールをカプセル化したものだ。エンティティは、メソッドを持ったオブジェクトでも、データ構造と関数でも構わない。企業にあるさまざまなアプリケーションから使用できるなら、エンティティは何であっても問題はない。

企業が存在せず、単一のアプリケーションを作成しているだけなら、エンティティはアプリケーションのビジネスオブジェクトになるだろう。それは、最も一般的で、最上位レベルのルールをカプセル化したものである。

ユースケースについては下記。

ユースケースのレイヤーのソフトウェアには、アプリケーション固有のビジネスルールが含まれている。ここには、システムのすべてのユースケースカプセル化・実装されている。ユースケースは、エンティティに入出力するデータの流れを調整し、ユースケースの目標を達成できるように、エンティティに最重要ビジネスルールを使用するように指示を出す。

これに照らしてみても、Fizz Buzz の数値から文字列を生成するロジックは問題の最上位レベルのルールなのでエンティティで、入出力回りなどをユースケースに記述する、って感じになりそう。

税率

という Fizz Buzz だけの話だったらブログに書くほどのものでもないのだけど、税率の話 (というか時間変化する状態のモデリング) については最近考えていたこともあったので吐き出しておく。

(税率とは関係ないけど、*2 上のついーとのリプ先に 『仕様追加が容易に想像できるならばUseCaseにします』 って書かれてるのは本質的ではない話で、ちゃんと問題領域をとらえてドメイン層を定義すべきでしょ、って思った。)

この話って、抽象的には 「時間変化する状態をアプリケーション上でどう扱うか」 という話 (ではなかった)*3 で、時刻が変化したときに状態がどう変化するか (例えば 2019 年 10 月までは税率 8 % で、2019 年 10 月からは税率 10 % になる) という部分も含めてドメインモデリングするのがうまい方法なんだと思う *4

つまり、ドメイン層には 「指定の商品について、指定された時刻における税込み価格を計算する」 みたいなロジックを定義して、アプリケーション層の方でそのロジックに現在時刻を渡してやることで、その時点での税込み価格を計算する、みたいな感じになる。

時間変化のモデリングの例 : Date and Time API におけるオフセット時間の時間変化

時刻によって変化する値のモデリングの例として Java の Date and Time API (JSR 310) がある。 Date and Time API は 「時刻やタイムゾーン」 を問題領域とする API であり、時刻についてのドメインモデルとして捉えて良いだろう。

このドメインにおける 「時間変化する値」 としてはタイムゾーンごとのオフセット時間がある。 タイムゾーンごとのオフセット時間は時刻によって変化するが、それがうまくモデリングされて、きれいな API が提供されている。

最近あった身近なオフセット時間の変化としては、平壌時間における 2015 年と 2018 年のオフセット時間の変化がある。

協定世界時 (UTC) より9時間進んでおり (UTC+9)、大韓民国(韓国)で使用されている韓国標準時 (KST) や、日本で使用されている日本標準時 (JST) と同じである。2015年8月15日から2018年5月4日まではUTC+8:30と定められていた。

平壌時間 - Wikipedia

こういうオフセット時間の時間変化も Date and Time API の実装が面倒を見てくれるので、API を使う側はオフセット時間の時間変化について知らずとも任意の時刻の指定のタイムゾーンでの現地時刻を取得することができる。

/** 渡された時刻を平壌時間での [LocalDateTime] にして返す。 */
fun calculatePyongyangLocalDateTime(instant: Instant) =
    instant.atZone(ZoneId.of("Asia/Pyongyang")).toLocalDateTime()

fun main() {
    val pyongyang2015 =
        calculatePyongyangLocalDateTime(Instant.parse("2015-01-01T12:00:00Z"))
    val pyongyang2017 =
        calculatePyongyangLocalDateTime(Instant.parse("2017-01-01T12:00:00Z"))
    val pyongyang2019 =
        calculatePyongyangLocalDateTime(Instant.parse("2019-01-01T12:00:00Z"))

    println(pyongyang2015) //=> 2015-01-01T21:00
    println(pyongyang2017) //=> 2017-01-01T20:30
    println(pyongyang2019) //=> 2019-01-01T21:00
}

(問題領域に税率を含み、時間の概念も持つアプリケーションを開発するのであれば) Date and Time API におけるタイムゾーンと同様に、税率を時間変化する値としてドメインモデル上で定義すると、ドメインモデルを使う側からはいい感じに税率の時間変化が隠蔽されて良い感じになるのではなかろうか。

ドメインとかユースケースとか

最近 「ドメイン」 とか 「ユースケース」 って言葉を聞くことが多いのだけど、人によって解釈が全然違ったりするなー、ということをよく感じる。 そもそも明確な定義みたいなのもないと思うのだけど、個人的には下記の書籍を読んだことで理解が進んだので、「ドメイン」 とか 「ユースケース」 についてよくわかんないけどなんか書籍を読みたい、って人は下記みたいな書籍を読むと良さそう。 (私は DDD に関する本はいろいろ読んだけどユースケースについて言及している書籍は下記の 1 冊しか読んだことがないので、他におすすめがあれば教えてください!)

ユースケース駆動開発実践ガイド

ユースケース駆動開発実践ガイド

この書籍では、要件定義からアプリケーションの実装までの開発のライフサイクルを扱っている。 まずアプリケーションが扱う問題領域から重要な言葉を抜き出してドメイン辞書を作る。 それからユースケース (アクター *5 とシステムの相互作用についての記述) を記述する。 ドメイン辞書は静的な設計の出発点で、ユースケースは動的な設計の出発点。 ここから初めて実装に落とし込むまでの流れを説明する、という感じ。

で、この書籍で説明されているユースケースと Clean Architecture でいうユースケース層の関係としては、ユースケース記述に書かれている文章と同じぐらいの粒度でユースケース層のメソッドを実装する、って感じになる。

なので、単純なユースケースについては 『ユースケース図の吹き出しで出てくるぐらいの粒度のものがその名の通り1UseCaseというイメージ』 となるはず *6。 実際のところは、画面をまたがるユースケースがあったり、イベント駆動な設計になっている場合もあったりするので、1 ユースケースユースケース層の 1 メソッドや 1 クラスに相当するということはなくて、複数メソッドに分散したりするはず。

*1:実際のコンソールへのアウトプットを行う処理自体はさらに上のレイヤから渡すべきで、アプリケーション層では抽象化された出力操作を行う。

*2:下の注釈に書いたとおり、関係あるということに気づいた。

*3:Fizz Buzz の話の流れで書かれていたので、この文を書いていた時には 「Fizz Buzz の入力数字を時刻、出力の文字列を税率と見立てて、税率の時間変化をどう扱うか」 という問題提起なのかなーと思ったんだけど、今読み返すと 「仕様追加が容易に想像できるならばUseCaseにします」 への応答だったんだな、と思った。 文脈が読めてなかった……><

*4:もちろんアプリケーションに求められるものによって税率などをどこで扱うのがいいかは違ってくるとは思っていて、例えば 「表示時にシュッと税込み価格を計算して表示すればいいだけで、ドメインロジックとして税率を扱う必要はない」 みたいなアプリケーションだったらプレゼンテーション層に税率計算の処理を入れちゃってもいい話だし。

*5:システムを使う人やモノ。 ユーザー。

*6:ユースケース図の吹き出しが、1 つのユースケース記述に相当する。