2026年8月31日月曜日

Cozyモデル駆動開発/ユースケース/ensures

本稿では、ユースケース間の関係の一つである ensures(保証関係)を取り上げます。

ensuresは、

  • あるユースケースの完了時に成立していなければならない状態を明示する
  • その状態を成立させるユースケースを関連付ける
  • ユースケースの結果を契約として表現する

ための関係です。

前回取り上げたrequiresが、あるユースケースを開始する前に必要な状態を表すのに対し、ensuresは、ユースケースが正常に完了した時に何が成立していることを保証するかを表します。

ensures

  • マージモード : Append
  • マージ点 : After end
  • 機能 : 事後条件統合
  • 由来 :
    • Requirements Engineeringにおけるpostcondition
    • Design by Contract
    • Goal satisfaction
    • Workflow outcome

ensuresの基本方針

ensuresは、

ユースケースの完了契約を、その結果を成立させる別のユースケースへ接続する関係

です。

UseCase A が UseCase B を ensures する場合、Aの正常終了後に、Bが成立させる状態または保証が、Aの完了契約の一部として成立している必要があることを表します。

例えば、注文を確定した後に、

  • 注文番号が発行されている
  • 注文確定の記録が保存されている
  • 顧客が注文内容を確認できる

必要があるとします。

このとき「注文確定結果を記録する」を独立したユースケースとして定義し、「注文を確定する」がそれをensuresする関係として記述できます。

重要なのは、ensuresが単に

Aの直後にBを呼び出す

という手続き的な関係ではないことです。

ensuresが表すのは、

Aが正常に完了したとみなす時点で、Bが保証する結果も成立している必要がある

という契約上の保証です。

結果をA自身が成立させるのか、Bへ委譲するのか、保存済みの状態を確認するのかは、実行設計で決めます。

マージモードのAppendは、AとBから合成シナリオを構成する場合に、BをAの後へ配置する基本戦略を表します。ただし、ensuresの契約上の意味と、単純にBを逐次実行するという実現方式は同一ではありません。

シナリオ技術としてのensures

includeやextendはユースケース内部のフロー構造を扱います。

precedesはユースケース間の時間順序を扱い、triggersは出来事に対する反応を扱います。

ensuresは、これらとは異なり、

あるユースケースが完了したと判断できる結果

を扱います。

このためensuresは、ユースケースを単に後ろへ連結するのではなく、

  • どの状態を保証するか
  • その状態を誰が成立させるか
  • 保証できない場合に完了をどう扱うか

を明確にするために使用します。

意味定義

UseCase A が UseCase B を ensures するとは、

Aの完了に必要な結果の一部または全部を、Bの結果が成立させる

ことを意味します。

基本的な流れは次のようになります。

  • Aの本体処理を実行する
  • Aが要求する保証に対応するBを必要に応じて実行する
  • BのPostを確認する
  • Bの結果がAの必要な事後条件を満たしたことを確認する
  • すべての必要な保証が成立した時点でAを正常終了とする

契約としては、次の関係を期待します。

Post(B) => EnsuredPost(A)

ここでEnsuredPost(A)は、Aのすべての事後条件ではなく、Bとのensures関係によって満たされる部分を表します。

直観的整理

  • ensuresは「これが終わったなら、この結果まで成立している」という関係
  • 対象は実行順序そのものではなく、正常終了時の保証
  • 結果を要求する側と、その結果を提供する側を接続する
  • 必要な結果が成立しなければ、対象ユースケースを正常終了にしない

契約との関係

ensuresでは、後続処理の順序よりも、

  • 保証を利用する側のPost
  • 結果を提供する側のPost

の対応が重要です。

UseCase A ensures UseCase B の場合、望ましい関係は次の通りです。

  • Aのどの事後条件をBが満たすのかが分かる
  • BのPostが明確に定義されている
  • Bが失敗した場合、Aの完了を成功として扱わない
  • Bの結果をどの時点で確認できるかが分かる

例えば、注文確定と確認メール送信では、注文の確定自体は成功していても、メール送信が一時的に失敗することがあります。

このとき、

  • メール送信までを注文確定の必須保証に含める
  • 通知依頼の登録だけを必須保証にする
  • メール送信はtriggersによる独立した後続処理にする

のどれを選ぶかで、完了契約が変わります。

ensuresを設計する際は、利用者に約束する結果と、内部的に後から実行できる処理を区別する必要があります。

ensuresとrequires

requiresは、前提を利用する側から見た関係です。

ensuresは、結果を保証する側から見た関係です。

両者は次のように対応します。

UseCase A ensures  Condition C
UseCase B requires Condition C

ユースケース間の関係として表現する場合は、

UseCase B requires UseCase A
Post(A) satisfies RequiredPre(B)

と整理できます。

ただし、ensuresを記述しただけで、必ず別のユースケースがその結果を利用するとは限りません。

ensuresは提供側の完了契約、requiresは利用側の開始契約です。両方が対応すると、ユースケース間の契約を提供側と利用側の双方から確認できます。

記述例

Pieris Booksで、注文を確定し、その結果を記録する業務を考えます。

usecase RecordConfirmedOrder
  pre:
    - 注文が確定可能である
  post:
    - 注文番号が発行されている
    - 注文確定の記録が保存されている
usecase ConfirmOrder
  pre:
    - 注文内容が検証されている
    - 支払い方法が承認されている
  post:
    - 注文が確定している
    - 注文番号が発行されている
    - 注文確定の記録が保存されている
usecase ConfirmOrder ensures RecordConfirmedOrder

この例では、

  • ConfirmOrderの正常終了には注文確定結果の保存が必要
  • RecordConfirmedOrderの事後条件がConfirmOrderの完了契約の一部を満たす
  • RecordConfirmedOrderが失敗した場合、ConfirmOrderを正常終了として扱わない

という関係になります。

ConfirmOrderの本体とRecordConfirmedOrderを同じトランザクションで実行するかどうかは、ensuresだけでは決まりません。

同じトランザクションで確実に保存する方式も、永続化された実行要求を介して結果成立を追跡する方式も考えられます。いずれの場合も、どの時点をConfirmOrderの正常終了とするかを契約として明確にします。

requiresとの違い

ensuresは、requiresと対になる関係です。

どちらも契約を扱いますが、契約を評価する時点と向きが異なります。

観点requiresensures
本質開始条件完了保証
マージモードPrependAppend
マージ点Before startAfter end
関係AにはBの保証が必要Aの完了はBの結果を保証する
主な契約Post(B)とRequiredPre(A)Post(B)とEnsuredPost(A)
失敗時Aを開始しないAを正常終了にしない
A requires B

は、Aの開始にBの結果が必要であることを表します。

A ensures B

は、Aが正常に完了した時にBの結果まで成立していることを表します。

したがって、

  • 処理を始められる条件を表すなら requires
  • 処理が終わったといえる条件を表すなら ensures

と考えると分かりやすくなります。

precedesとの違い

precedesは、Aの完了後にBへ進む時間的な順序を表します。

ensuresは、Aの完了時にBの結果が成立しているという保証を表します。

観点precedesensures
本質実行順序完了契約
マージモードSequenceAppend
マージ点After endAfter end
Bの失敗ワークフロー上の後続失敗Aの保証不成立
Aとの結合時間的な連結結果を介した契約
A precedes B

では、Aが成功した後にBが開始されます。Bが失敗しても、Aの結果自体は成立済みとして扱える場合があります。

A ensures B

では、Bが提供する結果はAの完了契約に含まれます。Bの結果が成立しない限り、A全体を成功として扱えません。

同じ「Aの後にBを行う」という実装でも、順序を表しているのか、Aの保証を成立させているのかで意味が異なります。

triggersとの違い

triggersは、イベントの発生によって別のユースケースが起動候補になる関係です。

ensuresは、イベントを発行したことではなく、必要な結果が成立したことを要求します。

ConfirmOrder triggers SendOrderConfirmation

と書くと、注文確定イベントをきっかけに確認通知の処理が起動する意味になります。

ConfirmOrder ensures RegisterNotificationRequest

と書くと、ConfirmOrderが正常に完了した時点で、通知要求が確実に登録されていることを意味します。

前者は起動の契機、後者は完了時の保証を表します。

非同期処理を使用する場合は、外部処理そのものの完了ではなく、

  • イベントが永続化されている
  • 実行要求が確実に登録されている
  • 再試行可能な状態になっている

ことをensuresの対象とする設計も有効です。

includeとの違い

includeは、別のユースケースのフローを対象ユースケースの内部へ組み込みます。

ensuresは、対象ユースケースの完了時に必要な結果を表します。

観点includeensures
対象内部フロー完了時の契約
関係動作を組み込む結果を保証する
実行原則として含まれる結果の成立方法は実行設計による
結合フロー構造として強い契約を介した依存

例えば、注文番号発行の手順を注文確定フローの一部として常に組み込みたい場合はincludeとして設計できます。

一方、手順の実装場所にかかわらず「注文番号が発行されていること」を完了条件とする場合はensuresが適しています。

実行時の扱い

ensuresはモデル上の保証関係であり、結果をどのように成立させるかは実行設計で決めます。

代表的な方式は次の三つです。

同一処理型

Aの本体処理と、保証を成立させるBを同じ処理境界で実行します。

  • 同じトランザクションで結果を確定したい
  • Bの失敗時にAの変更も取り消せる
  • 完了時点を一つに定めたい

場合に適しています。

確認型

Aの本体処理後に必要な状態を確認し、成立していなければエラーにします。

  • 結果は別の仕組みによって成立する
  • AからBを直接起動しない
  • 完了境界で契約違反を検出する

場合に適しています。

確実な委譲型

Aの完了前に、Bを後から確実に実行できる要求やイベントを永続化します。

  • 外部サービスとの連携を非同期にしたい
  • 一時的な失敗を再試行したい
  • Aのトランザクションを長時間保持したくない

場合に適しています。

この方式では、

Bの外部効果が完了している

ことではなく、

Bを確実に実行する責務が引き受けられている

ことをAの直接の保証とする場合があります。

どの方式でも、Aを正常終了として外部へ通知する時点で、定義したEnsuredPost(A)が成立している必要があります。

複数のensures

一つのユースケースが複数の結果を保証する場合があります。

usecase ConfirmOrder ensures RecordConfirmedOrder
usecase ConfirmOrder ensures RegisterNotificationRequest

この記述は、

  • 注文確定結果の保存
  • 通知要求の登録

の両方がConfirmOrderの完了に必要であることを表します。

ただし、ensuresだけでは、

  • どちらを先に実行するか
  • 並行して実行できるか
  • 一方の失敗時にどこまで取り消すか

までは決まりません。

保証同士に順序が必要な場合はprecedesなどの関係で明示し、並行実行や補償は実行設計で扱います。

失敗と部分的な成功

ensuresを使用する場合、保証を成立させる処理が失敗した時の扱いを決めておく必要があります。

特に、Aの本体処理が取り消せないのに、Bの保証だけが失敗する場合があります。

例えば、注文は保存されたが、外部の配送サービスへの登録が失敗した場合です。

このような場合は、

  • Aを失敗として返し、再実行可能にする
  • 補償ユースケースでAの結果を取り消す
  • Bを再試行待ちにし、その状態をAの結果として明示する
  • Aの保証を「配送登録」ではなく「配送登録要求の永続化」に狭める

といった設計が考えられます。

重要なのは、実際には成立していない結果を、ensuresとして成立したことにしないことです。

部分的な成功を許す場合は、その状態自体を明示的な結果としてモデル化します。

同期実行と非同期実行

ensuresの結果は、対象ユースケースの正常終了時点で成立している必要があります。

しかし、関連する外部処理のすべてが同期的に完了している必要はありません。

例えば、注文確定後のメール送信を非同期で行う場合、ConfirmOrderが保証する内容を、

- メールが送信されている

ではなく、

- メール送信要求が永続化されている
- 送信処理を再試行できる

と定義できます。

その後のメール送信完了は、別のユースケースのPostとして扱います。

ensuresが表す保証の粒度は、同期・非同期の境界と整合させる必要があります。

他の関係との違い

観点includeextendprecedestriggersrequiresensures
対象フロー構造フロー変形実行順序起動条件開始前提完了保証
本質構造合成条件付き拡張シナリオ連結イベント駆動契約依存結果保証
マージ点任意StepAfter指定After endAfter conditionBefore startAfter end
失敗時の意味内部フロー失敗条件付き拡張失敗後続フロー失敗反応処理失敗対象を開始しない対象を正常終了にしない

つまりensuresは、

別のユースケースを単に後続実行する関係ではなく、その結果を自分の完了契約として引き受ける関係

です。

設計上の注意点

ensuresを使用する際は、次の点に注意します。

  • 正常終了時に外部へ約束する結果を明確にすること
  • BのPostとAのEnsuredPostの対応を確認すること
  • 保証が成立しない場合の失敗、再試行、補償を決めること
  • 同期境界と非同期境界を明示すること
  • 実際に保証できない外部効果を事後条件にしないこと
  • 単純な内部処理をすべて独立ユースケースにしないこと

特に、外部サービスへの通知や連携は、相手側の処理完了までを自分のトランザクションで保証できない場合があります。

その場合は、

  • 自分が確実に保証できる状態
  • 外部処理へ委譲した状態
  • 外部処理が完了した状態

を分けてモデル化します。

また、ensuresの対象を増やしすぎると、一つのユースケースの成功が多数の外部処理に依存し、失敗しやすくなります。

ensuresの対象は、

  • 利用者にとって完了の意味を構成する結果
  • 業務上不可欠な事後条件
  • 独立して契約を定義する価値がある保証

を基本とします。

まとめ

ensuresは、

  • ユースケースの正常終了時に必要な結果を表す関係
  • 対象ユースケースのPostと、結果を提供する側のPostを接続する関係
  • 後続処理の呼び出しではなく、契約を介して保証を表現する仕組み

です。

requiresがユースケースを開始できる条件を扱うのに対し、ensuresは、

ユースケースが完了したといえる条件

を扱います。

ensuresを使用することで、

  • 注文確定時の注文番号発行
  • 予約完了時の予約記録
  • 申請受付時の受付番号発行
  • 更新完了時の監査記録

のような結果を、単なる後続処理ではなく完了契約として表現できます。

次回は、複数のユースケースが並行して協力するcollaboratesを取り上げます。

0 件のコメント:

コメントを投稿