TypeScript で簡易な Result 型を定義する

TypeScript で Result 型を使いたいけど、ライブラリ入れるほどではないなーというときに見つけた記事。

dev.classmethod.jp

エラーハンドリングの試行錯誤の結果、最終形として以下の実装が紹介されていた。

type Result<T, E> = Success<T, E> | Failure<T, E>

class Success<T, E> {
  constructor(readonly value: T) {}
  type = 'success' as const // ここを追加
  isSuccess(): this is Success<T, E> {
    return true
  }
  isFailure(): this is Failure<T, E> {
    return false
  }
}

class Failure<T, E> {
  constructor(readonly value: E) {}
  type = 'failure' as const // ここを追加
  isSuccess(): this is Success<T, E> {
    return false
  }
  isFailure(): this is Failure<T, E> {
    return true
  }
}

(引用ブロックにするとシンタックスハイライトが失われるのでコードブロックで引用させていただいてます)

ポイントはリテラル型の type プロパティを定義している点で、これにより Result がタグ付きユニオンとなるためユーザー定義型ガードの isFailure() で型解決できるようになる。

最初は「なるほどなー」と思っていたのだが、ふと「タグ付きユニオンにするならユーザー定義型ガードも不要で、リテラル型のプロパティだけで型解決できるのでは?」という考えが浮かんだので実装してみた。

クラス版

type Result<T, E> = Success<T> | Failure<E>

class Success<T> {
  readonly isSuccess = true
  readonly isFailure = false
  constructor(readonly value: T) {}
}

class Failure<E> {
  readonly isSuccess = false
  readonly isFailure = true
  constructor(readonly value: E) {}
}

オブジェクト版

type Result<T, E> = Success<T> | Failure<E>

type Success<T> = {
  readonly isSuccess: true
  readonly isFailure: false
  readonly value: T
}
type Failure<E> = {
  readonly isSuccess: false
  readonly isFailure: true
  readonly value: E
}

const createSuccess = <T>(value: T): Success<T> => {
  return { isSuccess: true, isFailure: false, value }
}

const createFailure = <E>(value: E): Failure<E> => {
  return { isSuccess: false, isFailure: true, value }
}

オブジェクト版の方は生成時に value 以外のプロパティを書くのが手間なので、生成用の関数を用意した方が便利かなと思って書いている。

どちらも value, isSuccess, isFailure のプロパティだけで構成され、よりシンプルな形になったはず(もっと攻めるならユースケースを早期リターンに限定して isSuccess を削る手もある)。

使用例としてはこんな感じ。

function main() {
  const result: Result<number, string> = doSomething()

  if (result.isFailure) {
    // result: Failure<string> で型推論される
  } else {
    // result: Success<number> で型推論される
  }
}

さらに関数型っぽく Rust の and_then のような実装もしたくなるけど、いったんここまで。

WIP: EventStorming の手順

LDDD 第12章に書かれている EventStorming の手順をまとめる。

amzn.asia

手順

Step 1:Unstructured Exploration

ドメインイベントのブレインストーミングをする。

  • メンバー全員が参加する
  • オレンジの付箋を使用する
  • ドメインイベントを思い付く限り書き出す
  • ドメインイベントなので「名詞 + 動詞の過去形」で書く(たとえば Order shippedNotification sent など)
  • イベントの順番や重複は気にしない
  • 新しいイベントが追加される速度が大幅に遅くなるまで(ほぼ出なくなるまで)書き出し続ける

Step 2: Timelines

ドメインイベントを発生する順番に並べる。

  • (特別な理由がなければ)並び順は左から右
  • まずは正常系(ハッピーパスシナリオ)から始める
  • 次に異常系
  • 正常系と異常系の分岐は矢印などで表現する
  • またここでは間違ったイベントの修正や重複の削除、イベントの追加を行ってもよい

Step 3: Pain Points

タイムラインを見ながら注意が必要な点を特定する。

  • ボトルネック・自動化が必要な手動のステップ・ドキュメントの欠落・ドメイン知識の欠落など
  • ひし形の向きにしたピンクの付箋を使用する
  • このステップだけでなく、後続のステップでも問題や懸念が見つかれば、それを pain point として書き出す

Step 4: Pivotal Events

タイムラインからコンテキストやフェーズの変化を示す重要なドメインイベントを探す。

  • このイベントは重要なイベント (pivotal event) と呼ばれる
  • たとえば「ショッピングカート初期化」や「注文発送」など
  • 重要なイベントは境界づけられたコンテキストを示す潜在的な指標である

Step 5: Commands

イベントやイベントの流れのトリガーになったものをコマンドといい、これを書き出す。

  • コマンドはシステムの動作を説明するもの
  • 命令形で書く(たとえば Roll back transactionSubmit order など)
  • コマンドは水色の付箋を使用する
  • 付箋はそのコマンドによって発生するイベントの前に貼る

コマンドが特定の役割を持つヒト(アクター)によって実行される場合、アクターも書き出す。

  • アクターはドメインにおけるユーザーペルソナを表している
  • アクターは小さな黄色の付箋を使用し、コマンドの左下に少し重なるように貼る
  • すべてのコマンドにアクターがあるわけではないので、明確にアクターがいる場合のみ追加する

Step 6: Policies

アクターのないコマンドに対し、それを実行する可能性がある自動化ポリシーを探す。

  • 自動化ポリシーとはイベントがコマンドの実行するシナリオのこと
  • 自動化ポリシーがあることで、特定のイベントが発生するとコマンドが自動的に実行されることになる
  • 紫色の付箋を使用し、イベントとコマンドを繋ぐように貼る
  • コマンドの実行に条件がある場合、ポリシーの付箋にその条件を書く(条件がなければ何も書かなくてよい)

Step 7: Read Models

アクターがコマンドを実行するか決めるときに使用するデータのビューのことをリードモデルといい、これを書き出す。

  • 例としてシステム画面・レポート・通知など(もっと具体的には「ショッピングカート」や「対応が必須のメール」など)がこれに当たる
  • 緑色の付箋を使用し、アクターの左に貼る

Step 8: External Systems

対象のドメインに含まれないすべてのシステムを外部システムといい、これを追加してモデルを補強する。

  • 外部システムはコマンドの実行やイベントの通知を受けたりできる
  • ピンクの付箋を使用する
  • このステップが終わると、すべてのコマンドはアクターに実行されるか、ポリシーにトリガーされるか、外部システムに呼び出されているはず

Step 9: Aggregates

ここまでですべてのイベントとコマンドが表現されたので、関連する概念を集約ごとに整理する。

  • 集約はコマンド受けてイベントを生成する
  • 大きな黄色の付箋を使用する
  • 付箋が大きい理由は、集約が複数のコマンドを受ける可能性があるため

Step 10: Bounded Contexts

境界づけられたコンテキストを形成するような、互いに関連している集約を探し出す。

  • 機能的に密接に関連していたり、ポリシーを通じて結合されている集約をグループ化する
  • グループの境界は点線で表す
  • (所感)けっこう難しそうなので、知識の整理やガイダンスなどの初期フェーズの場合、ここまで詰めなくてもいい気がする