解決したい問題
例として、飲食店の予約サービスを考える。
予約を受け付けるためには各店舗の営業スケジュールを管理しておいて、営業日の営業時間内のみ予約を受け付けるようにする必要がある。
たとえば、ある店舗は各曜日の営業時間について、以下のように定めているとする。
- 平日:11:30-22:00
- 土曜日:11:00-22:00
- 日曜日:11:00-21:00
- 定休日:木曜日
これを素朴に設計すると、たとえば以下のような「営業日については営業時間を保持し、定休日についてはレコードがない」というテーブルになるかもしれない。
店舗 | 曜日 | 開店時刻 | 閉店時刻 |
---|---|---|---|
佐竹飯店 | 月 | 11:30 | 22:00 |
佐竹飯店 | 火 | 11:30 | 22:00 |
佐竹飯店 | 水 | 11:30 | 22:00 |
佐竹飯店 | 金 | 11:30 | 22:00 |
佐竹飯店 | 土 | 11:00 | 22:00 |
佐竹飯店 | 日 | 11:00 | 21:00 |
現実
素朴に設計した上記のテーブルだが、多くの場合、実際に運用していく中で
- 年末年始・お盆・大型連休などの暦に影響される休業
- 社員研修や店内の改装などによる臨時休業
など、規則通りではない営業スケジュールが必要になってくる。
新たな要求に応えるため、ここでまた素朴に臨時休業テーブルを追加する。
店舗 | 日付 | コメント |
---|---|---|
佐竹飯店 | 2020-10-14 | 臨時休業 |
また、別の要求として、
- 通常であれば木曜日は定休日だが、祝日あるいは休前日の場合は営業する
- 店内清掃や設備更新のための早閉め
といった臨時営業や営業時間変更なども必要になり、臨時営業テーブルを追加する。
店舗 | 日付 | 開店時刻 | 閉店時刻 | コメント |
---|---|---|---|---|
佐竹飯店 | 2020-10-15 | 11:30 | 22:00 | 定休日に臨時営業 |
佐竹飯店 | 2020-10-16 | 11:30 | 16:00 | 早閉め |
このとき、アプリケーションが営業時間を判別するロジックは、
- 臨時営業が設定されていればそれに従う
- 臨時営業が設定されておらず、臨時休業が設定されていればその日を休業にする
- 臨時営業も臨時休業も設定されていない場合はデフォルトの営業時間になる
という風になる。
上記のような設計では営業時間を知るためのロジックが複雑になり、ある店舗のある日の営業状態が簡単にわからない。
また、「ある寿司屋では仕入れの都合により、豊洲市場がやっている日のみ営業する」など、別の規則が入ってきたときにうまく扱うことが難しい。
解決方法
コンセプト
現実のスケジュールが規則に従うというのは幻想なので諦める。
- スケジュールをベタに保持するテーブルをつくり、アプリケーションはそのテーブルのデータに基づいて動作するようにする
- この種のデータを schedule と呼ぶことにする
- 上記の schedule を生成するための規則は別テーブルで保持しておいて、その規則に基づいて schedule を生成する
- この種のデータを scheduling policy と呼ぶことにする
具体例
先ほどからの例で考えると schedule のテーブルは以下のようになる。木曜日は定休日なのでレコードが存在していない。
店舗 | 日付 | 開店時刻 | 閉店時刻 | コメント |
---|---|---|---|---|
佐竹飯店 | 2020-10-14 | 11:30 | 22:00 | 水曜日 |
佐竹飯店 | 2020-10-16 | 11:30 | 22:00 | 金曜日 |
佐竹飯店 | 2020-10-17 | 11:00 | 22:00 | 土曜日 |
佐竹飯店 | 2020-10-18 | 11:00 | 21:00 | 日曜日 |
佐竹飯店 | 2020-10-18 | 11:30 | 22:00 | 月曜日 |
… | … | … | … | 以下同様 |
上記のようなレコードを生成・保持しておいて、アプリケーションはこれらのレコードに基づいて動作する。 また、臨時休業・臨時営業・臨時営業時間変更などが必要な場合は schedule を delete / insert / update すると、アプリケーションは変更後のスケジュールに基づいて動作する。
これらのレコードさえあればアプリケーションは動作する。
しかし、 人間が schedule を都度生成するのは大変だし、スケジュールが規則的に定まるというのもある程度までは正しい。 そこで、デフォルトスケジュールを生成するための規則として scheduling policy を設定して、その scheduling policy に基づいて schedule を生成してあげるようにする。
たとえば、曜日ごとに営業日を設定するのであれば、以下のようになる。
店舗 | 曜日 | 開店時刻 | 閉店時刻 |
---|---|---|---|
佐竹飯店 | 月 | 11:30 | 22:00 |
佐竹飯店 | 火 | 11:30 | 22:00 |
佐竹飯店 | 水 | 11:30 | 22:00 |
佐竹飯店 | 金 | 11:30 | 22:00 |
佐竹飯店 | 土 | 11:00 | 22:00 |
佐竹飯店 | 日 | 11:00 | 21:00 |
典型的な規則を表すデータなので、冒頭で素朴に設計したテーブルと同じになっている。
生成方法については、最終的に schedule が生成されれば何でも良いが、典型的にはバッチで生成する。 たとえば、4週間先まで予約を入れられるサービスだとすると、6週間分など多めに生成しておいて、予約を入れられるようになるまでの2週間の間に修正してもらうようにする。 あるいは、自動生成よりも前に schedule を手動生成できるようにして、すでに schedule が存在する日については自動生成をスキップするなどでも良い。
別の例として挙げた「豊洲市場など特定の市場が開いている日は営業する」という規則が必要であれば「市場カレンダー」のような scheduling policy をつくって店舗が選べるようにすれば良い。
市場 | 日付 | 営業している |
---|---|---|
豊洲市場 | 2020-01-01 | false |
豊洲市場 | 2020-01-02 | false |
豊洲市場 | 2020-01-03 | false |
豊洲市場 | 2020-01-04 | false |
豊洲市場 | 2020-01-05 | true |
豊洲市場 | 2020-01-06 | true |
… | … | … |
豊洲市場 | 2020-12-30 | true |
豊洲市場 | 2020-12-31 | false |
さらに、仮に「素数の日付のみ営業する」というような一般的ではない規則で営業スケジュールを定める店舗があったとしたら、その店舗には scheduling policy を使わず手動で schedule を設定してもらうようコミュニケーションすれば良い。
また、アプリケーションの機能は scheduling policy ではなく schedule に依存しているので、scheduling policy を追加・変更した場合でも schedule を利用する機能の修正はいらない。
おわりに
最近、上記のような設計を使う機会が何度かあり、自分の中で Schedule / Scheduling Policies パターンと名付けて呼んでいたので簡単にまとめておいた。
上記の他にも細かいバリエーションとして、
- 特定の週の休み
- 木曜日の定休日に加えて、第3水曜日は休みにする
- 曜日と祝日の合わせ技
- 平日の祝日は休みにするが、土日にかぶった場合は営業する
- 通し営業をしていない場合
- 営業時間が 11:00-14:00, 17:00-22:00 のように分かれている
など、いろいろな場合が考えるが、
- 具体的な schedule をベタで持って、アプリケーションはその schedule に基づいて動作する
- schedule をつくるための規則は scheduling policy として別に保持する
という基本的な考え方は変わらずに適用できる。