(24日目) schema2rst と Sphinx でお手軽データベース定義書作り

ここ数日 blockdiag の話が続いていましたが、久しぶりに Sphinx の話に戻ります。

僕は Sphinx を使ってお仕事のドキュメントを書いているので、
ドキュメントのひとつとしてデータベース定義書を書くことがあります。
このデータベース定義書、みなさんはどうやってメンテナンスしていますか?

以前、僕のチームでは Excel で定義書を作っていたのですが、
定義書と実際に稼動しているスキーマが異なることが稀にあり、
混乱が生したり、ちょっとしたトラブルの元になることがありました。*1

よく考えてみると、データベース定義書とスキーマを二重に管理しなくてはならないので、
どこかで同期に失敗すると(更新忘れなど)そこに差ができてしまうのは自明なことです。

一部の企業では Excel から DDL を書きだすようなマクロを持っているそうですが、
差分更新ができないなどでうまくメンテナンスされていないという話を聞きます。

問題のもととなっているのは

  • データベース定義とスキーマを二重管理していること
  • 最新の情報であるか確認する術がないこと

のふたつです。

schema2rst

そこで、問題を解決するべく schema2rst というツールを開発しました。
名前の通り、スキーマ定義から reST ファイルを生成するツールです。
schema2rst を利用することで、問題のもととなるふたつを根本から解決することができます。

schema2rst 自体はずっと前にたたき台になるものを開発してありましたが、
この記事を書くために昨日ようやくリリースしました。
今読み返すとほぼ一年間放置してあったようです。

テスト用のデータベースを作る

schema2rst 0.1.0 (最新版)は MySQL をターゲットに作られているので、
MySQL に次のようなデータベースとテーブルを作成します。

CREATE DATABASE sample CHARACTER SET utf8;

作成するテーブルは 4つです。

  • users (ユーザ)
  • items (商品)
  • order_history (購入履歴)
  • uncommented_table (テスト用/MySQL コメントなし)
DROP TABLE IF EXISTS users, items, order_history, uncommented_table;

CREATE TABLE users (
  id int primary key auto_increment comment 'ユーザ ID',
  login_id varchar(16) default '' not null comment 'ログイン ID',
  fullname varchar(255) default '' not null comment '氏名',
  mailaddr varchar(255) default '' not null unique comment 'メールアドレス',
  key (mailaddr)
) ENGINE='InnoDB' COMMENT 'ユーザ';

CREATE TABLE items (
  id int primary key auto_increment comment '商品 ID',
  name varchar(255) not null comment '商品名',
  type int not null default 1 comment '種別 (1:食品, 2:文具, 3:雑貨)',
  description text comment '説明文'
) ENGINE='InnoDB' COMMENT '商品';

CREATE TABLE order_history (
  id int primary key auto_increment comment '履歴 ID',
  user_id int not null comment 'ユーザ ID',
  item_id int not null comment '商品 ID',
  amount int not null comment '数量',
  order_date datetime comment '購入日',
  index (user_id, order_date),
  index (item_id, order_date),
  foreign key (user_id) references users(id),
  foreign key (item_id) references items(id)
) ENGINE='InnoDB' COMMENT '購入履歴';

CREATE TABLE uncommented_table (
  id int primary key auto_increment,
  name varchar(255) binary
) ENGINE='InnoDB';

DDL をよく見ると、COMMENT 文を利用しています。
ちょっとマイナーな存在ですが、MySQL ではテーブルやカラムに対してコメントを付与することができるので、
schema2rst ではこのコメント領域を利用してテーブルやカラムの日本語名の情報、
制約などの情報をスキーマから取得します。
schema2rst を利用する際はなるべくコメントを利用して、
詳細にスキーマ定義を行うことを推奨します。

schema2rst でスキーマ情報を取り出す

schema2rst は easy_install でインストールすることができます。

$ sudo easy_install schema2rst

インストール後、schema2rst の設定ファイルを作成します。
設定ファイルはデータベースへの接続情報を YAML 形式で記述します。

$ vi config.yaml
db: sample
host: localhost
passwd: passwd
user: username

最後に schema2rst を実行します。引数には先ほど作成した設定ファイルを指定します。

$ schema2rst config.yaml > schema.rst

生成された schema.rst にはデータベース定義の情報が含まれています。
サンプルとして items テーブルの情報はこんな感じで出力されます。

商品 (items)
------------

.. list-table::
   :header-rows: 1

   * - Fullname
     - Name
     - Type
     - NOT NULL
     - PKey
     - Default
     - Comment
   * - 商品 ID
     - id
     - int(11)
     - False
     - True
     - None
     - auto_increment
   * - 商品名
     - name
     - varchar(255)
     - False
     - False
     - None
     -
   * - 種別
     - type
     - int(11)
     - False
     - False
     - '1'
     - 1:食品, 2:文具, 3:雑貨
   * - 説明文
     - description
     - text
     - True
     - False
     - None
     -

reST では list-table を使っているのでパッと見では分かりづらいですね。
どのような出力になるか把握しやすいよう、Sphinx でビルドしたものも用意しました。

先程の DDL 文と見比べて、同等のデータベース定義が取り出せています。
MySQL のコメントがないテーブル・カラム(uncommented_table)も
必要な情報は列挙されています。
コメントがない状態でも最低限のデータベース定義はすぐ生成できます。

まとめ

schema2rst を利用すると DB 上のスキーマ定義情報から直接データベース定義書を作成することができます。
いまのところ MySQL にしか対応していませんが、今後他の DBMS にも対応していくつもりです。*2
興味が有る方は一度使ってみてはいかがでしょうか。

いよいよ明日 12/25 でこのアドベントカレンダーもおしまいです。
せっかくですので、どうかもう一日だけお付き合い下さい :-)

*1:幸い大きいトラブルになったことはありません。幸運なだけかもしれませんけど。

*2:考えてはいますが、僕自身は MySQL ばかり使っているので他のエンジンへの対応は>のんびりやるつもりです

(23日目) blockdiag の便利な使い方(サンプル集)

いま現在、blockdiag.com には使い方の例は書いてありますが、
どんな図が書けるのかというサンプルがほとんどありません。
これは単に僕の努力不足なのですが、たまには書きこまれた図をお見せしてみたいと思います。

画面遷移図

最初にご紹介するのは画面遷移図です。
もともと blockdiag を作り始めるきっかけは画面遷移図を書くことだったので、
blockdiag と画面遷移図は非常に相性がいいです。
どの画面からどの画面に遷移するのかを矢印(->)で書くだけで図が出来上がります。

ちょっとしたマスタ管理であれば、遷移だけを書いていけば図が出来上がります。
もちろん、途中で画面を追加したくなっても、画面が廃止になっても定義を書き換えるだけです。

blockdiag {
   トップページ -> メニューA, メニューB, メニューC;

   メニューA -> ○○一覧 -> ○○追加, ○○編集, "○○削除(確認)";
      ○○追加 -> "○○追加(確認)" -> ○○一覧;
      ○○編集 -> "○○編集(確認)" -> ○○一覧;
      "○○削除(確認)" -> ○○一覧;

   メニューB -> △△一覧 -> △△設定;
   メニューC -> □□一覧 -> □□設定;
}

Web でありがちなフローも同じように書くことができます。
例えば会員登録は次のように書きます。

blockdiag {
   各画面 [stacked];
   DB [shape = flowchart.database];
   メール [shape = mail];

   各画面 -> メールアドレス入力 -> アドレス確認画面 -> メール送信完了画面 -> DB, メール;
   メール -> 会員登録フォーム [folded];
   会員登録フォーム -> 会員登録確認画面 -> 登録完了画面;
}

機能構成図

blockdiag では機能構成図のような幾つかのアイテムの関係を書くこともできます。
すべての図に使えるわけではないですが、シンプルなものは大抵かけるのではないかと思います。

少し前はこんな図を書いてメンバーと共有していました。

blockdiag {
  Webブラウザ [stacked];
  MySQL [shape = "flowchart.database"];

  Webブラウザ -> Webアプリ -> MySQL, RabbitMQ;
  RabbitMQ -> バッチ処理A, バッチ処理B, バッチ処理C;

  group {
    Webアプリ; MySQL; RabbitMQ;
  }
}

図によっては blockdiag のレイアウトエンジンと相性が悪いものがあるかもしれません。
僕がいままで書いた中では次のような図は blockdiag で書くには工夫が必要でした。
自分のシステムを中心においた放射状の図は苦手な部類に入ります。

自分の書きたい図に blockdiag が合うようであれば使ってみてもいいかもしれません。

ちなみに定義はこういうものです。

blockdiag {
  group system_A {
    orientation = portrait;
    color = none;

    none01 [shape = none];
    none02 [shape = none];
    A_API;
  }

  group system_B {
    color = none;
    B_API -- DB [folded];
  }
  A_API -- A連携機能 [folded];
  B_API -- B連携機能

  group MySystem {
    管理機能 -> ○○管理機能, △△管理機能, ××管理機能;
    ○○管理機能 -> A連携機能;
    ××管理機能 -> B連携機能 [folded];
    △△管理機能 -> C連携機能, D連携機能;
  }
  C連携機能 -- C_API;
  D連携機能 -- D_API [folded];

  group system_C {
    color = none;
    C_API;
  }

  group system_D {
    orientation = portrait;
    color = none;

    none04 [shape = none];
    none05 [shape = none];
    D_API;
  }
}

それぞれのノードの宣言順序が並び方に強く影響しているので、書き換えるとレイアウトが壊れることがあります。

フローチャート

次の図はフローチャートです。
blockdiag はフローチャート用のノードレンダラーを持っているように、
フローチャートを書くことを得意としています。

例えばよくある携帯アプリの認証ロジックは次のようなフローチャートになります。

blockdiag {
   orientation = portrait;
   plugin autoclass;
   class if [shape = diamond];
   class state [color = pink];

   携帯認証モード -> UA取得 -> キャリア判定_if -> au, Docomo, SoftBank, その他;
      Docomo -> uid取得 -> ログイン判定_if;
      au -> EZ番号取得 -> ログイン判定_if;
      SoftBank -> Cookie取得 -> ログイン判定_if;
      その他 -> エラー終了_state;

   ログイン判定_if -> ログイン状態_state, 非ログイン状態_state;
}

組織図

blockdiag のおもしろい利用方法のひとつが組織図です。
blockdiag を作る際にまったく想定していなかった図なのですが、
ユーザーの方で実際にこんな風に使っている方がいらっしゃいます。

やってみると馴染むものなんですね。ここでは自衛艦隊の組織図を書いてみます。
この図はもともと @99blues さんのところで公開された定義をアレンジしたものです。

blockdiag {
   自衛艦隊 -- 護衛艦隊, 航空集団, 潜水艦隊, "掃海隊群(横須賀)";

   group{
      color=khaki;
      護衛艦隊 -> "第1護衛艦隊(横須賀)", "第2護衛艦隊(佐世保)",
                  "第3護衛艦隊(舞鶴)", "第4護衛艦隊(呉)";
   };
   group{
      color = lightgreen;
      航空集団 -> "第1航空群(鹿屋)", "第2航空群(八戸)",
                  "第4航空群(厚木)", "第4航空群(那覇)";
   };
   group{
      color = aquamarine;
      潜水艦隊 -> "第1潜水隊群(呉)", "第2潜水隊群(横須賀)";
   }
}

座席表

あと最近耳にしたものでは、blockdiag で会社の座席表を書いたそうです。
もちろん好ましいかたちにレイアウトしてくれるわけではないので、
blockdiag の細かいテクニックを駆使して組み上げたそうです。

実物を見ていないのですが、こんな感じの定義で書いたのではないかと思われます。

blockdiag {
   span_width = 0;
   span_height = 0;

   group {
      color = none;
      A01 -- A02 -- A03 -- A04
      A05 -- A06 -- A07 -- A08;
   }
   A04 -- A09;
   A09 [colheight = 2, width = 80];

   spacer_01 [shape = none];

   group {
      color = none;
      B01 -- B02 -- B03 -- B04
      B05 -- B06 -- B07 -- B08;
   }
   B04 -- B09;
   B09 [colheight = 2, width = 80];

   spacer_02 [shape = none];

   group {
      color = none;
      C01 -- C02 -- C03 -- C04
      C05 -- C06 -- C07 -- C08;
   }
   C04 -- C09;
   C09 [colheight = 2, width = 80];
}

ただ、これは blockdiag のレイアウトエンジンを理解した上で、
応用しながら書いている図なので自由度はかなり低いです。
正直なところこれは Visio で書いたほうが幸せになれると思います :-p

シーケンス図

今度は seqdiag の例を紹介します。
seqdiag はシーケンス図というかなり特化した図を対象としているので、
あまりびっくりするような図は出てきません。

ただ、要素を組み合わせていくと結構綺麗な図ができるので個人的には重用しています。
たとえばグループ機能を使うと見栄えがぐっと良くなります。*1

seqdiag {
   PHP => ライブラリ [label = "状態確認", return = "認証状態"] {
      ライブラリ => 認証API [label = "REST Call"] {
         認証API => データベース [label = "ユーザ問い合わせ", return = "ユーザ情報"];
         認証API --> Twitter [label = "ログイン情報"] {
            Twitter -> Twitter [label = "つぶやく"];
         }
      }
   }

   group {
     label = "Webサーバ";
     color = pink;

     PHP; ライブラリ;
   }

   group {
     label = "認証鯖";
     color = lightgreen;

     認証デーモン; データベース;
   }

   group {
     color = lightblue;
     Twitter;
   }
}

他にも最近導入したノート表示もシーケンスの説明を書く上で重要な要素です。
リクエストしてくれたチームではうまく使っておられるそうです。
ここではサンプルとしてメール送信を行う際のやりとり(SMTP)を図示化してみます。

seqdiag {
   activation = none;

   クライアント -> サーバ [label = "HELO", leftnote = "ハンドシェイク\nクライアントから HELO という文字列を送信する"];
   クライアント <- サーバ [label = "250 OK" rightnote = "ok が返ってくる"];
   クライアント -> サーバ [label = "MAIL FROM: ...", leftnote = "送信元アドレスを伝える"];
   クライアント <- サーバ [label = "250 OK" rightnote = "ok が返ってくる"];
   クライアント -> サーバ [label = "RCPT TO: ...", leftnote = "送信先アドレスを伝える"];
   クライアント <- サーバ [label = "250 OK" rightnote = "ok が返ってくる\n(アドレスがなくても ok であることが多い)"];
   ... 以降、本文を送信する ...
}

まとめ

今日は blockdiag シリーズで生成される図の中で、見栄えの良いものをピックアップしてご紹介しました。
Web ではシンプルな例が多いのでどういう図ができるのかイメージしづらいですし、
なかなか人の書いた図を見る機会がないので、もし参考になればうれしいです。

今後、こういうサンプルをまとめて blockdiag.com に掲載しようと思っているので、
こういうのができたよ!もっといい例があるよ!というものがあれば @tk0miya まで教えて下さい。

*1:この機能は珍しく自分のために実装した機能です。僕のケースではグループを使うことで図が格段に見やすくなりまし>た。