#18 plantuml を使ってみた

この記事は tech.kayac.com Advent Calendar 2014 18日目です。

どうも。system perl version 5.8 で class とかほぼ使わない ORM も無い 生DBI で でもなぜか Xslate は使ってて Apache 2.0 と MySQL5.1 と memcached でなぜかシャーディング(DBの水平分割)された、生まれてから死ぬまで(約3年近く)を見届けたモバイルな案件から一転して、plenv でビルドする perl の version 5.20 を使って Carton でモジュールを固定し Mouse を使って class を作りまくり DBIx::Class で ORM を使いまくり nginx と MySQL5.6 と Redis でシャーディングされていない fluentd でログを投げまくるスマートなデバイスの新規案件にぶち込まれて戸惑いを隠せない @mix3 です。

新規案件入ってからのこと

「まずは仕様を読んでDB設計しよう」ってことでアウトプットとしてはER図を描いてみることになったわけですが、ER図を手(絵)で描いてくのも面倒だし、いきなり MySQL Workbench とか使い始めるのも仰々しいしでなんかこう手頃な感じで出来ないかなと他プロジェクトのリポジトリを眺めていたら plantuml なるものを使ってUMLのクラス図をER図に見立てて書いているのを見つけたので真似してみました

left to right direction

hide circle

class user {
    - id
    name
}

class item {
    - id
    name
}

class user_item {
    - user_id
    - item_id
    created_at
}

user --o user_item
item --o user_item

こんな感じで書くとそれを元に、

advent1.png

こんな感じで勝手に画像にしてくれるので、テキストで 定義していくこと に集中出来てそれがとても良かったです。ただしレイアウトはあまり触れないので見た目を綺麗にしたい場合は別のツールを使いましょう。

他のプロジェクトのDB設計をER図で見たい

で、ER図をごりごり書いていくわけですが当然他のプロジェクトのスキーマを参考にしたくなるので「ER図で見たい」となるわけですが都合よく自分の見たいものがドキュメントとして残ってるということは無かったのでさてどうしたものかと考えたところ

「実装にはORMが使われているし、ORMが持ってるメタデータからplantuml用のテキストを生成すれば良いのでは???」

と思い至りました。

弊社ではORMに DBIx::Class が(特にゲームチームで)よく使われているので、DBIx::Class であれば DBIx::Class::ResultSourceからテーブル名からカラム名からカラムタイプ、インデックス、リレーションなど諸々の情報が取れるので簡単にpluntuml用のテキストが作れちゃいそうです。

ということで書いたのがこちら DBICのスキーマから適当にplantuml用の出力を得るスクリプト 見るからに使い捨てスクリプトっぽいですね。

使うときは適当にこんな感じで。

carton exec perl -I /target/path/to/lib dbic_schema_to_plantuml.pl Target::Schema

スクリプトは先ほど書いた通りメタデータから変換しているだけですがちょっと捻っているところがあって、少しリレーションの線を間引く処理を入れています。

単純に「エイヤ!」で変換すると、テーブル数が7,80になってくるようなものをリレーションの線まで含めて変換してしまうと画像にしたときに バルスされたムスカ大佐 のようになってしまうので

  • user_*:ユーザテーブル
  • event_*:イベント用テーブル
  • *_history:ログ用テーブル

など、ある程度の規則にそってテーブル名が付いていることを期待して分割、リレーションの線は分割されたところをまたがないようにしました。

結局間引いてもでかいものはでかいし目は痛かったですが、 それなりに綺麗にER図になってくれたので個人的には捗りました。そして使ってみて良いなと思ったのは、これがやってるのは要するに 「現状の実装へのドキュメントの追従作業」 なのでとちゃんと運用すればドキュメントと実装の乖離を減らすのに役立つなーということ。CIで回すとかすると良いんじゃないかとか思いました。

まあ「ドキュメントにER図はいいからAPI仕様よこせ」 と言われるとそれまでですが、そのときは autodoc とか Shodo とか Test::JsonAPI::Autodoc とか使うと良いのかなと思っています。

それ、一つ一つ温かみのある手作業で DBIx::Class::Schema を書いてくの?

で、ER図がαクオリティではあるものの書けたので次はそれを元に DBIx::Class::Schema に落としていく作業が発生するわけですが、

package App::Schema::Result::Hoge;

use strict;
use warnings;
use utf8;

use parent 'App::Schema::Result';

__PACKAGE__->table('hoge');

__PACKAGE__->add_columns(
    id => {
        data_type         => 'INTEGER',
        is_nullable       => 0,
        is_auto_increment => 1,
        extra => {
            unsigned => 1,
        },
    },
    name => {
        data_type   => 'VARCHAR',
        size        => 191,
        is_nullable => 0,
    },
);

__PACKAGE__->set_primary_key(qw/id/);

1;

具体的にはこういうのを書くわけですが、最初のER図の時点で 30テーブル超え てきててそれを一つ一つ温かみのある手作業で DBIx::Class::Schema に落としていくのは正直 狂気の沙汰 にしか思えなかったので少し考えたところ

「さっき書いたplantumlのテキストをパースして、DBIx::Class::Schemaのpmファイルを生成すれば良いのでは???」

と思い至りました。

ということでコードジェネレータみたいなものを書いたのですが、コードはここに載せるにはあまりにも汚すぎるので具体例で示すと

object hoge {
    - id PK_INTEGER
    fuga VARCHAR(size => 191) -- コメント
    piyo DATETIME
}

こういうのを

package App::Schema::Result::Hoge;

use App::Schema::Types;

__PACKAGE__->table('hoge');

__PACKAGE__->add_columns(
    id => PK_INTEGER,
    fuga => VARCHAR(size => 191, _comment => "コメント"),
    piyo => DATETIME,
);

__PACKAGE__->set_primary_key(qw/id/);

1;

こんな感じで変換するものを作りました(class ではなくobject を使ってるのは()を使うとmethodとして判断されて画像にしたときにカラムの順序が崩れるからです) ちなみに App::Schema::Types は良く使われるカラムの設定に PK_INTEGER, VARCHAR, etc... と名前を付けてExportしているもので、これをしておくと DBIx::Class::Schema の記述量がゴリっと減るのでなるほどなぁと今回の話とは関係無いですが初めて見たとき感動してました。

package App::Schema::Types;

use strict;
use warnings;
use utf8;

use parent 'Exporter';
our @EXPORT = qw/PK_BIGINT .../;

sub PK_BIGINT {
    +{
        data_type         => 'BIGINT',
        is_nullable       => 0,
        is_auto_increment => 1,
        extra => {
            unsigned => 1,
        },
        @_,
    };
}

sub VARCHAR {
...

こんな感じですね。

ということで、planutumlからゴリっとコード生成することで人間は人間らしい仕事に集中することができました。めでたしめでたし。

妄想

コードジェネレートしたものをベースクラスとしてしまい、リレーション張ったりインデックス張ったりメソッド生やしたりなどはそれを継承して使うようにすれば 「開発中のカラム修正とかを自動生成に任せられて捗るのでは???」 とか思ったりもしましたが結局そこまではしませんでした。単純に継承して使うと result_class が継承元を見てしまって わざわざ result_class を設定しないといけないなど少し変なことをしないといけないようだったので、余計なことしてバグを仕込んでも拙いですしね。

なお

言うまでもないですが plantuml は UML を記述するためのもので、もちろん UML を記述するツールとして優れているのでそういう使い方もします。シーケンス図とか簡単、綺麗に書けるのでとても良いですよ。

まとめ

  • plantumlは便利
  • 特にテキストで記述できるのでゴニョりやすい
  • 発狂しそうな気がしたら人間以外にさせましょう

明日は

足を向けては寝れない、インフラエンジニアとして弊社サービスを支えてくれる @tkuchiki さんです。