この記事はTitanium™ Advent Calendar 2013の25日目の記事です。
24日目はryugooさんのTitanium 3.2 の本筋から微妙にずれた話でした。
Titaniumでのコンシューマアプリケーションの実装は、パフォーマンスや見た目のキャッチーさなどに多少の問題があると思われていて、実際、痒いところに微妙に手が届かなかったりして困ったものです。
でも、実はTitaniumの一番の使い所はインハウスツールやビジネス用アプリケーションだと思うのですね。AppceleratorもTi.Nextで、よりコンシューマアプリケーションの要望に応えやすいような方向性を打ち出していますが、Mobile Enterpriseへのコミットメントを強くしているようにも思われます。
今回紹介するのはMobile Enterprise向けのコードジェネレータRutileです。
Advent Calendarの最終日ですが、まだ埋まっていなかったので急遽公開しこちらに投稿させて頂きました。乱筆乱文ご容赦下さい。
一通りサンプルが動く状態ではありますが、まだコードにはバグがあったりしたりします。また、ドキュメントも整備されていません。
ここでは、Rutileの概要と、コンフィグレーションの補足をさせていただきたいと思います。
※何となく記事の外観はTitaniumについて話しているようには見えませんが、生成されるものはAlloyで出来たTitaniumのアプリなのです。Alloyの記述感とかはこちらに。
Rutileの概要
Titanium/Node.js/PostgreSQLという構成に依存したジェネレーターなので、その頭文字を取って名前にしようかと思ったのですが、どうも日本語での発音が思わしくないので、チタン合金的な名前にしてみました。
Rutileはトラディッショナルなコンテナを提供するサーバサイドフレームワークと、それに基づいたサーバアプリーション、またそれに接続してデータを検索・操作するUIとしてTitaniumアプリケーションを生成します。
開発環境はMavericks(MacOS X 10.9)を前提としています。これはiOSアプリの出力のみを対象にしているためで、現状Androidで業務環境を構築する理由がないのではないかという理由もあります。(もちろん、私が生粋のマカーであることも大きく影響していますが)
サーバサイドはNode.jsですが、データの永続化機構としてのRedisとPostgreSQL/PostGISに同期接続します。これによってビジネスアプリケーションで必要になるトランザクションの管理やアイソレーションを簡単にしています。一方、Node.jsのランタイムはシングルスレッドなので通常のスケーラビリティは失われます。スケールさせるためには伝統的なプロセスベースの手法を利用することになります。またビジネスロジックと無関係な部分はNode.js的な非同期で分離しても大丈夫です。
バックエンドがMySQLではなくPostgreSQLなのは、トランザクションの管理が、より業務アプリケーションに適しているからです。また、モバイルアプリケーションに必要な位置情報の扱いもPostGISを利用することで容易になるためでもあります。同期接続ドライバのあるDBに対してなら移植は容易と思いますし、コネクションを抽象化してもよいかなとは思っています。
サーバ・クライアント双方がJavaScriptであるからには、モデルクラスを共有するべきだと思うのですが、そうは問屋が卸さなかったため別々の実装になっています。
コード生成の出発点は、アプリケーションとデータベース定義を記述したファイルになります。ここを出発点として演繹的にクライアントUIまで自動生成します。
サーバ・クライアント共に、全てのSCRUDパターンをカバーするように構築されるので、生成されるコードは非常に冗長です。でも、これは所謂KitchenSinkで、必要なモジュールやコードをここから選択しつつ、自分のアプリケーションを構築するためのコードベースとして使って頂ければと思います。
そのために十分な品質のコードであるかどうかはまた別の話ですが、顧客へ即座にデモが出来るのは実物を見るまで理解して貰えない多くのシーンに於いて役立つのではないかと思います。
以下簡単に設定ファイルの記述方法を紹介します。
コンフィグレーション : Config.txt
Githubにはまだコンフィグレーションのマークアップマニュアルがないので、こちらでザックリと。
サンプルと見比べつつご確認下さい・・・
APP_NAME : DemoShop
SelectAllLimit : 1000
DefaultLanguage : en
Map : Tokyo
GeoTools_DefaultRegion_longitude : 139.78317260742188
GeoTools_DefaultRegion_latitude : 35.65126037597656
GeoTools_DefaultRegion_longitudeDelta : 0.439453125
GeoTools_DefaultRegion_latitudeDelta : 0.46421724557876587
GeoTools_DefaultLongitudeDelta : 0.003433227539
GeoTools_DefaultLatitudeDelta : 0.003635423956
@Product.txt
以下の要素を定義します。
キー | 値 |
---|---|
APP_NAME | アプリケーション名 |
SelectAllLimit | select * 時のリミッタ |
DefaultLanguage | jaまたはen |
Map | 定義済み位置(とりあえずTokyoだけ) |
GeoTools_DefaultRegion_longitude | Ti.Mapを呼んだときに表示するリージョン。Mapと排他。 |
GeoTools_DefaultRegion_latitude | 同上 |
GeoTools_DefaultRegion_longitudeDelta | 同上 |
GeoTools_DefaultRegion_latitudeDelta | 同上 |
GeoTools_DefaultLongitudeDelta | Ti.Mapを位置付きで呼んだときのdelta |
GeoTools_DefaultLatitudeDelta | 同上 |
@FILE | スキーマファイルのインポート |
スキーマ定義
フォーマットはタブ区切りにしましたがアラインメントが環境依存になってしまうので、マークダウンのテーブルと同じようにしようかと考え中。(でもそれだとタイプ数が増えるのだけどどうしたものでしょう。)スキーマ構築用のUIを付ける手もありますが、キーボードから手を放さずに全てが出来ることを善とするべきなのかも知れません。
手の早い人はエクセルで適当なマクロを書いてみたらよいのではないかと思います。
=== Product (商品) ===
# Product (商品)
aggregate:Product/ProductProductImage
sequence:productSeq(1000)
field* type* name* search* valid* tags
—————————————-—+—————-—+—————-——+—————————-————————+———————————————+——————————————
productID int4 商品ID key notNull
productName(*) text 商品名 key,like,orderby notNull
price int2 価格 num,orderby positiveValue
productClassID int 商品区分ID key notNull helper:Product/ProductClass
productStatusID int 商品状態ID key notNull helper:Product/ProductStatus
depositoryID int 保管場所ID key - helper:Product/Depository
registerDate timestamp 登録日 timestamp,orderby timestampString
* ProductProductImage (商品商品画像)
collector/collected* type*
---------------------------------------+-----
Product/Product.productID int4
Product/ProductImage.productImageID int4
※要素の区切りは本来タブです。表示の都合で上記引用はスペース区切りになってます。
行頭: ===
- スキーマ定義の始まり。
- データベース名の最初が大文字になったものを定義します。
- 括弧内はUI上の名称。
行頭: #
- テーブル定義の始まり。
- テーブル名の最初が大文字になったものを定義します。
- 括弧内はUI上の名称。
行頭: *
- コレクション定義の始まり。
- 所謂ジャンクションテーブルです。
- aggregateがあるなら定義する必要あり。
- コレクトするテーブル名とコレクトされるテーブル名を繋げた名前にする。
aggregate
- 当該エンティティの主キーが他のエンティティと一対多の関係にあることを現します。
sequence
- 主キーの生成元。
行頭: TAB
- タブが始まると当該テーブルのフィールド定義が始まったと認識。
- 最初の2行はコメント扱いで無視されます。
フィールド定義
- 左から順に以下に記述。
- 最初のフィールドは主キー。
field
- テーブル上のフィールドの物理名。
- (*)をフィールドの最後に付けるとこのエンティティの文字列表現として使われます。どれか一つに付ける必要あり。
- (i)をフィールドの最後に付けるとこのエンティティの画像表現として使われます。type:imageを同時に指定する必要あり。(オプション)
type
- テーブル上のデータタイプ。
- imageを指定するとMIMEエンコーディングされた画像を保存するようになります。
- geographyを指定すると位置を記録するようになります。
name
- UI上の名称。
search
- 検索ロジックの生成を指定。
- key : 全一致検索を生成
- like : 部分一致検索を生成
- num : 数値の範囲検索を生成
- timestamp : 時刻の範囲検索を生成
- nearby : 位置的近傍検索
- area : エリア内のレコードを検索
- orderby : 降順昇順のモジュールを生成
- join : 当該フィールドを外部キーとして結合先のフィールドでの検索を生成。tags:helperと同時に使用。
ここまでが必須定義。
valid
- フィールド値のバリデーション方法を指定。
- notNull : ヌルにならないように
- positiveValue : 正値を要求
- negativeValue : 不値を要求
- timestampString : 時刻を現す文字列であることを要求
- geographyPoint : PostGISのPOINTを要求
tags
- その他の生成要求を定義。
- helper : 外部キーの参照先エンティティを指定。DBセグメントが違うとdblinkで結合。
- editor : textフィールドのUIをtextAreaにるすかtextFieldにするか。
- autoset : UIイベントの処理に対するコードの生成に対してインジェクションを行う。3rd_step参照
今後の予定
バグ取ります。あと、とりあえずドキュメント書いて見ようかと。
来年はTi.NextのUIを出力するように書き換えようかな。
では、埋まって良かったメリーメリークリスマス!!!
そして良いお年を!!!