はじめに
今日あった増田さんのDDD Allianceの3週連続DDDの話を聞いてきた所、最後の質疑応答で、
「ScalaやHaskellなんかの関数型的な考え方が適応できるんじゃないか?」
という質問が聴講者の方から上がったのですが、
増田さん的には「まだ挑戦的試みの域を出ない」という回答があったので、
ScalaでDDDを2年近くやってきた者として、これは役立つよねという手法を紹介しようと思います。
正直な話、DDDも関数型プログラミングも学ぶのに根気のいる難しい概念にもかかわらず、
バズワード化していろんな人が違う意味で使うようになってしまったので、
正直最近こういう話を書きたいと思わなくなってしまったし、
イスラムのムジャーヒディーンと十字軍の両軍の前で正義の定義について演説することに
近いものがあると思うので、気は進まないながらも、役立つものを紹介しようと思います。
まず最初に前提を定義させてください。
DDDについて、この文章においては、
ドメインモデルを中心に置き、それを表現するユビキタス言語によって行う設計と定義します。
細かく言うと違うけれど、ここではそうします。
ドメインモデルとは、そのソフトウェアが対象とする業務ドメインを単純化、抽象化した概念で、
企画や開発者などの関係者の中で図などで共有されているものと定義します。
またユビキタス言語について、そのソフトウェア開発のプロダクトにかかわる全員が、
会話、ドキュメント、コードにおいて統一的に使うドメインモデルに関する用語の集まりであると定義します。
前置きはおいておいて、関数型プログラミングのエッセンスでDDDをやるうえで便利な機能を紹介していきます。
なおここでは、差を出すためにサンプルコードを7以前のJavaと、Scalaでそれぞれ記述します。
- ドメインモデルにおける文脈の表現
- エンティティの複数の集合から、異なる複数のエンティティの集合に移すようなドメインロジックの表現
- 文脈を持ったエンティティの集合に対して、~して~して~するというようなビジネスロジックの表現
- ~して~することを~とする事と言う、というビジネスロジックにおけるルールの表現
主にこの4つが、関数型プログラミングをDDDに適用する際に非常に役立つ機能です。
ドメインモデルにおける文脈の表現
例えばドメインモデルにおいて、
- ユーザーは、くじびきを引くと、プレゼント取得したりしなかったりする
というようなルールがあった場合。
Javaだとおそらく以下のような実装になります。
public interface User {
public LotResult lot();
public class Present {}
public interface LotResult {
public Present present();
}
public class Hit implements LotResult {
public Present present() { return new Present();}
}
public class Blank implements LotResult {
public Present present() { return null;}
}
}
このようにJavaである場合、オブジェクトで文脈を表現しなくてはいけません。
Scalaだとこうなります。
trait User {
def lot:Option[Present]
}
class Present
Scalaには、あるかもしれないし、ないかもしれないという文脈を表すOptionがあるためです。
このJavaの例とScalaの例を比べて、Scalaで表現しているほうが明らかに、わかりやすい事がわかります。
Javaでもnullを返せばいいじゃん、という反論があるかもしれませんが、
nullが帰ったときに何を表すのかはコードでは表現されていません。
もしかしたら、例外が起こった時にnullを返す仕様かもしれません。
それをコードで表現せずにドキュメントやコメントだけに書いては本末転倒です。
なおこのような文脈を表す例として、他にもEitherとFutureもあります。
Eitherの例をあげるならば、
- 学生が試験を受けると、合格した学部、または、不合格理由の情報を受け取る
といったものの表現ができます。おそらくインターフェースとしては
trait Student {
def tryTest:Either[FailReason, Department]
}
のようになるでしょう。文脈を表すことができる様々なクラスがScalaに存在しているおかげで
非常に簡潔にビジネスロジックを表現できていることがわかります。
なお具体的なEitherの用法については、Optionで書いたものをEitherでリファクタリングする例などを書いたので参考にしてもらえると助かります。
エンティティの複数の集合から、異なる複数のエンティティの集合に移すようなドメインロジックの表現
今回のドメインロジックの例は、
- ユーザーは、管理者の誰かによってBANされて、BANされたユーザーとなり、BANされたユーザーに対するメールが作成される
のようなものです。こういう処理は非常に関数型のほうが簡潔に表現できます。
trait BanExecutor {
def ban(user:User, administrator:Administrator): (BanedUser, BanMail)
}
上記のようなサービスが作られることでしょう。
関数型プログラミングは、DDDにおける複数のエンティティ間にかかわるサービスが表現しやすくなっています。
これを逆にオブジェクト指向的にエンティティのオブジェクトの振る舞いとして表現すると以下のようになります。
interface Administrator {
public BanResult ban(user:User);
}
interface BanResult
仮にこのように書くと、管理者クラスがなぜかユーザークラスを知ってしまったりするような変な依存関係が生まれてしまったり、BanResult
のような、企画者や開発者の中で会話に出てこなかったような言葉を無理して型として作らなくてはいけなくなってしまいます。
これではコードとドメインモデルの間に乖離が生まれてしまいます。
このようにして使うと関数は、ドメインにおけるサービスと非常に相性の良いものだと感じています。
文脈を持ったエンティティに対して、~して~して~するというようなビジネスロジックの表現
これは、今はやりのストリームを扱う考え方です。どのようなドメインロジックに活躍するかというと、
- サイト訪問者が、サインアップし、都道府県が都内だった時に、イベントの招待状作成に試みて作成可能な場合は、招待状を作る
というようなものです。このビジネスロジックをScalaで表現すると。
val invitations:Seq[Invitaion] = visitors
.filter(_.signUped)
.filter(_.prefecture == "東京都")
.map(createInvitaion)
このような表現になります。visitorsの方はSeq[Visitor]
ですが、見込みユーザーとしてSeq[Future[User]]
というような型でも表現できます。仮にこれを手続き型的な手法で、3回ループを回して一時変数を利用して表現するとここまで簡潔には書けないでしょう。
~して~することを~とする事と言う、というビジネスロジックにおけるルールの表現
このようなビジネスロジックこそ、合成可能な関数の腕の見せ所です。
- ユーザーが商品を購入し、その購入した商品の権利を他人に移動させることを、プレゼントすると定義する
以上のようなビジネスルールがあったとします。Scalaのコードで書くと以下のようになります。
trait User {
def present(item:Item, targetUser:User): Unit = {
moveOwnership(buy(item), targetUser)
}
}
プレゼントとは、商品を購入してその得られた所有物の権利を他人に移動することだということが、コード上から簡潔にわかりやすく伝わってきます。このような処理も手続き的にUserが状態をもって、それらに対して様々な処理をした場合には、そのルールが読み取りにくいものとなってしまうでしょう。
最後に
以上が簡単にわかる、DDDにおける関数型プログラミングの要素を利用して、コードとドメインモデルとの距離を近づけるための機能の紹介でした。
他にも自分は、関数型プログラミングのテクニックは、DDDにおける技術的手法の一つである、
ドメインイベントのやりとりや、それを非同期で並列に処理するというようなイベントソーシングという技術とも非常に相性が良いと感じています。
これらについてまたエントリーを書く機会があれば書きたいと思っています。
以上、技術ポエムでした。