domaindoc でドメインモデルをドキュメントする


この記事は CureApp Advent Calendar 2016 18日目の記事です。

今日はドメインモデルのドキュメンテーションの話です。

さて、DDD を実践していくと、成果物としてドメインモデルの定義が上がってきます。最終的には、それをソースコードに落とし込むというのが、DDD の目標ですが、その前に一旦、これこれのドメインモデルが定義された、ということを記録する必要が必ず生じます。

その定義をどうやって保存するかというのはチームによってまちまちなのではないかと思いますが、自分の経験上、どこにどういう形でドメインモデルのドキュメントを残すかはわりと意見が分かれるトピックです。

自分が今までやってきた手段としては、wiki (コンフルーエンス) に残す、Google スプレッドシートに残す、ホワイトボードに手書きしたものを写真でとるなどの手法をやってきましたが、どれも一長一短でした。

それぞれの手法にどういう問題があるのか考えるために、まず、ドメインモデルのドキュメントが備えるべき性質を考えていきましょう。

ドメインモデルのドキュメントはどういう性質のものであるべきか

共有容易性

ドメインモデルのドキュメントはまず共有が容易でなければなりません。簡単に言えば、URL 1本で共有できて、クリックするだけで見れる状態になっていれば十分でしょう。逆に URL を持っていない/ブラウザで見れない何か (例えば特定の有料ソフトでしか見れない特定形式のファイル (エ○セル) など) にドキュメントするのは得策ではないんでしょう。

一覧性

ドメインモデルは一覧できないと全体観がつかめず、ドキュメントを見ること自体が苦痛になってしまいます。例えば、モデルのプロパティを知るために、各モデルの個別ページをいちいちクリックさせられるようだと、ドキュメントを眺めるという行為自体が億劫になってしまいます。

詳細性

一覧性とは逆で、どこまで詳細にドキュメントされるかという点も重要です。一覧性を高めるために、ドメインモデルの一覧表示をコンパクトに収めようとすると逆に詳細をうまくドキュメントに残せない/表示がうまくいかない、というような事態に陥りがちです。(Google スプレッドシートのセルの中に1000文字の詳細説明を書かれていても読む気が起きません。)

メンテナンス容易性

ドメインモデルは常に変わっていきます。開発の途中でも変わりますし、リリース後も変わります。リファクタリングによって変わるという場合もあるでしょうし、そもそもビジネスルール自体が変わるので、それに追従して変わるというケースもあり得ます。同期がズレたドキュメントは混乱の元でしかなく、一度同期がズレたドキュメントはやがてどんどん同期がズレて、現状を全く反映していない有害な情報になっていきます。そのような状態になることを防ぐためには、ドキュメントのメンテナンスが誰でも容易にできるという状態である必要があります。特定の職人化したメンテナーとか、特定のソフトを持っている人だけが、ドキュメントを更新できるという状態は非常にリスキーです。従って、ドメインモデルのドキュメントは誰でも手軽にメンテナンスできることが望ましいです。

トレーサビリティ

ドメインモデルは常に変わっていくということは、ある時点での正しかったドメインモデルがある時点で正しくなくなるというようなこともよくあります。あるドメインモデルのあるプロパティが、なぜ今の形になっているかということを理解するために、過去の履歴をたどる必要がある場合がよくあります。

また、ドメインモデルの変更というのはプロダクトの仕様そのものの変更と同義です。あるドメインモデルが、自分が知っていた時期と違うものになっているという場合に、なぜそうなのかをきちんと辿れることは、プロダクトの履歴をたどる上で重要です。

既存ツールの比較

さてドキュメントツールが備えるべき性質を 5つ挙げたところで、各ツールが各性質をどの程度備えているかを見ていきましょう。

Google スプレッドシート

Google スプレッドシートは、共有は容易で、一覧性もそこそこありますが、詳細の記述とトレーサビリティに問題があります。実際どのセルを誰が最後に変えたのか知る手段がありません。

項目 評価
共有容易性
一覧性
詳細性
メンテナンス容易性
トレーサビリティ

Confluence

コンフルは、大きいテーブルを作って、モデルの表のようなものを作ればわりと機能します (コンフルのテーブル編集機能が比較的優秀なため)。ただ、Google スプレッドシートと同じで、セルにそこまで詳細情報を記述できないことが問題です。各モデルのページを分け始めると今度は一覧と個別ページで同じ情報の多重管理になってしまい、メンテナンス難易度が上がってしまいます。

また、トレーサビリティについては、一応履歴を辿れますが、git blame 的な機能を持たないため、過去の特定の変更を調べたい場合手で1件1件たどる必要が生じるためスケールしません。

項目 評価
共有容易性
一覧性
詳細性
メンテナンス容易性
トレーサビリティ

Cacoo の図

とあるチームで Cacoo でドメインモデルの大きな図を書くということを行なったことがありますが、図中のモデルの配置を手動うまく調節する必要があり、メンテナンスコストが高いものでした。

項目 評価
共有容易性
一覧性
詳細性
メンテナンス容易性
トレーサビリティ

ホワイトボードの写真を撮る

ホワイトボードに書きながらモデルを探る作業は、自由度が高く、その場にいるメンバーにとってはインタラクティブでもあって、モデリングの最初期の議論には非常に適していますが、写真で撮ってそのままだと、全くメンテナンスが出来ないため、後々困ることになるでしょう。誰かが責任を持ってきちんと文字データ化しましょう。

項目 評価
共有容易性
一覧性
詳細性
メンテナンス容易性
トレーサビリティ

domaindoc を使う

さて、ここまで挙げたツールに共通する問題点は、主にトレーサビリティと詳細性です。誰がいつなにをどう変えた、を追うのがどのツールでも結構辛かったり不可能だったりという難点があります。

また、ドメインモデルは大抵量が膨大になるため、一覧性を確保しようとして、逆に詳細性が犠牲になるというパターンが多いです。(Google スプレッドシート、コンフルのテーブルなど)

そこで、トレーサビリティを担保しつつ、一覧性・詳細性を確保し、なおかつメンテナンス容易性を失わない方法はないかと考えて作ったものが domaindoc というツールです。

以下、domaindoc の使い方を紹介していきます。

準備

まず、node.js (v4以上) を用意してください。

インストール

適当なディレクトリを作って、domaindoc をインストールします。

npm install domaindoc

次にモデルのドキュメントとなる (モデル名).md というマークダウンファイルを source/ 配下に配置します。(source/ はデフォルトのソースファイル置き場ですが、変更可能です(後述)。マークダウンファイルには冒頭にフロントマターをつけてモデルのメタ情報を記述できます。)

ここでは仮に、User モデルと Item モデルがあることとします。

source/
  - user.md
  - item.md

User のドキュメントを以下のように記述します。(フロントマター付きのマークダウンファイル形式です)

source/user.md
---
name: User
desc: ユーザを表すモデル
props:
  - name: id
    desc: ユーザの id
    type: string
  - name: name
    desc: ユーザ名
    type: string
  - name: items
    desc: ユーザが持っている Item
    type: Item[]
---

ユーザを表すモデルです。 1人の User は0個以上400個以内の Item を持ちます。(サンプルです)

Item のドキュメントを以下のように記述します。

source/item.md
---
name: Item
desc: 商品を表すモデル
props:
  - name: id
    desc: 商品の id
    type: string
  - name: name
    desc: 商品名
    type: string
  - name: price
    desc: 商品の価格
    type: number
---

商品を表すモデルです。(サンプルです)

(フロントマターの中で使えるプロパティについては 公式ドキュメント を参照してください。)

ここまで準備ができたら、./node_modules/.bin/domaindoc というコマンドを実行します:

$ ./node_modules/.bin/domaindoc
domaindoc [21:28:22] serving
domaindoc [21:28:22] Reading: source/**/*.md
domaindoc [21:28:22] Reading: source/**/*.md
domaindoc [21:28:22] Reading: ./node_modules/domaindoc/src/styles/**/*.css
domaindoc [21:28:22] Server started at: http://0.0.0.0:8011/
domaindoc [21:28:22] See debug info at: http://0.0.0.0:8011/__domaindoc__
domaindoc [21:28:22] Ready: ./node_modules/domaindoc/src/styles/**/*.css
domaindoc [21:28:23] Ready: source/**/*.md
domaindoc [21:28:23] Ready: source/**/*.md

コマンドが成功すると、ローカルサーバーがポート 8011 で立ち上がります。 http://0.0.0.0:8011/index.html にアクセスすると、下のように、ローカルサーバー上でドキュメントサイトを確認できます。

(この状態で、ドキュメントのソースが watch 状態になっていて、変更がすぐに確認できます。)

ローカルサーバー上で内容が確認できたら、./node_modules/.bin/domaindoc build コマンドを実行してください。

$ ./node_modules/.bin/domaindoc build 
domaindoc [22:26:09] building
domaindoc [22:26:09] done

上のように、コマンドが成功すると、build/ 以下に次のようなファイルが生成されます。

build/
  - index.html
  - user.html
  - item.html

build/index.html を開くと、ローカルサーバー上で確認したのと同じ内容のドキュメントが生成されていることが確認できます。

ドキュメントデモサイト

上のソースレポジトリ

さて domaindoc の出力する build 以下のファイルはそれ自体で静的サイトになっているため、例えば、github の gh-pages に置けばそれだけで誰でもみれるドキュメントサイトになります。(共有容易性 )

また、出力されるサイトは一覧と詳細をそれぞれ別画面で持っているため、index ページで全体観を見せつつ、詳細ページにいくらでも詳細な性質を記述できます。(一覧性 & 詳細性 )

ソースファイルは markdown 形式のため、git で管理すれば全ての変更を容易に追うことが出来ます。git/github の blame 機能を使えば、この行は最後に誰が書いたという調査が簡単に出来ます。(トレーサビリティ )

テキストエディタで markdown ファイルを編集すれば良いため、煩雑な記述や操作が必要ありません。(メンテナンス容易性 )

domaindocfile.js で各種設定をする

domaindocfile.js というファイルを用意することで、以下のようにビルド内容をカスタマイズできます。

domaindocfile.js
const domaindoc = require('domaindoc')

domaindoc.title('○○サービスのドメインモデル一覧') // デフォルトは `The list of domain models` です
domaindoc.source('modles') // デフォルトは `source` です
domaindoc.dest('site') // デフォルトは `build` です
domaindoc.basepath('ベースパス') // 指定しない場合は、相対パスになります。

上のファイルを用意した上で、./node_modules/.bin/domaindoc build コマンドを実行すると、自動的に、設定が読み込まれた状態でビルドが始まります。

domaindoc の仕組み

domaindoc は内部的に bulbo という静的サイトジェネレータを使っています。bulbo は gulp プラグインを組み合わせてビルドパイプラインを構築できるジェネレータで、 domaindoc の場合は、マークダウンファイルのパースや html への変換は全て gulp プラグインの組み合わせで行なっています

Example

以下の例は、自分のプロジェクト Pairs (ゲームアプリ) で使っている少し大きめの使用例です。

まとめ

DDD を実践している方で、ドメインモデルのドキュメント方法に悩んでいる方、ぜひ domaindoc を試してみてください!

Happy modeling!

明日は、 @imoans さんのお話です!