ROPとBOP——変換から存在へ
Unixはパイプを与えた。関数型プログラミングはResultを与えた。ドメインにはどちらも要らないとしたら?
パイプライン
Railway Oriented Programmingの核心は単純です。ワークフローはパイプライン。失敗はもう一本のレール。
Input → validate → extract → identify → save
↓ ↓ ↓ ↓
Error Error Error Error
cat | grep | sort がテキストを流すように、ROPはドメインデータを関数の連鎖に流す。エラーハンドリングを型システムで明示しながら。
TypeScriptでの実装例:
const workflow: WorkFlow = (command) =>
ok(command)
.andThen(validateReservationEvent)
.andThen(extractActualVisitor)
.asyncAndThen(identifyCustomer(findIdenticalCustomer))
.andThen(importReservationEvent)
各段階は固有の型を持ちます:
UnvalidatedCommand
→ SiteReservationValidated
→ VisitorExtracted
→ CustomerIdentified
→ SiteReservationEventImported
関数型の関心事はデータの変換。ROPはその変換に失敗の文脈を与えた。Result型でエラーを明示し、Union型で不可能な状態を排除し、イミュータブルで破壊を防ぐ。
共鳴
Be Frameworkの連鎖を横に置いてみます。
OrderInput → OrderValidated → PaymentProcessed → OrderConfirmed
Union型で存在しうるものだけを制約する。イミュータブル。各遷移が明示的。異なる出発点から、似た構造に辿り着いている。
主語
似ているのは構造。違うのは文法。
// ROP
const archived = archiveCustomer(customer)
// BOP
$becoming(new PatientArrival($vitals))
ROPでは extractActualVisitor(command)。BOPでは PatientArrival がトリアージプロトコルと出会い、EmergencyCase になる。
ROPのワークフローは総譜を持つ指揮者。BOPの各存在は #[Be] を宣言するだけ。全体を知る者はいない。
問い
ROPが問うのは:この変換は成功したか、失敗したか?
エラーをどう扱うか。失敗をどう伝播させるか。失敗しうる関数をどう合成するか。HOWの問い。
BOPが問うのは:そもそもこれは存在できるか?
Email はバリデーションされて Result<Email, Error> を返すのではない。存在するか、しないか。トラックがないのではなく、トラックという概念がない。意味変数は、不正な値がそもそも存在できない世界を定義する。WHETHERの問い。
この問いの転換から、自己証明も、自己決定も、コレオグラフィーも導出される。
積み重なる
ROPが解いた問題は実在する。
BOPはその問題を解かない。問いの場所を変える。
両者は同じ風景を異なる高さから見ている。一方からはレールとポイントが見える。もう一方からは、列車がレールと分かれていなかったことが見える。
- Railway Oriented Programming — Scott Wlaschin
- TypeScript 関数型スタイルでバックエンド開発のリアル — 伊藤直也 (TSKaigi 2024)