Be Framework Blog

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はその問題を解かない。問いの場所を変える。

両者は同じ風景を異なる高さから見ている。一方からはレールとポイントが見える。もう一方からは、列車がレールと分かれていなかったことが見える。