Shin x Blog

PHPをメインにWebシステムを開発してます。Webシステム開発チームの技術サポートも行っています。

関数型言語で DDD - Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F#

Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F#

オブジェクト指向言語でドメインモデルを実装することが当然のように行われていますが、Go で開発したり、Haskell で遊んだりしている中で、他のパラダイムの言語で実装するのはどうなんだろうかという想いがありました。

そんな時に出会ったのが、Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F# という本です。

概要

本書は、とある会社の受注とその関連業務をドメインとし、モデリングして、実装していくという内容です。紙ベースで行われている業務を Web システムにしていきます。

オブジェクト指向言語を対象とした書籍は、エリック・エヴァンスの DDD 本をはじめ、いくつかありますが、この本では、関数型言語である F# で実装するのが特徴です。関数型言語で実装するということは、モデリングにも影響を及ぼすことになります。関数型言語で、どのようにドメインをモデリングし、現実的なコードに落としていくのかというというのが読む前の関心でした。

なお、サンプルコードなどは、F# で実装はされていますが、固有の記述は少ないですし、F# 特有の機能はちゃんと解説があるので、特に問題無く読めました。

構成

書籍は、「Understanding the Domain」「Modeling the Domain」「Implementing the Model」の 3 部で構成されています。全体を通じて、同じ業務プロセス(主に受注業務)について、ドメインの理解、モデリング、実装と進んでいくので、DDD を実践する流れが掴みやすいです。個人的には、受注業務というイメージしやすいドメインが対象というのも良かったです。

3 部の最後にある「Evolving a Design and Keeping It Clean」は、これまでのデザインや実装をベースに仕様変更に対して、どのように対応していくかという内容なので、全てのパートに関連するところなので、第 4 部かなという気がしますが、ボリューム的に 3 部におさめたように感じました。

1. Understanding the Domain
  * Introducing Domain-Driven Design
  * Understanding the Domain 
  * A Functional Architecture
2. Modeling the Domain
  * Understanding Types 
  * Domain Modeling with Types 
  * Integrity and Consistency in the Domain
  * Modeling Workflows as Pipelines 
3. Implementing the Model
  * Understanding Functions 
  * Implementation: Composing a Pipeline 
  * Implementation: Working with Errors
  * Serialization 
  * Persistence
  * Evolving a Design and Keeping It Clean

ドメインを理解し、モデリングする

読む前は、関数型言語による DDD 実装を示すといった内容をイメージしていたのですが、タイトルのとおり、いかにドメインを理解して、モデリングしていくという内容が半分以上占めています。

はじめに、Event Storming やインタビューから要求を聞き出して、対象ドメインの発見、そして理解を深めていきます。問題領域にあるドメインをサブドメインに分割し、解決領域に境界づけられたコンテキストとして定義します。さらにコンテキスト間の関係をコンテキストマップで示します。

これで全体像を把握した後に、対象業務のワークフローを題材に必要なドメインを F# の型としてモデリングを進めていきます。各要素の型を組み合わせて、ワークフローのサブステップを関数とし、さらに関数を合成してワークフローを組み上げていきます。

これらの流れが一気通貫で解説されており、対象ドメインがイメージしやすく、また小さな規模の話ということもあって、関数型言語云々は関係無く、とても分かりやすかったです。

端的なフレーズ

DDD や関数型言語に関する知見を短い文で端的に示している箇所がいくつかあり、内容を理解したり、考えを整理する上でとても役立ちました。記憶にも残りやすくて良いですね。

例えば、下記のような感じです。

  • 共有モデルを作るガイドライン。
    • データ構造ではなく、ビジネスイベントやワークフローに注目する。
    • 問題領域では、ドメインを小さなサブドメインに分割する。
    • 解決領域では、サブドメイン毎にモデルを作る。
    • プロジェクトに関わる全ての人で共有され、コードでも利用される共通の言語(ユビキタス言語)を作る。
  • 問題を理解することは、安易に解決策を作ることではない。
  • まず、ビジネスイベントに注目する。
  • persistence ignorance*1
  • make illegal states unrepresentable

Database-Drive-Design や Class-Driven-Design との違い

Domain-Driven 以外の設計方法として、Database-Driven や Class-Driven な方法との違いについても書かれています。データベーステーブルから設計しないということは良く言われているとは思いますが、Class-Drivenについては、やや例示されている内容が恣意的な面もあり、まあそういう見方もあるかなという程度でした。

いずれにしても、ドメインを理解するフェーズにおいては、システムに関する技術要素を入れないようにするという点は共通のものですね。

型、型、型

対象ドメインについて、とにかく型で表現していきます。代数的データ型の表現力のおかげで、簡潔な記述でドメインが型として表現でき、コンパイラでチェックができるのは良いですね。この簡単に型が書けるというのは、数多くの型を作る上で一つのポイントだと思います。*2

対象ドメインの受注業務では、注文の状態が「未処理」「未検証」「検証済」「価格算出済 or 無効」といった具合に遷移していきます。状態の管理ということで、ステートマシンの話になるのですが、ここでは、それぞれの状態を別の型にして、これを Choise Type にしています。

type Order = 
  | Unvalidated of UnvalidatedOrder
  | Validated of ValidatedOrder
  | Priced of PricedOrder
  // etc

型を分けておけば、状態によって必要なフィールドが異なっていても、それぞれの型で定義すれば良く、無駄なフィールドを持つ必要が無くなります。このような手法を make illegal states unrepresentable*3 と呼ぶようです。

この値を利用する関数では、パターンマッチによって状態毎の処理を記述します。OOP なら、State パターンで表現できそうですが、この場合、State クラス(とその派生クラス)側で実装を持つ形になります。このあたりは、FP と OOP で表現が異なるので面白いところですね。

こうした箇所はいくつもあり、例えばメールアドレスが未検証か検証済かなども別の型で定義して、Choice Type でまとめられています。

とにかく型で表現していくので、「型を大量に作るけど、本当に必要?」という読者の懸念にも触れられていて、必要に応じて減らして下さいとも書かれています。このあたりは現場での導入も考慮されているなと感じました。

関数型言語による実用例

ワークフローを実装する箇所では、必要なサブステップを関数にして、それを合成していきます。合成するにあたって、関数の出力と次の関数の入力の型が合わない箇所への対応についても解説されています。アダプタ関数やモナドを使って、とにかく合成していくというのは関数型らしいです。

また、部分適用による DI も興味深かったです。関数を元に新しい関数を作って、というのは理解はしていたのですが、永続化処理を行う関数をトップレベルで部分適用して、内包した関数をワークフローに渡すというのは、実用例として分かりやすかったです。OOP でいうと、コンストラクタインジェクションで IO 操作を与えておけば、生成されたインスタンスを使う側はそれを意識せずに使えるというのと同じですね。

なお、本書では、ドメインに関する実装だけではなく、データベースアクセス処理や外部システムとの連携(DTO とドメインモデルの相互変換)についても解説されており、関数型言語をビジネスアプリケーションで利用する上での実例として参考になりました。

恐怖のモナド

エラーハンドリングの章では、モナドを活用にしたモナディック関数の合成についても触れられています。the scary-sounding monad と表現されていて、やっぱり、そういう扱いなんですね ;)

本文では、モナドに関する解説無しに実際のコードに適用した後に、モナドについての説明が入ります。下記の文は、端的に表現されていてイメージしやすいです。

A monad is just a programming pattern that allows you to chain “monadic” functions together in series. OK, then what’s a “monadic” function? It’s a function that takes a “normal” value and returns some kind of “enhanced” value. In the error-handling approach developed in this chapter, the “enhanced” value is something wrapped in the Result type, so a monadic function is exactly the kind of Result-generating “switch” functions that we’ve been working with.

さいごに

DDD と関数型言語による身近な題材による実装テクニックが平易な解説で学べるという、一粒で二度美味しい良い本でした。ドメインの定義やモデリングについては、これまで読んだ本の中で一番イメージしやすかったです。

この本で、日頃自分が開発している領域についても関数型言語を利用するイメージができました。OOP による DDD の情報が多いですが、もしかすると関数型言語の方が平易に DDD を実装できるのはないかという印象すら持っています。

関数型言語に興味が無い人も、DDD による設計と型による表現は学ぶべきものがあると思うので、おすすめです。

Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F#

Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F#

下記なら、eBook が DRM フリーです。 pragprog.com

参考

*1:blessed ignorance から来てる表現?

*2:PHP でも、代数的データ型とパターンマッチが欲しい。。。

*3:https://blog.janestreet.com/effective-ml-revisited/