全国バス100%のダイヤ改正に対応するため、ドメイン駆動設計でWebアプリを作った話
こんにちは、みみぞうです。
ナビタイムジャパンで『システムや開発環境、チームの改善』を担当しています。
はじめに
先月、『コミュニティバスカバー率100%導入への道のり』という記事を公開しました。
その中で紹介した課題の1つに改正対応の割り振りの難しさがありました。
ダイヤ改正対応の際は、何十社、何百社のダイヤ改正がある場合もあります。弊社では担当を決めているわけではなく、誰でもどのデータの取り扱いができるような仕組みにしています。
とはいえ、メンバーによってはデータの取り扱いに慣れている会社があったり、改正日までに日数が少ない場合は経験の深いメンバーが対応したり、フレキシブルに対応できるようにしています。
逆に経験の浅いメンバーには規模の小さい改正対応をお願いするなど、都度改正日に応じた最適な優先度付けや最適なメンバーへの割り振りを行う必要があります。しかし、先程お話した通り自治体が1,170市町村、路線バス515社の数があるので、担当者が全てを把握するのはとても難しいです…。
ではこの課題をどのようにして乗り切ったかのでしょうか。(この話はとても長くなりますので、また別の記事で紹介します。お楽しみに!)
この課題をドメイン駆動設計を用いて開発したWebアプリによって乗り越えました。本記事ではそれについて紹介します。
BusiRisk
BusiRiskとは今回作成したWebアプリの名前です。以下はトップ画面のキャプチャです。
BusiRiskはすべての改正に1日でも早く対応するため、いつ誰にどのような改正対応を割りあてるか提案し、マネージャーがフレキシブルにアクションするため必要な情報を可視化します。
当時の改正対応フローと課題
当時、改正対応はAtlassian製の課題管理ツールJiraだけを使って管理していました。
Jiraは非常に優れたツールです。以下を管理するには十分でした。
・誰が対応するか
・どのバス事業者・地方自治体に関する改正か
・どのような改正内容か
・いつまでに対応する必要があるか
・現在の対応ステータス
しかし、バスデータの改正対応で求められる以下の要件を満たすことはできませんでした。
・メンバーのスキルや役割によって、割り当て可能な改正対応を変えたい
・提供されたデータの性質によって、割り当て可能な改正対応を変えたい
・ナビタイム社内で整備されたドキュメントの状況によって、割り当て可能な改正対応を変えたい
・複数の日付(改正日、ナビタイムの検証日/リリース日など)を考慮して優先度を最適化したい
・改正内容ではなく、バス事業者・地方自治体ごとにデータをまとめて見たい
・複数の改正担当者が同じバス事業者・地方自治体の改正対応をした場合に起こる競合を回避したい
それらの要件を満たすため、マネージャーは各情報源にアクセスし、誰に改正対応をお願いするかに日々時間を費やしていました。(数年前はまだそれで回せる分量でした)
他にも、同じ状況でも判断する人によって指示が変わるという課題もありました。
システム構成とイメージ
先ほどの要件を満たし、改正速度を上げるためにはどのようなシステムを作るべきか考えました。まず必要だったのは、今回作成したWebアプリ BusiRisk を見れば、必要な情報すべてを確認できるようにすることです。
その結果、マネージャーや改正担当者はBusiRiskを見るだけで、必要な最新データを確認できるようになりました。
実際の画面イメージです。
この1画面に必要な情報が集約され、マネージャーや改正担当者ごとに、表示/非表示、ソート状態が最適化されています。機械的に行われるため、人によって判断が変わるという問題も解消されています。
なお、BusiRiskには以下の技術を採用しています。
・Vue.js
・Nuxt.js
・TypeScript
・Element UI
ドメイン駆動設計を取り入れた理由
必要なデータを1箇所に集約し、Webアプリケーションを作れば課題は解決しそうに見えます。しかし、本当の課題はその先にありました。開発を進めるにあたって直面した3つの課題です。
・開発者(私)に改正対応経験がほとんどない
・ビジネスロジックを知らない
・改正対応経験者でも、人によって優先度や割り当ての判断が違う
・ビジネスロジックが可視化/統一されていない
・改正対応経験者でWebアプリの設計に明るい人がいない
このまま開発を進めたら、何が正解か分からず、可読性も低いWebアプリになるのが想像できました。そのため、以下の開発方針を定めました。
1. 改正対応経験者達に『何を元にどう判断するか』をヒアリングする
2. 1でヒアリングした内容をドキュメントに記載する
3. 2に一致するようBusiRiskのパッケージ/モデルを作成する
4. 3を反映させたBusiRiskをリリースする
5. 改正対応経験者達と4の結果を確認し、気になる点があれば1に戻る
これらの要件を満たす手法として、ドメイン駆動設計を取り入れました。
具体例
ドメイン駆動設計で実装した機能として、次の課題を解決した例を説明します。
複数のメンバーが同じバス事業者・地方自治体の改正対応をした場合に起こる競合を回避したい
今まで『改正対応』と呼んでいたものを、以降は『チケット』と呼びます。
📝作業ブロックになるチケット
競合を回避するには、複数人で同じデータに関わる作業をさせないことです。まずは『競合状態にあって着手できないチケット』に『作業ブロックになるチケット』という名前を与えました。
『作業ブロックになるチケット』の条件、実は一言で言い表せるほど単純ではありません。ヒアリングを進めていくと、以下の条件であると分かりました。
この画像はConfluenceに記載されたドキュメントです。先ほどの開発方針では1と2に該当します。
1. 改正対応経験者達に『何を元にどう判断するか』をヒアリングする
2. 1でヒアリングした内容をドキュメントに記載する
画像のページでリンクされている以下の用語も同じように定義しています。数が多いため詳細は割愛させてください。 (不正な状態のチケットは人によって解釈がかなり異なっていました..!!)
・チケット
・着手可能なチケット
・待機中のチケット
・不正な状態のチケット
・会社
このようにしてユビキタス言語(開発者と非開発者が共通で使える言語)の定義を徹底します。そして、BusiRiskの開発に関わる話をするときはユビキタス言語の利用を義務づけます。もし誰かが『競合チケット』と言った場合は、『作業ブロックになるチケット』と言い直してもらいます。面倒ではありますが、この小さな積み重ねは開発スピードを何倍にも速めてくれました。
※ Confluenceのリンクは以下の記事でご紹介した方法を使っています
💻モデルに沿った実装
先ほど定義したドキュメントに従って、構造やインタフェースを維持するように実装します。先ほどの開発方針では3に該当します。
3. 2に一致するようBusiRiskのパッケージ/モデルを作成する
ソースコードには domain/ticket/vo/Ticket.ts という『チケット』のクラスがあります。
import { ValueObject } from "~/utils/vo";
interface Props {
id: TicketId;
title: TicketTitle;
type: TicketType;
assignee?: User;
priority: TicketPriority;
operationManagementLayer: OperationManagementLayer;
status: TicketStatus;
resolution?: TicketResolution;
xrfNames?: TicketXrfName[];
createdDate: DateTime;
revisedDate?: DateTime;
releasedDate?: DateTime;
unreleasedDate?: DateTime;
resolvedDate?: DateTime;
periodEndDate?: DateTime;
labels: TicketLabel[];
waitReasons: WaitReason[];
}
export class Ticket extends ValueObject<Props> {
// 中略...
}
Ticketクラスには多くのgetterやmethodがあります。その中には『作業ブロックになるチケット』かどうかをチェックするものがあります。
export class Ticket extends ValueObject<Props> {
// 中略
/**
* 作業ブロックになるチケットかどうか
* <ここにはConfluenceのURLが記載されています>
*/
isBlockOthers(user: User | undefined): boolean {
return user
? !this.isReady &&
!this.isWaiting &&
!this.isInvalidAsOperation &&
!this.isAssigneeMine(user)
: false;
}
// 中略
}
これは先ほどドキュメントに記載された表現とほぼ一致します。userの判定など若干のズレはありますが、開発をしていく中で少しずつ整備していけば問題ありません。
🔎作業ブロックになるチケットを考慮した結果
冒頭でお見せしたBusiRiskのイメージ、これはマネージャーから見た画面です。
これを改正担当者から見ると、作業ブロックになっているチケットは全て非表示となります。
最後にこれをリリースし、期待通りであるかを改正対応経験者に確認します。
4. 3を反映させたBusiRiskをリリースする
5. 改正対応経験者達と4の結果を確認し、気になる点があれば1に戻る
このサイクルを繰り返すことによって、利用者から本当に求められているアプリケーションを提供し続けることができます。
BusiRiskで扱うドメインとモデル
BusiRiskでは7つのドメインと60近くのモデルを扱っています。先ほどの『作業ブロックになるチケット』はチケットドメインに所属する1つのモデルに過ぎません。
ドメインやモデルは一度決めたら終わりではありません。完成という形はなく、日々ブラッシュアップしていく必要があります。特に繁忙期直前はドラスティックに変更が入ります。具体的には、お盆・年末年始・年度末の時期です。
ドメイン駆動設計の手応え
BusiRiskの開発を通して、ドメイン駆動設計に以下の手応えを感じました。
・認識齟齬を最小限に防げる
・中長期的に開発速度を向上/維持できる
実際にBusiRiskはこの1年間で60回以上のリリースをしています。縦軸がリリース回数です。
リリース速度を維持できた最大の理由は、ドメインモデルの設計がConfluenceにユビキタス言語を用いて明文化されているからだと思っています。実装はそのままソースコードに書き換えるだけであり、ほとんどの場合は開発/テスト/リリースを含め1日かかりません。
これらの経験から、ドメイン駆動設計で何より大切なのは 『開発者と非開発者がユビキタス言語という共通言語を使って、仕様を作成したり開発することである』 と感じました。
まとめ
改正対応の割り振りが難しいという課題について、BusiRiskというWebアプリを開発/利用して乗り越えた話をしました。
ナビタイムでは今後、高速バスやフェリーなど、その他交通機関にも対応する予定です。対応データ数が増えても、テクノロジーを用いて、より迅速に最新の情報提供ができるサービスを目指します。
最後までお読みいただきありがとうございました!