英会話セッションの予約システムのワークフローのモデリングをやってみる

## はじめに
　[Domain Modeling Made Functional](https://www.amazon.co.jp/-/en/Scott-Wlaschin/dp/1680502549)の日本語訳版である[関数型ドメインモデリング](https://tatsu-zine.com/books/domain-modeling-made-functional)が今年の夏に発売されました。日本語訳版が発売開始された時期に、知り合いのエンジニアから誘われて輪読会を週一で実施することになり、ようやく90%くらい読み切ったので、本記事では具体的な題材を取り上げてワークフローのモデリングを実践してみたいと思います。

## 題材
　業務フローについてある程度知っているものが良いと思い、私が個人的に大変お世話になった大学内の英会話セッションの予約確定までのフローを取り上げたいと思います。具体的には以下のフローです。

※下記シーケンス図に登場する`SA(Student Assistant)`は、英会話の相手をしてくれる人です。自分が在学中の時は、2割ほどが日本人、残りは外国人留学生でした。

```mermaid
sequenceDiagram
    actor s as 学生
    actor g as 事務局

    activate s
    s->>s: fill a booking form
    Note Over s,s: Student Code, SA, Language, time, place etc
    s->>g: submit booking
    activate g
    g->>g: check submitted booking

    opt something invalid included
        g-->>s: send email
    end
    
    actor sa as SA
    g->>sa: ask SA to confirm booking details
    
    activate sa
    sa->>sa: check booking details
    sa-->>g: nofify if SA accept the booking or not

    alt SA accept the booking
        g->>s: send a booking confirmation email
        g->>s: schedule the booking on google calendar
        g->>教務課: send notification of booking placed
    else SA decline the booking
        g->>s: send a booking decline email
    end
```

　後半の方に登場する教務課ですが、英会話セッションに予約すると授業の成績に加点される取り決めとなっているため記述しています。教務課の方では、成績付けという別コンテキストのアクションがあると考えられます。
　今回は上記のシーケンス図に記されている事務局の業務(予約確定)のモデリングをしていきます。

## 文書によるモデリング

　まずは文書によるモデリングを行なっていきます。ER図やクラス図ではなく、シンプルなテキストでモデリングをすることで、以下のような利点があります。
- 技術的なインフラストラクチャによって、モデリングが歪められることを防げる
- 技術的な知見が無いドメインエキスパートも一緒にモデリングについて議論ができる

書籍に倣って、以下のように記してみました。


```txt
workflow "Place Booking"
    input: UnvalidatedBooking
    output: (on success):
        BookingConfirmationSent
        AND BookingPlaced
    output: (on error):
        ValidationError

    do ValidateBooking
    if booking is invalid then:
        return with ValiationError

    do CheckBooking
    if booking is not acceptable
        return UnacceptableBookingError

    do SendBookingConfirmationToStudent

    return the events
```

次に各サブステップの詳細をテキストで表現していきます。
入力と出力に加えて、サブステップの依存関係も明示します。

```txt
substep: "ValidateBooking"
    input: UnvalidatedBooking
    output: ValidatedBooking OR ValidationError
    dependencies: CheckSudentCodeExists, CheckBookingLimitExceeds

    check the student code syntax
    check that student code exists in CollegeSystem

    if everything is OK, then:
        return ValidatedBooking
    else:
        return ValidationError
```

```txt
substep: "CheckBookingDetails"
    input: ValidatedBooking
    output: AcceptedBooking OR UnacceptableBookingError

    ask SA that the booking is acceptable or not

    if booking is accepted, then:
        return AcceptedBooking
    else:
        return UnacceptableBookingError
```

```txt
substep: "SendBookingConfirmationToStudent"
    input: AcceptedBooking
    output: None

    send booking confirmation email to the student
```

一見するとGithub Actionsのworkflowのyamlファイルに似ている気がしますが、特定の技術に依存しない自然言語で記されているので、ドメインエキスパートも確認することが可能です。

### 型によるモデリング

文書によるモデリングを実施した後は、静的型付け言語の型システムを利用したモデリングを行なっていきます。
今回は書籍と同じくF#を用いて型を記述していきます。

### 入力
　まずはワークフローの入力部分であるコマンドについて見ていきます。
今回は単一のワークフローしか扱いませんが、別のワークフローが増えた場合のことを考慮して、ジェネリクスを用いて汎用的なCommand型を定義し、それを利用します。必要に応じてCommandに持たせるフィールドは増減させても良いかもしれません。

```fsharp
type UnvalidatedBooking = {
    BookingId: BookingId
    StudentInfo: UnvalidatedStudent
    BookingDetails: BookingDetails
}
and UnvalidatedStudent = {
    StudentCode: string
    Name: string
    Email: string
}

type Command<'data> = {
 Data: 'data
 Timestamp: DateTime
 UserId: string
}

type PlaceBookingCommand = Command<UnvalidatedBooking>
```

### 出力

 　次にワークフローの出力部分を見ていきます。ワークフローはドメインイベントを返し、そのドメインイベントが別のコマンドをトリガーし、別のワークフローを開始され、...といった具合に続いていきます。
 　成功時の出力と失敗時の出力をそれぞれ直積型と直和型で定義しています。

 ```fsharp
 // 予約確定ワークフローの成功出力
type BookingConfirmationSent = {
    BookingId: BookingId
    EmailAddress: EmailAddress
}

type AcceptedBooking = { //SAによる確認完了時に生成される予約状態
...
}

type BookingPlaced = AcceptedBooking 

type PlaceBookingEvents {
     BookingConfirmationSent: BookingConfirmationSent
     AcceptedBooking: AcceptedBooking
}

type PlaceBookingError = 
| ValidationError
| UnacceptableBookingError
 ```

### トップレベル
 　最後に予約確定ワークフローのトップレベルの関数を定義します。
前述のモデリングで見てきた通り、予約の検証やSAによる予約内容のチェックの際にエラーの発生する可能性があります。そこで、このワークフローがエラーエフェクトを持つ可能性があることを型シグネチャから分かるようにしたいため[Result](https://learn.microsoft.com/ja-jp/dotnet/fsharp/language-reference/results)を用いています。またプロセスが非同期であることを型シグネチャで示すために、`AsyncResult`というエイリアスを定義します。
　こうすることで、関数の実装の詳細を覗かずとも、関数の型シグネチャからどのような出力が得られるのかが分かります。

 ```fsharp
type AsyncResult<'success, 'failures> = Async<Result<'success, 'failures>>

type PlaceBookingWorkflow = 
    PlaceBookingCommand -> AsyncResult<PlaceBookingEvents, PlaceBookingError>
 ```

### おわりに
　以上が英会話セッションの予約システムのワークフローのモデリングです。各サブステップの実装では、カリー化や部分適用、モナドなどの関数型プログラミングのテクニックを使って実現していきますが、後続記事に譲りたいと思います。今回はF#で型を記述していきましたが、今後はKotlinやTypeScriptなど、私が業務でよく用いる言語ではどのように記述していけば良いかについても探究していきたいと思います。