このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

WebアプリケーションフレームワークCatyの現状と今後

最近どんな感じで、何を目指してやっているのかを簡単に紹介します。目指していることはイッパイあるのですが、特に「インスタントモックアップ」と我々(僕とKuwataさん)が呼んでいる機能にフォーカスします。インスタントモックアップは、最近着手したばかりで何も出来てないのが実状ですが、できたらけっこう便利そう。すごく便利かもしれません。

インスタントモックアップの目標は「Webサイト/Webアプリケーションのプロトタイプ作成の労力を、百分の一にする」ことです。効率を二桁上げたいわけね。なんかホラ話みたいでしょ。実際、誇張表現なんですが、まったく根拠がないわけでもないんですよ。

内容:

  1. 例題「ユーザー管理業務」
  2. Catyの用語を少しだけ
  3. モジュールの構造
  4. 定義が書いてないモジュール
  5. 何をすべきかはナントナク予想できる
  6. どんなに中途半端・不完全な設計でも動かす

例題「ユーザー管理業務」

1年たって、手書きから自動描画へ」と「ハイパーバリデーションに向けて」で、Catyで状態遷移図の自動描画が出来るようになったことを紹介しました。それで、Kuwataさんと僕が作ったサンプルじゃない例題を探していたら、http://terasoluna.sourceforge.jp/tutorial/server-web/Document/WebTutorial_2.html にある図(URL: http://terasoluna.sourceforge.jp/tutorial/server-web/Document/WebTutorialImg/WebTutorial_2.1_01.png)を発見しました。Webサイトというより業務アプリケーションですが、これはこれで典型的な例だと思えるので拝借します。

この図とほぼ同じ状態遷移をCatyにより定義して可視化すると次のようになります。

もとの図の初期画面というのは省略しました。他の画面は、Catyが描いた図の黄色い四角と対応します。

  1. ログオン画面 → dummy-logon
  2. メニュー画面 → menu
  3. 一覧表示画面 → list
  4. 登録画面 → register
  5. 結果画面 → result

画面遷移を引き起こす“操作”の対応は下に挙げます。ログオン関係は省略しています(それでダミーログオンと命名)。「5ページ進むリンク」「5ページ戻るリンク」も煩雑になるという理由で省いています。Catyの図の辺ラベルに付いているプラス記号は気にしないでください。状態遷移を引き起こす操作の名前にプラスが付いていると思ってください*1

  1. 「一覧画面」ボタン → + to-list
  2. 「登録画面」ボタン → + to-register
  3. 「前へ」リンク → + next
  4. 「後へ」リンク → + prev
  5. 「ページ番号」リンク → + go-to
  6. 「登録」ボタン → + do-register
  7. 「メニュー」リンク → + menu
  8. 「ログオフ」ボタン → + logoff

もとの図とCatyが描いた図が、ほぼ対応が取れていることが分かれば十分です。(グラフのレイアウトは、Graphvizレイアウトエンジンに任せているので、画面ノードの配置までもとの図に似せることはできないです。あしからず。)

Catyの用語を少しだけ

説明のためには、基本的な言葉が必要です。

  • アプリケーション -- ひとつのWebサイトやWeb APIプロバイダに対応する単位です。1個のCaty実行インスタンスにより複数のアプリケーションを提供できます。
  • モジュール -- アプリケーションの挙動を宣言・定義するファイルです。ひとつのアプリケーションはいくつかのモジュールから構成されますが、簡単なアプリケーションなら単一モジュールで十分です*2。モジュールには、後述する状態、アクション、リソースクラスの定義が含まれます。
  • 状態(≒画面) -- アプリケーション状態、あるいは単に状態とは、Webクライアントの実行時スナップショットのようなものです。典型的な状態はブラウザの画面なので、状態とは画面だと思ってかまいません。
  • アクション -- HTTPリクエストで起動されるサーバー側のプログラム(リクエストを処理する単位)のことです。
  • リソースクラス -- アクションをグループ化したものがリソースクラスです。リソースクラスはURLのパターンを持ち、このパターンにマッチするURLとHTTPメソッド等でアクションが識別されます。

モジュールの構造

僕が毎日実際に使っているアプリケーションのモジュールを紹介しましょう。Wiki記法で書いた仕様文書を提供するだけの簡単なアプリケーションで、単一モジュールで出来ています。


/** specdocsアプリケーションのリソース&アクション
*/
module specdocs in cara;

/** ディレクトリ・リソース
*/
resource Dir("/" | "*/") {
/** ディレクトリへのGETリクエスト */
action dir-get("/GET")
::
{
%CATY_APP | $.name > app;
{
"wikifiles" : lsdir %PATH_INFO .wiki | each {pv name},
"subdirs" : lsdir --kind=dir %PATH_INFO | each {pv name},
"path" : %PATH_INFO,
"app" : %app,
"title" : [%app, " ", %PATH_INFO] | text:concat
} | print /list-files.html
};
};


/** Wikiファイル・リソース
*/
resource Wiki("*.wiki") {
filetype {
"contentType" : "text/x-wiki",
"isText" : true,
};

/** WikiファイルへのGETリクエスト */
action wiki-get("/GET")
::
{
{
"content": file:read %0 | text:creole,
"title" : %0 | path:trunk
} | print /show-wiki.html
};
};

小さなモジュールでも、はじめて見ると複雑な印象を持つかも知れません。単純化してみましょう。specdocsと名付けられたモジュールには、2つのリソースクラスが含まれます。


/** specdocsアプリケーションのリソース&アクション
*/
module specdocs in cara;

/** ディレクトリ・リソース
*/
resource Dir("/" | "*/") {

// ...

};


/** Wikiファイル・リソース
*/
resource Wiki("*.wiki") {

// ...

};

Dirという名前のリソースクラスに注目すると:


/** ディレクトリ・リソース
*/
resource Dir("/" | "*/") {

/** ディレクトリへのGETリクエスト */
action dir-get("/GET")
// ...

};

dir-getというアクションがあります。Dirリソースクラスは、このアクション1つしか持ちません。同様に、Wikiリソースクラスもwiki-getという1個のアクションだけで出来ています。

Catyのコンソールヘルプで見ると、次のような感じです(見た目がショボい(苦笑))。

定義が書いてないモジュール

モジュールには、アプリケーションを構成する状態(画面)、リソースクラス、アクションを記述します。実際に動作するのはアクションですが、このアクションはCatyScriptといスクリプト言語を使って書きます。

ところが、アクションの内容は書かなくてもCatyは何も文句を言いません。先の例題「ユーザー管理業務」を記述したモジュールでも、アクションの中身は何も書いてません。以下に実物を示しましょう。


/** ユーザー管理業務モジュール */
module user-mgr in cara;


/* == 型定義 == */

/** ユーザーリストの型 */
type UserList = deferred; // 後で定義

/** 登録UIが持つデータの型 */
type RegisterUI = deferred;

/** 登録されるユーザー情報の型 */
type UserInfo = deferred;

/** 登録完了時に戻されるデータの型 */
type Result = deferred;


/* == リソースと画面(状態)の定義 == */

/** メニュー画面の生成 */
resource Menu ("/menu.html") {
action get("/GET")
:: @[in, out] #1 void -> void produces menu ;
};

/** メニュー画面 */
state menu :: void links {
+ to-list --> List.get;
+ to-register --> Register.get;
+ logoff --> DummyLogon.get;
};

/** ユーザー一覧の生成 */
resource List ("/list.cgi") {
action get("/GET#dont-care")
:: @[in, out]#1 void -> UserList produces list ;

action next("next/GET#dont-care") {"current": integer}
:: @[in, out]#1 void -> UserList produces list ;

action prev("prev/GET#dont-care") {"current": integer}
:: @[in, out]#1 void -> UserList produces list ;

action go-to("go-to/GET#dont-care") {"current": integer, "target": integer}
:: @[in, out]#1 void -> UserList produces list ;

};

/** ユーザー一覧画面 */
state list :: UserList links {
+ list --> List.get;
+ next --> List.next;
+ prev --> List.prev;
+ go-to --> List.go-to;
+ menu --> Menu.get;
};

/** 登録画面の生成 */
resource Register("/register.html") {
action get("/GET")
:: @[in, out]#1 void -> RegisterUI produces register ;
};

/** 登録画面 */
state register :: RegisterUI links {
+ do-regster --> DoRegister.do;
+ menu --> Menu.get;
};

/** 登録処理実行 */
resource DoRegister("/do-register.cgi") {
action do ("/POST#dont-care")
:: @[in] #in UserInfo -> _ relays [ok, ng],
@[out]#ok _ -> Result produces result,
@[out]#ng _ -> RegisterUI produces register
;
};

/** 結果表示画面 */
state result :: Result links {
+ menu --> Menu.get;
};

/** ダミーのログオン画面の生成 */
resource DummyLogon ("/dumy-logon.html") {
action get("/GET")
:: @[in, out]#1 void -> void produces dummy-logon ;
};

/** ダミーのログオン画面 */
state dummy-logon :: void links {
+ menu --> Menu.get;
};

アクションがどう動くかの手続きは一切書いてありません。全体の構造が把握しやすいように、リソースクラスとアクションも含めた図をCatyに描かせてみましょう。

緑の箱がリソースクラスで、そのなかに入っている薄緑の丸がアクションです。画面を表す黄色の四角から出ている紫の線がHTTPリクエスト、サーバー側にいるアクションから出ている赤い線がHTTPレスポンスです。

何をすべきかはナントナク予想できる

モジュールに手続きを一切書いていませんが、宣言は丁寧にしています。先に挙げた画面遷移図やリソース&アクションの構成図は、モジュール内の宣言だけに基づいて描いています。

もっと局所的な情報、例えば「Menu.get というアクションを呼び出している状態(画面)達」を知りたいなら次のようにします。

さらに、アクション定義が書いてないにもかかわらず、アクションが何をするのか?分かってしまうようなケースもあります。ユーザー登録を実際に行うアクションであるDoRegister.doを見てみましょう。

どうですか? 次のようなことがソコハカトナク読み取れます。

  1. register画面からdo-registerというボタン(かなんか)を押して、DoRegister.doを呼び出す。
  2. このとき、フォーム(かなんか)で、UserInfo型のデータをサーバーに送る。
  3. UseInfo型データを受け取ったDoRegister.doは、入り口(in)でそれが正しいかどうかバリデーションする。
  4. バリデーションが成功(ok)だったら、Result型データの情報を持ったresult画面を表示する。
  5. バリデーションが失敗(ng)だったら、RegisterUI型データの情報を持ったregister画面をもう一度表示する。

僕ら人間が、この程度のことをたやすく推測できるってことは、ある程度賢いプログラムなら同じことを推測できるでしょう。そのような推測を実際に行なって、定義が何も書いてないモジュールからでも動作するWebアプリケーションを瞬時に作り出してしまう機能 -- それがインスタントモックアップです。

どんなに中途半端・不完全な設計でも動かす

モジュールは、Webサイト/Webアプリケーションの仕様だとも言えます。しかし、単なる文書や指示ではなくて、それは実装の一部に組み込まれて動作します。設計成果物であると同時に、実行されるソースコードなのです。

インスタントモックアップとは、実装作業が何ひとつ出来てなくても設計成果物さえあればプロトタイプが動く、ということです。もちろん、アクションがちゃんと書いてあればその書かれたスクリプトが動作します。スクリプト部分に何も書いてなかったりコメントアウトされていると、それらしい動作をCatyが作り出します。(そうなる予定

こう言うと、「ソースコード自動生成か」と思う人がいるでしょうが、そうではありません。ソースコード自動生成は弊害もあり、僕はいい方法だとは思っていません。「それらしい動作」は、CatyScriptのインタプリタがその場で考えて行うのです。

今、「設計成果物が動く」と言いましたが、その設計成果物が完成品である必要はありません。そもそも何をもって完成なのか分かりませんしね。Catyは、モジュールの整合性チェックをゆるやかに行うので、行き先のないリクエストや、未定義の型(deferred型)があってもエラーとはしません。なにかしらモジュールに書いてあれば、書いてある分だけの情報でそれなりに(モジュールの不完全さを反映した不完全さで)動くでしょう。(そうなる予定


以上に述べたことは構想に過ぎません。が、夢想ではありません。かなり強力な型システム、たいていの処理は書けるようになったスクリプト言語とそのインタプリタ型推論機構(これは未完)、型付きテンプレートエンジン、可視化ツールのような周辺ユーティリティ、などは既に存在します。「プロトタイプ作成の労力を、百分の一にする」は、誇張が含まれるという意味でホラですが、ウソやデタラメではないだろうと思っています。

*1:実際には、静的HTMLファイルやテンプレートなどの担当者が責任を持つリンクにプラスが付いています。

*2:正確には、cara(キャラ)モジュールと呼ばれるものですが、今回の話に出てくるモジュールはcaraモジュールだけです。