永続化を端に追いやる
LT;DR
理想としてはすべての関数を「純粋」にしたいが、外部とデータのやり取りをする処理は「純粋」でない
「純粋」でないと、テストしづらいなどの問題が生じる
https://scrapbox.io/files/6690960a4edb40001c7d7b30.png
純粋なコードの途中で I/O が必要になった場合、小さなワークフローに分割できないか検討する
hr.icon
請求書を支払うロジックの例
処理フロー
1. DB から請求書をロード
2. 支払いをする
3.
完全に支払いに成功した場合
DB で「完全に支払われた」ことをマークし、「請求書が支払われた」イベントを作成する
それ以外
DB で「部分的に支払われた」ことをマークし、イベントは作成しない
code:fsharp
let paymentInvoice invoiceId payment =
// 1.
let invoice = loadInvoiceFromDatabase invoiceId
// 2.
invoice.ApplyPayment payment
// 3.
if invoice.IsFullyPaid then
markAsFullyPaidInDb invoiceId
postInvoicePaidEvent invoiceId
else
markAsPartiallyPaidInDb invoiceId
問題点
純粋でないため、テストするのが難しい
改善案
1. 純粋なビジネスロジックを関数に切り出す
code:fsharp
let applyPaymentToInvoice unpaidInvoice payment : InvoicePaymentResult =
let updatedInvoice = unpaidInvoice |> applyPayment payment
if isFullyPaid updatedInvoice then
FullyPaid
else
PartiallyPaid
テストが簡単に
このとき、テストができるように I/O 処理を行う関数はすべて 依存関係としてパラメータで受け取るように する
code:fsharp
type PayInvoiceCommand =
{ InvoiceId: ...
Payment: ... }
let paymentInvoice
paymentInvoiceCommand =
// DB から読み込む (I/O)
let invoiceId = paymentInvoiceCommand.InvoiceId
let unpaidInvoice =
loadUnpaidInvoiceFromDatabase invoiceId
// 純粋関数の呼び出し
let payment = paymentInvoiceCommand.Payment
let paymentResult = applyPaymentToInvoice unpaidInvoice payment
// 結果を処理 (I/O)
match paymentResult with
| FullyPaid -> markAsFullyPaidInDb invoiceId
| PartiallyPaid -> markAsPartiallyPaidInDb invoiceId
永続化ロジックは単純なので、この関数をテストする必要は無い
必要なら 3. へ
統合テストでカバーしたほうが良い
I/O が端に、ビジネスロジックが中心に来る構造になっている
https://scrapbox.io/files/66b585b279cd47001d8158fa.png
3. (任意: テストがしたい場合)I/O 処理を行う関数をすべて依存関係としてパラメータで受け取るようにする
code:fsharp
let paymentInvoice
loadUnpaidInvoiceFromDatabase // 依存関係
markAsFullyPaidInDb // 依存関係
markAsPartiallyPaidInDb // 依存関係
paymentInvoiceCommand = // コマンド
クエリに基づく意思決定
「純粋」なコードの途中で、データベースからの読み込みに基づいて処理を分岐したいケースもある
この場合、不純な I/O 関数の間に挟み込む選択が取ることもできる
https://scrapbox.io/files/66b58756091b99001cf3c3a2.png
リポジトリパターンは?
第 6 章 に記載 radish-miyazaki.icon
理由
リポジトリパターンは、ミュータブルを前提とした OOP において、永続化 を隠蔽するためのパターン 必要な関数だけをパラメータで受け取って使用できるので、保守しやすい