『Domain Modeling Made Functional』を観たので動画評を書いてみました。Microsoft テクノロジを使用する中で、いつかは F# を学びたいと思っていたのですが、この動画を観てから本気で F# を学び始めることになりました。
この動画では、下記のコードについて「この設計にいくつの問題があるだろうか?」と問います。そして、ドメイン駆動設計 (domain driven development, DDD) と関数型プログラミング (functional programming, FP) の観点から修正していきます。
type Contact =
{ FirstName: string
MiddleInitial: string
LastName: string
EmailAddress: string
IsEmailVerified: bool }
なお、このコードにある問題として下記の4つが挙げられています。
- どの値がオプションなのか?
- どのような制約があるのか?
- どのフィールドが関連しているのか?
- ドメインロジックは?
ドメイン駆動設計への理解があると、この問題の意味するところを理解しやすいと思います。
関数型プログラミングの神秘性をなくす
関数型プログラミングへの障壁を取り除くところから始まります。関数型プログラミングの専門用語は「怖い」ものではなく「よく知らない」ものであると説明しています。
関数型プログラミングの専門用語を換言するところと、オブジェクト指向プログラミング (object oriented programming, OOP) の専門用語も同様に「怖い」ように見えることを示しているところは絶妙でした。しかしながら、『Adaptive Code』を読了できるくらいのオブジェクト指向プログラミングの知識がないとどちらも「怖い」になってしまいそうなので要注意です。
現実世界のアプリケーションのための関数型プログラミング
関数型プログラミングは「退屈な業務アプリケーション (boring line of business applications, BLOBAs)」にとても適していることを説明しています。F# は BLOBA 開発に求められる「明快」「敏速」「品質」を満たしているそうです。
この時点では理解が追い付かないところですが、動画の後半を観ていくと納得できるはずです。
ドメイン駆動設計のための F#
ドメイン駆動設計における「境界づけられたコンテキスト」と「ユビキタス言語」を説明しています。例えば、unionize という単語は、ビジネスのコンテキストでは「労働組合化する」に、化学のコンテキストでは「非イオン化する」になります。さらに、トランプゲームの境界づけられたコンテキストとそのユビキタス言語を F# のコードで例示します。
このあたりで F# の表現力に興味を持つことになるかと思います。とはいえ、例はトランプゲームなので、本当に業務アプリケーションをモデリングしやすいかについては懐疑的に感じるかもしれません。
F# の型システムを理解する
代数的データ型 (algebraic data types) の直積型 (product types) と直和型 (sum types) について説明しています。この動画では直和型を選択型 (choice types) と言い換えています。その上で、代数的データ型が業務アプリケーションのモデリングにどのように役立つかの一端をコードで例示しています。また、オブジェクト指向のコードとも比較して優位性を示しています。
オブジェクト指向におけるドメイン駆動設計に理解のある人は、オブジェクト指向のコードから遡って F# のコードを理解し、その F# のコードにおいて代数的データ型が使用されていると考えるといいかもしれません。10分ほどの説明で F# における型システムの有用性を感じられます。
型で設計する
ここから冒頭に示したコードをドメイン駆動設計の観点でリファクタリングしていきます。
オプション値
F# がオプション値を null ではなく、オプション型 (option types) で取り扱えることについて説明しています。
下記のように、Contact 型において MiddleInitial フィールドに option を後置することでオプション値にしています。
type Contact =
{ FirstName: string
MiddleInitial: string option
LastName: string
EmailAddress: string
IsEmailVerified: bool }
option を後置するのは Option<‘T> 型のシンタックスシュガーになります。FSharp.Core/prim-types.fs に ‘T option = Option<‘T> と定義されているので、F# のキーワードというわけではないようです。
Java や Scala でもオプション型が使用できますが、C# では公式に導入されていません。オプション型を使用できるという点でも F# を使用する利点がありますね。
単一選択型
単一選択型 (single choice types) としてケースがひとつだけの判別共用体について説明しています。プリミティブ型をユビキタス言語で表現された型にラッピングし、きちんと制約を付与するために使用します。例えば、Eメールアドレスとして有効な文字列であることを保証している EmailAddress 型や50文字以内の文字列である String50 型などを例示しています。
下記のように、Contact 型においていくつかのフィールドを専用の型にしています。
type Contact =
{ FirstName: String50
MiddleInitial: String1 option
LastName: String50
EmailAddress: EmailAddress
IsEmailVerified: bool }
ドメイン駆動設計でいうところの値オブジェクトになります。F# だと値オブジェクトの定義が簡潔にできることは大きな利点ですね。
関連するフィールドの分離
下記のように、Contact 型において関連するフィールドをまとめて専用の型にしています。
type PersonalName =
{ FirstName: String50
MiddleInitial: String1 option
LastName: String50 }
type EmailContactInfo =
{ EmailAddress: EmailAddress
IsEmailVerified: bool }
type Contact =
{ Name: PersonalName
Email: EmailContactInfo }
『ThoughtWorks アンソロジー』にあるオブジェクト指向エクササイズを思い出しました。OOP でも FP でも大事なポイントは類似しているように感じます。
ドメインロジックをコードにする
EmailContactInfo 型の IsEmailVerified フィールドが暗黙的に示しているドメインロジックをコードにすることについて説明しています。例えば、Eメールアドレスが変更された場合、IsEmailVerified フィールドは false にされる必要があります。また、IsEmailVerified フィールドを true にすることは、Eメールアドレス検証サービスによって行われる必要があります。
これを下記のように、オプション型や選択型を使用して表現しています。C# でも真偽型を列挙型に置き換えることがありますが、F# だと判別共用体で簡潔に表現できます。
type VerifiedEmail = VerifiedEmail of EmailAddress
type VerificationService =
(EmailAddress * VerificationHash) -> VerifiedEmail option
type EmailContactInfo =
| Unverified of EmailAddress
| Verified of VerifiedEmail
GitHub にあるコードを含めて補完すると、最終的なコードは下記のようになります。型の定義だけではありますが、ユビキタス言語によって適切にモデリングできていると感じます。
type String1 = String1 of string
type String50 = String50 of string
type PersonalName =
{ FirstName: String50
MiddleInitial: String1 option
LastName: String50 }
type EmailAddress = EmailAddress of string
type VerifiedEmail = VerifiedEmail of EmailAddress
type VerificationHash = VerificationHash of string
type VerificationService =
(EmailAddress * VerificationHash) -> VerifiedEmail option
type EmailContactInfo =
| Unverified of EmailAddress
| Verified of VerifiedEmail
type Contact =
{ Name: PersonalName
Email: EmailContactInfo }
『Domain Modeling Made Functional』は .NET エンジニアとして F# を学習すべきと思わされる動画でした。.NET 言語においては C# が標準言語の地位を占めていますが、同じ .NET 言語である F# の強力さには舌を巻きました。C# との相互運用が可能であれば、ドメイン層を F# で記述し、インフラストラクチャ層を C# で記述するということができるのではないかと思っているので、今後の実務における活用を目指して追加調査するつもりです。