graphvizでwebの状態遷移図を描く


ここ1年半ほどはブリッジSEという仕事もしていて、日本の顧客の要求をいかに効果的に現地のエンジニアに伝えるかということに関心を持っているのですが、最近読んだ記事がなんとなくしっくりくるなと感じていて、じゃあそれを手っ取り早く図にする方法を考えていたらgraphvizを使うというところに行き着きました。

webの状態遷移図の書き方については上記のサイトをご覧になられた前提で書き進めようと思います。

この手法について私が感じているメリットとしては

  1. webに最適化されているので理解しやすい
  2. このまま結合テスト仕様書のたたき台としても使える

が挙げられます。
1.については少し私なりのアレンジをしていまして、詳細は後述しますがhttpメソッドやURLを書くことで一覧性を高めるような工夫をしています。一覧性が高いので抜け漏れが出にくくなるのではと考えています。
2.についてはプロジェクトの後半で参画するテスターの人たちでも仕様が理解しやすく、結合テスト仕様書のたたき台としても使うこともできると思います。

またgraphvizを使うメリットとしては

  1. 文法がシンプルで少ない記述で書ける
  2. レイアウトを自動で調整してくれる
  3. テキストファイルなのでバージョン管理しやすい

が挙げられるかと思います。
これまでメンテナンスされなくなったドキュメントを数多く見てきましたが、修正に手間暇がかかるというのが一番大きな問題かなと感じていました。私が見た幾つかのドキュメントには修正した日付と内容がびっしりと書かれており、時間をかけてメンテナンスしていることが伺えました。
それに対してgraphvizの方は.dotという拡張子を持つテキストファイルのみとなりますので、バージョン管理システムの恩恵を最大限にあずかることができます。

こちらが作成例です。これを書くのに必要な行数は150行程度でした。

では、さっそく描き方についてご説明しようと思います。
例としてユーザ登録関連の処理を用いることにします。

まずは以下のように基本的な設定を行います。

digraph registration {
    // Graph Settings
    graph[label="User Registration",labelloc=t,fontsize=18];
}

digraphは有向グラフを意味します。
graph[label="...とすることでグラフに対してラベルをつけることができます。文字サイズは少し大きめで、上部にラベルを表示するようにしています。

次に画面を定義します。
エントリーポイントになる画面(この場合はトップ画面)にはperipheries=3として三重丸になるようにしています。その他の画面は二重丸にしています。

    // 画面の定義
    top_view[peripheries=3,label="トップ /"];

    registration_view[peripheries=2,label="ユーザ登録 /user/register"];
    confirm_view[peripheries=2,label="ユーザ登録確認 /user/confirm"];
    sent_activation_email_view[peripheries=2,label="アクティベーションメールの送信完了 /user/sent_activation_email"];
    email_activation_view[peripheries=2,label="メーラの受信トレイ"];
    activated_view[peripheries=2,label="アクティベーション完了 /user/activated"];
    activation_code_expired_view[peripheries=2,label="アクティベーションコードの有効期限切れ /user/activation_code_expired"];

画面を定義したら次にそれらの画面を関連付けて画面遷移を定義します。
先に画面遷移だけ定義しておくと、後でサーバサイドの処理を追加していく際に迷いにくくなります。
関連付けを表す矢印のstyleにdashedを使った理由としては、後述するサーバサイドの処理の流れと区別をするためとなります。

    // 画面遷移の定義
    top_view->registration_view[style=dashed];
    registration_view->confirm_view[style=dashed];
    confirm_view->sent_activation_email_view[style=dashed];
    sent_activation_email_view->email_activation_view[style=dashed];
    email_activation_view->activated_view[style=dashed];
    email_activation_view->activation_code_expired_view[style=dashed];

次にサーバサイドの処理のstyleを定義します。
参考にした記事に従い、サーバサイドの処理は黒塗りにしています。
リクエストを受け付ける箇所にはhttpメソッドとURLを書くことで実装しやすくしています。

    // サーバサイドの処理のスタイル定義
    // ユーザ登録とアクティベーション
    "GET /user/register"[style=filled];
    "POST /user/confirm"[style=filled];
    validate_inputted_user[style=filled];
    set_inputted_user_on_session[style=filled];
    "GET /user/sent_activation_email"[style=filled];
    restore_inputted_user_from_session[style=filled];
    generate_activation_code[style=filled];
    save_user[style=filled];
    send_activation_email[style=filled];
    save_as_activated[style=filled];
    "GET /user/activated"[style=filled];
    validate_activation_code[style=filled];

最後にサーバサイドの処理を定義します。
subgraphを使うことでサーバサイドの一つのまとまりの処理を四角で囲むようにしています。
また矢印(エッジと呼ばれている)にラベルをつけることで、処理の結果を表示することができます。

    // サーバサイドの処理の定義
    // ユーザ登録とアクティベーション
    top_view->"GET /user/register"[label="ユーザ登録ボタンをクリック"];
    "GET /user/register"->registration_view;
    registration_view->"POST /user/confirm"[label="登録ボタンをクリック"];
    subgraph cluster_confirm_registration {
        label="登録確認";
        "POST /user/confirm"->validate_inputted_user;
        validate_inputted_user->set_inputted_user_on_session[label="入力値妥当な場合"];
    }
    validate_inputted_user->registration_view[label="入力値が無効な場合"];
    set_inputted_user_on_session->confirm_view;
    confirm_view->"GET /user/sent_activation_email"[label="登録ボタンをクリック"];
    subgraph cluster_register_user {
        label="ユーザを保存しアクティベーションメールを送信";
        "GET /user/sent_activation_email"->restore_inputted_user_from_session;
        restore_inputted_user_from_session->generate_activation_code
        generate_activation_code->save_user;
        send_activation_email->send_activation_email[label="エラー後にリトライ"];
    }
    save_user->send_activation_email[label="保存に成功"];
    save_user->server_error[label="データベースエラー"];
    send_activation_email->sent_activation_email_view[label="保存に成功"];
    email_activation_view->"GET /user/activated"[label="メール本文にあるアクティベーションリンクをクリック"];
    subgraph cluster_activate {
        label="ユーザを有効化";
        "GET /user/activated"->validate_activation_code;
        validate_activation_code->save_as_activated[label="アクティベーションコードが有効な場合"];
    }
    validate_activation_code->activation_code_expired_view[label="アクティベーションコードが無効な場合"];
    save_as_activated->activated_view[label="保存に成功"];
    save_as_activated->server_error[label="データベースエラー"];

描き方についてはまだまだ改善の余地があるかと思いますので、ご興味があれば皆さんの方でも工夫してみていただければと思います。
Githubの方にもソースをあげておきましたので、参考にしてみてください。