2010年12月15日水曜日

Androidのアーキテクチャ

このところAndroidに集中して取り組んでいるのですが、その作業の中で、Androidアプリのアーキテクチャとして使えそうなものが見えてきたのでメモしておきます。

図で赤くなっているオブジェクトは、Androidの基本クラスでAndroid OS上で特別な機能を担うものです。これらのオブジェクトをベースとして、どのようなオブジェクト群を追加し、どのような責務配分をして全体をバランスさせるのかという点が論点となります。

このアーキテクチャでは、ControllerとModelという2つのクラスを導入して、上記の配線でActivity、Handler、Thread、Serviceを結びつけています。
ここで注意が必要なのは、ControllerとModelという名前。他に良い名前がないのでControllerとModelという名前を付けていますが、気持ちとしては(MVCではなく)PACアーキテクチャパターンのControlとAbstractionを意図しています。
UIデバイス周りの低レベルイベントはActivity(PACのPresentation)で処理し、Controller(PACのC)はUX領域のセマンティックイベントの処理を行います。
一方Serviceの方は、利用者以外のアクター、すなわち外部サービスや時間といった外部事象からのイベント処理を行います。
Modelは、アプリケーションロジックを記述します。データベースやファイルそしてDriver経由で外部サービスの呼出しを行います。
外部との連携はDriverとReceiverの2つのオブジェクトを用います。Driverは従来的なPull型の連携を行うドライバです。Receiverはクラウド的なプッシュ型連携を受け取るドライバです。

Androidは、ちょっと本格的なアプリケーションになると内部的には非同期メッセージを駆使したプログラミングモデルになってくるので、そのあたりをどうさばくのかというがアーキテクチャを考える上での軸の一つとなります。
Real-Time UML的にはタスク設計的なことも必要になります。

この図では直接見えてきませんが、このアーキテクチャは、Androidのスレッド構造にも配慮しています。肝になるのが、GUI用スレッドがGUIリソースを専有している点。Handlerが鍵となるオブジェクトです。


また、クラウドとスマートデバイスを統合したトータルでのアプリケーションアーキテクチャとの中でも機能するアーキテクチャである点も重要です。
この点は、非同期メッセージをプログラミングモデルの基盤に据えることで要件を満たせると考えており、その点をアーキテクチャに盛り込んでいます。

2010年11月27日土曜日

アプリケーションアーキテクチャとデータベース

引き続き右のクラウドアプリケーションアーキテクチャについて考えています。

JJUG CCC 2010 Fallでは、CQRSアーキテクチャパターンの動きを下の図を用いて説明しました。
これを実現する場合、Commandによる更新系とQueryによる問合せ系で、物理的なデータベースを分けるのがよいわけですが、さらにデータベースのシステムも性質に合わせたものを適材適所で選択するとより良い結果が得られるはずです。

問合せ系のデータベースとしては、ここまでの議論からカラム指向データベース/HBaseが候補となっています。

Commandとして投入されたEventはコミットログ的な実現方式で、データベースに格納します。このデータをここではEventログと呼ぶことにします。Eventログを格納するデータベースは、通常オペレーションではappend-onlyなので、分散ISAM的なシンプルなデータベースが向いています。shared nothingなのでshardingでスケーラビリティを確保するのも容易です。このあたりの性質を軸にデータベースを選択することになります。append-only&shardingに強い、更新が高速で信頼性の高い分散KVSが候補となりますが、まだ見つけていないので保留。現時点ではRDBMSを(ISAM的に)使うのがよいかもしれません。

問題は、Eventログの内容をカラム指向データベースに反映するところです。
カラム指向データベースは、問合せに強い反面、更新に弱い性質があります。このため、カラム指向データベースを通常のトランザクション向けデータベースとして使うのは、あまり得策ではありません。

本アーキテクチャでは、このギャップをドメインサービスにおいて、トランザクションデータの格納にインメモリデータベースを使うことで埋めることが眼目の一つになっています。インメモリデータベースはVoltDBが候補になっています。
アプリケーション実行中のトランザクションは、インメモリデータベースに格納・管理されます。カラム指向データベースはあくまでもインメモリデータベースのバックアップなので、更新に多少の時間がかかっても問題ありません。
また、データの永続性はEventログのデータベースによって担保されるため、バックアップデータベースであるカラム指向データベースへのデータ更新は、インメモリデータベースへの更新とは非同期に、バックグラウンドでゆっくり行っても大丈夫というわけです。

以上のように、分散KVS(またはRDBMS)、インメモリデータベース、カラム指向データベースを適材適所で組合わせてアプリケーションを構築すると面白い結果が得られそうです。
まだまだ机上の議論ですが、この方向で考察を深めていきたいと考えています。

2010年11月26日金曜日

データベースの選択

カラム指向データベースにHBaseを使うと、Hadoopとの連携がスムーズになるので、バランスがよさそうというお話をしました。

こうなると、インメモリデータベースも具体的に考えてみたくなります。

当初は、SQLiteのインメモリデータベースモードを使って、自分でメモリクラスタを組むようなことを考えていたのですが、色々調べてみるとどうもVoltDBがよさそうに思えてきました。

VoltDBはRDBMSなので、SQLの豊富なデータ操作機能を用いることができます。
いわゆるKVSを使うと、アプリケーション側で相当の作り込みが必要なので、この点でRDBMSは安心感があります。

VoltDBは、自動的にメモリクラスタを組んでくれるみたいなので、この点でもアプリケーションは何もしなくて済みそうです。
メモリクラスタによってスケーラビリティを高めるとともに、信頼性を向上させることができます。

VoltDBではJavaを使ったストアドプロシジャとしてプログラムを書く必要があるので、この点がちょっとクセのあるところです。
たとえば、本アーキテクチャの場合、ドメインサービスだけでなく、アプリケーションサービスもストアドプロシジャとして実現するような実現方式も検討が必要になります。
この点に問題がなければ、かなり有力な選択肢ではないかと思います。

インメモリデータベースの弱点は、メモリクラスタが完全にダウンした場合の永続性の確保と、大規模データがメモリに載り切らないリスクです。
本アーキテクチャでは、Eventログを用いて永続性は確保できているので、メモリにデータが載り切る場合には、Eventログの分散KVS(または普通のRDBMS)とVoltDBだけでシステムが組むこともできそうです。(AWSの場合、メモリモデルとして7.5GB, 15GB, 34.2GB, 68.4GBが用意されており、一般的なアプリケーションではメモリのみで運用することが可能です。すでにそういう時代に入っています。)
ただし、メモリクラスタ再起動時にEventログから最新の状態を復元するのに時間がかかりそうなので、何らかのチェックポイントをどこかに保存して置く必要があります。
そうなると、結局のところバックアップデータベースとしてHBaseを併用するのがバランスがよさそうです。

図の左側にあるマスターデータはローディングが早ければ何でもよいので、HBaseに相乗りで十分でしょう。

以上の考察から、今の所:

  • Eventログ用DB:append-only&shardingで高速書き込みできる分散KVSの何か
  • ドメインサービスのインメモリデータベース:VoltDB
  • バックアップデータベース&Hadoop:HBase
  • マスターデータベース:HBase(に相乗り)
というのが面白そうと考えています。

2010年11月25日木曜日

Hadoopの置き場所

土曜日に書いたアーキテクチャは、Hadoop座談会で得られたインスピレーションによるものでしたが、肝心のHadoopの置き場所を考えるのを忘れていました。
そこで、考えてみたのが以下の図です。


アーキテクチャ的には、MapReduceやその実装としてのHadoopだけでなく、より汎用的な概念の導入が有効ではないかということでBackground Computingとしています。
さらに細かく考えていくと、Background ComputingもBatchとRealtime(Streaming)に分けることができそうですが、これはいずれ。

ボクは、クラウドアプリケーションとは、非同期に発生するイベントを連続して受け取りながら、イベント受信をうけて内部状態を刻一刻と遷移させていくオブジェクトと考えています。右の図はこのあたりをJJUG CCC 2010 Fallでお話した時に使ったものです。
この中で、インデクサとしているものが、上の図のBackground Computingに相当します。
クラウドアプリケーション自身はイベント駆動で動作しますが、その応答性能や結果精度を向上させるために、必要な情報をバックグラウンドで常に作り続けているのも、クラウドアプリケーションのもうひとつの側面になります。
この部分の実現機構として、MapReduce/Hadoopが重要な選択肢というわけですね。

Hadoopを組み合わせるということで具体的に考えてみると、HBaseはカラム指向データベースということに思い至りました。アーキテクチャ素案では、ドメインサービスのメインデータベースはインメモリデータベースで、このバックアップとしてカラム指向データベースを併用しています。このバックアップデータベースをHBaseとすることで、Hadoopとの連携をシームレスに行う事ができるようになります。

Background Computingの結果は、バックアップデータベースに直接戻すケースと、イベントとして通常のデータ更新ルートに載せるケースの両方が必要でしょう。
そのあたりを図に追加しています。

2010年11月20日土曜日

クラウドアプリケーションのアーキテクチャ素案

きのうはHadoop座談会(第3回)。佐藤先生と萩原さんのお話を聞くことができました。
並行・並列・分散技術。RDBMS JoinからDryad。旬のテーマで参考になる点が多々ありました。
これに加えて、懇親会で萩原さんからお聞きしたカラム指向データベースのお話が刺激的。クラウドアプリケーション・アーキテクチャのインスピレーションが浮かんだので、図にまとめてみました。


CQRS、Event Sourceのアーキテクチャの上で:

  • 各種データベースの選択とアーキテクチャ責務分割
  • プレゼンテーション、アプリケーション、ドメインの各サービスのアーキテクチャ責務分割
  • 可用性の担保
  • スケーラビリティの担保
といったものを織り込んでいます。
Domain Serviceのクラスタを構築してin memory databaseでワークデータの管理をするのが面白いかなと思っているのですが、Domain Service初期化時にすべてのデータをメモリにローディングするのは現実的ではないので、バックエンドのデータベースが必要となります。ここにカラム指向データベースがぴったりはまるのではないか、というのがインスピレーション。RDBMSでもよいところですが、スケーラビリティを高めるにはカラム指向データベースという選択が有効と考えられます。
エージェント,というとやや大げさですが、ストアドプロシジャとかクロージャとか、そういった技術でデータの近くで処理を行って結果を返す機能が必要になるのは明らかなので、そのあたりもアーキテクチャに取り込みたいところ。ここは、Domain Service界隈で実現できるのではないかと考えています。
他にも色々と話題があるので、この図をもとにブログで実現方式を整理していこうと思っています。

2010年10月13日水曜日

[g3]データストア3

金曜日の続きです。
日曜日に公開したg3 0.2.1でg3アプリケーションDataStoreCrud.scala(以下に再掲)を実行してみましょう。

DataStoreCrud.scala
package org.goldenport.g3.app

import org.goldenport.g3._
import org.goldenport.g3.atom._
import org.goldenport.g3.messages._
import org.goldenport.g3.messages.datastore.{Create, Fetch, Query, Insert, \
    Update, Delete, Drop}

class DataStoreCrud extends G3Application with UseRecord {
  val KIND_NAME = 'g3crud
  val schema = Schema(
    IdField,
    ('name, XToken),
    ('zip, XToken),
    ('address, XString),
    ('phone, XToken, ZeroMore),
    ('comment, XString))

  datastore('appds)

  val create = Create(KIND_NAME, schema)

  val fetch = Fetch(KIND_NAME, 5)

  val query = Query(KIND_NAME, Id(5))

  val insert = Insert(
    KIND_NAME,
    Record('id -> 5, 'name -> "Yamada Taro",
           'zip -> "1234567", 'address -> "Yokohama",
           'phone -> "0451234567", 'comment -> "omlet rice"))

  val update = Update(
    KIND_NAME,
    Record('id -> 5, 'name -> "Suzuki Hanako"))

  val delete = Delete(KIND_NAME,
                      Record('id -> 5))

  val drop = Drop(KIND_NAME)

  port("/create") agents(create) invoke("appds")
  port("/fetch") agents(fetch) invoke("appds")
  port("/query") agents(query) invoke("appds")
  port("/insert") agents(insert) invoke("appds")
  port("/update") agents(update) invoke("appds")
  port("/delete") agents(delete) invoke("appds")
  port("/drop") agents(drop) invoke("appds")
}

org.goldenport.g3.app.DataStoreCurdはg3に組み込まれているので、以下のように実行します。

$ g3 -g3.application:org.goldenport.g3.app.DataStoreCurd \
    -g3.server

ルート「/」にアクセスすると、DataStoreCurdでは対応するポートがないのでエラーとなります。(エラーメッセージは改良する予定。)



以下、Create、Insert、Fetch、Update、Query、Delete、Dropの順に実行すると以下のようになります。特に画面の定義はしていませんが、ポートチャネルを実行して結果のメッセージに応じて自動的に整形して表示するようになっています。









おまけ


g3 0.2.1では、本当は以下のようにヘッダー、サイドバー、フッターを自動的に挿入した画面が表示されるはずでしたが、バグで挿入されなくなってました。ちょっと残念。次のバージョンから自動的に挿入されるようになります。


2010年10月10日日曜日

[g3]g3 version 0.2.1

メッセージング・フレームワークg3のバージョン0.2.1を公開しました。


配布物は以下の2つです。用途に合わせてどちらかをダウンロードしてください。

  • g3-0.2-bin.zip:配布バイナリ。unzipして使用。
  • g3-0.2-jar-with-dependencies.jar:実行形式。java -jar g3-0.2-jar-with-dependencies.jarで直接使用。

g3 0.2.1は、Google AppEngine、データストア、WebUI周りの改良を行ないました。

2010年10月8日金曜日

[g3]データストア2

前回の続きです。


データストアチャネル


データストアはデータストアチャネル経由でアクセスします。

ここでは、以下のようにappdsという名前のデータストアチャネルを定義しています。


  datastore('appds)

データストアチャネルはデフォルトではg3に組み込んでいるRDBMSのDerbyを使用します。また、AppEngine上で動作させるとAppEngineのデータストアを使用します。

データストアチャネルの定義あるいは外部定義ファイルによって、任意のJDBC URLやJDBCドライバを指定することができます。ただし、現時点の実装ではデータベース固有のデータ型に対応していないので、Derby以外のRDBMSは事実上動作しないと思われます。いずれ、MySQLやPostgresなどのデータベースにもアクセス可能にする予定です。


カインドの作成


カインドの作成は、Createコマンドをデータストアチャネルに送信することで行ないます。Createコマンドには、カインド名とスキーマを設定します。


  val create = Create(KIND_NAME, schema)


  port("/create") agents(create) invoke("appds")

agentsエージェントは、メッセージを受信すると、引数に指定されたコマンドを発行するエージェントです。この場合は、Createコマンドをinvokeエージェントに送信しています。invokeエージェントは同期型でデータストアチャネルappdsにメッセージを送り、データストアアクセスの結果を受け取ります。 invokeエージェントは、このチャネルの最後のエージェントなので、invokeエージェントが受け取ったメッセージが、このチャネルの最終結果となります。


レコードのインサート


カインドに対するレコードのインサートは、Insertコマンドをデータストアチャネルに送信することで行ないます。Insertコマンドには、カインド名とインサートするレコードを設定します。


  val insert = Insert(
    KIND_NAME,
    Record('id -> 5, 'name -> "Yamada Taro",
           'zip -> "1234567", 'address -> "Yokohama",
           'phone -> "0451234567", 'comment -> "omlet rice"))


  port("/insert") agents(insert) invoke("appds")


レコードのアップデート


カインドに格納されているレコードのアップデートは、Updateコマンドをデータストアチャネルに送信することで行ないます。Updateコマンドには、カインド名とアップデートするレコードを設定します。レコードには、IDと更新するフィールドのみを設定すればOKです。

SQLの場合はUPDATE文で、AppEngineの場合は読み込みと書き戻しをデータストアチャネル側で行ないます。


  val update = Update(
    KIND_NAME,
    Record('id -> 5, 'name -> "Suzuki Hanako"))


  port("/update") agents(update) invoke("appds")


レコードのフェッチ


カインドに格納されているレコードの取り出しは、Fetchコマンドをデータストアチャネルに送信することで行ないます。Fetchコマンドには、カインド名とIDを設定します。


  val fetch = Fetch(KIND_NAME, 5)


  port("/fetch") agents(fetch) invoke("appds")


レコードのクエリ


カインドに格納されているレコードの問合せは、Queryコマンドをデータストアチャネルに送信することで行ないます。Queryコマンドには、カインド名と問合せ式を設定します。

ここでは「Id(5)」という問合せ式で「IDが5」のレコードの問合せを行っています。


  val query = Query(KIND_NAME, Id(5))


  port("/query") agents(query) invoke("appds")


レコードの削除


カインドに格納されているレコードの削除は、Deleteコマンドをデータストアチャネルに送信することで行ないます。ここでは、Queryコマンドには、カインド名とIDを設定したレコードを設定しています。


  val delete = Delete(KIND_NAME,
                      Record('id -> 5))


  port("/delete") agents(delete) invoke("appds")


カインドの削除


カインドの削除は、Dropコマンドをデータストアチャネルに送信することで行ないます。Dropコマンドにはカインド名を設定しています。


  val drop = Drop(KIND_NAME)


  port("/drop") agents(drop) invoke("appds")

次回に続きます。

2010年10月7日木曜日

[g3]データストア

g3では、RDBMSとKVSの両方に統一的にアクセスできるデータストアAPIを用意しています。

当面の目標は、同一のg3アプリケーションがJDBCとGoogle AppEngine Data Storeのどちらでも動作するようにすることです。

基本的には、Google AppEngineのデータストアを基準に、RDBMSにも対応するというアプローチのAPIになっています。Google AppEngineデータストアはかなり制約がきついので、これを基準にしておけば、将来、他のKVSあるいはNoSQLをサポートすることも比較的容易にできるのではと考えています。

org.goldenport.g3.app.DataStoreCrudは、データストアをアクセスするサンプルアプリケーションです。 このアプリケーションに沿ってデータストアの使い方についてみていきましょう。


DataStoreCrud.scala
package org.goldenport.g3.app

import org.goldenport.g3._
import org.goldenport.g3.atom._
import org.goldenport.g3.messages._
import org.goldenport.g3.messages.datastore.{Create, Fetch, Query, Insert, \
    Update, Delete, Drop}

class DataStoreCrud extends G3Application with UseRecord {
  val KIND_NAME = 'g3crud
  val schema = Schema(
    IdField,
    ('name, XToken),
    ('zip, XToken),
    ('address, XString),
    ('phone, XToken, ZeroMore),
    ('comment, XString))

  datastore('appds)

  val create = Create(KIND_NAME, schema)

  val fetch = Fetch(KIND_NAME, 5)

  val query = Query(KIND_NAME, Id(5))

  val insert = Insert(
    KIND_NAME,
    Record('id -> 5, 'name -> "Yamada Taro",
           'zip -> "1234567", 'address -> "Yokohama",
           'phone -> "0451234567", 'comment -> "omlet rice"))

  val update = Update(
    KIND_NAME,
    Record('id -> 5, 'name -> "Suzuki Hanako"))

  val delete = Delete(KIND_NAME,
                      Record('id -> 5))

  val drop = Drop(KIND_NAME)

  port("/create") agents(create) invoke("appds")
  port("/fetch") agents(fetch) invoke("appds")
  port("/query") agents(query) invoke("appds")
  port("/insert") agents(insert) invoke("appds")
  port("/update") agents(update) invoke("appds")
  port("/delete") agents(delete) invoke("appds")
  port("/drop") agents(drop) invoke("appds")
}


カインド


g3では、レコードの集りをカインドと呼んでいます。RDBMSのテーブル、Google AppEngineデータストアのカインドに対応します。

DataStoreCrudでは、データストアのカインド名としてg3curdを使用します。変数KIND_NAMEに設定しており、この変数を、プログラムの中で利用します。


  val KIND_NAME = 'g3crud


スキーマ


DataStoreCrudで使用するスキーマは、以下のようにスキーマは、Schemaリテラルで定義したものを変数schemaに設定しています。この変数schemaに設定したスキーマを、プログラムの中で利用します。


  val schema = Schema(
    IdField,
    ('name, XToken),
    ('zip, XToken),
    ('address, XString),
    ('phone, XToken, ZeroMore),
    ('comment, XString))

スキーマでは、カインドの各フィールドに対して、フィールド名とデータ型の対を定義します。たとえば、「('name, XToken)」はXToken型のフィールドnameということです。

データ型はXML Datatypeをベースにしたものをg3フレームワークで事前定義しています。 

フィールドは、フィールド名とデータ型の他に多重度、制約、ファセット、プロパティを設定する事ができます。ファセットはXML Datatypeのファセットに対応するもので、データの値域を定義します。制約との棲み分けは懸案事項で将来統廃合するかもしれません。プロパティは、SQLデータ型のVARCHARといったデータストア固有の情報を定義します。

IdFieldは、Id用のフィールドを宣言するためのリテラルです。中身は「'id, XLong, One, List(CId), Nil」となっており、フィールド名「id」、データ型XLong、多重度1、Id制約あり、プロパティなし、を簡単に設定するための文法糖衣です。

以下、明日に続きます。

2010年10月6日水曜日

[g3]マスターHTMLの構成

g3が生成するHTMLのマスターデータは以下のようになっています。(これは現在の最新。version 0.2のものと少し変わっています。)

head要素はフレームワーク側で生成するのでbody要素をデータとして用意しています。試しにHTML5のセクション関連の要素を使ってみました。

また、CSSによる修飾や、HTML生成時の置換に対応するため、必要だと思われる要素にIDを設定しています。


HTMLの雛形
<body>
<header>
<div id="header">
<div id="header-content">
<h1>{title}</h1>
</div>
</div>
</header>
<div id="container">
<aside>
<div id="aside">
<div id="aside-content"/>
</div>
</aside>
<article>
<div id="article">
<div id="article-body">
<div id="article-content"/>
</div>
</div>
</article>
</div>
<footer>
<div id="footer">
<div class="powered">
Powered by g3.
</div>
</div>
</footer>
</body>

g3では、g3アプリケーションが生成するHTML断片によって、以下の置き換えが行われます。


  • body要素まるごと
  • 指定したIDの要素
  • ID article-contentのdiv要素

body要素まるごとの場合は、前述のマスターHTMLは使用されなくなります。

HTML断片のルート要素にIDが指定されていて、マスターHTMLの要素のIDと一致したときは、マスターHTMLの該当する要素がHTML断片に置換されます。 

それ以外の場合は、IDがarticle-contentのdiv要素がHTML断片に置換されます。 

このため、g3アプリケーション側ではアプリケーションとして表示したいコンテンツのみの作成を行うだけで、ヘッダー、サイド、コンテンツ、フッターから構成されるWebページのコンテンツに自動的に埋め込まれます。

一般的には、最後のarticle-contentのdiv要素に置換する方法が使われることになると考えられます。

今回、色々と調べてみて、HTML5はAjaxでGUI的な処理を行わない場合でも便利に使える、色々な機能が地味に拡張されてことが分かりました。そういうこともあり、g3ではWeb UIはHTML5を軸に機能を作り込んでいく予定です。

2010年10月5日火曜日

[g3]Web UI

g3 version 0.2では、Web UIを改良しました。
基本組込みのCSSを用意することで、アプリケーション側でCSSの設定をしなくてもそれなりの見栄えのWebページを表示できるようにしました。元々、マスターのHTMLは、以下の4つのペインを設定していたのですが、CSSの設定を行っていなかったので、見た目は何もしていないのと同じ状態でした。マスターのCSSを用意することでこの点が改善したわけです。

  • ヘッダー
  • サイド
  • コンテンツ
  • フッター
g3アプリケーションが生成するHTMLは、このマスターHTMLのコンテンツ・ペインに埋め込まれますが、他のペインも置き換え可能です。
それでは効果をHelloWorldで確認してみましょう。
HelloWorld.sdoc
package org.goldenport.g3.app

import org.goldenport.g3._
import org.goldenport.g3.messages._

class HelloWorld extends G3Application {
  port("/") agent {
    case _ => "Hello World"
  }
}
以下のように実行してみましょう。
$ g3 -g -g3.application:org.goldenport.g3.app.HelloWorld -g3.server


version 0.1の時は以下の表示だったので,ずいぶん見栄えが良くなりました。



在はマスターのHTMLとCSSは固定ですが、いずれパラメタで指定できるようにする予定です。

AppEngineの設定

どうもユーザガイド的なものをである調で書くと書きにくいのでg3関連は文体を変えることにしました。

さて、g3 version 0.2では、Google AppEngineをサポートしました。

今回はEclipseによる開発環境でg3アプリケーションをGoogle AppEngine上で動作させるセットアップ手順について説明します。


前提


本記事の前提として以下の環境が整っていることとします。


  • EclipseによるGoogle AppEngine開発環境
  • g3アプリケーションのインストール(g3-0.2-bin.zipを展開またはg3-0.2-jar-with-dependencies.jarの配備)

JARファイル

g3アプリケーションのコンパイルや実行に必要なJARファイルを開発環境にコピーします。

コピー先はwar/WEB-INF/libです。コピー後、BuildPathの設定を行ない、コピーしたJARファイルをライブラリとして有効にします。

コピー元には以下の2つの選択肢があります。

  • g3-0.2-bin.zipを展開したディレクトリのlib配下にあるすべてのJARファイル
  • g3-0.2-jar-with-dependencies.jar

後者のg3-0.2-jar-with-dependencies.jarを一つだけコピーする方法が簡単でよいのですが、本番環境に配備時にJARファイルが大きすぎるというエラーになります。このため、開発環境でちょっと試したい場合のみに利用するとよいでしょう。

g3アプリケーションを作成

簡単に試す場合には、本ブログで紹介している基本組込みのサンプルプログラムを使ってもよいでしょう。

web.xml

サーブレットの設定ファイルwar/WEB-INF/web.xmlを設定します。

サーブレットとしてorg.goldenport.g3.servlet.AppEngineServletを設定します。また、g3アプリケーションは、サーブレットのパラメタg3.applicationに指定します。以下の例ではorg.goldenport.g3.app.HelloWorldを指定しています。

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <!-- Servlets -->
  <servlet>
    <servlet-name>g3</servlet-name>
<servlet-class>org.goldenport.g3.servlet.AppEngineServlet</servlet-class>
    <init-param>
      <param-name>g3.application</param-name>
      <param-value>org.goldenport.g3.app.HelloWorld</param-value>
    </init-param>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>g3</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
  
  <!-- Default page to serve -->
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>

</web-app>

application-web.xml

続けてAppEngineの定義ファイルであるwar/WEB-INF/application-web.xmlを設定します。必要最小限の設定の場合は、以下のようになります。

application-web.xml
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
  <application>yourappname</application>
  <version>1</version>
</appengine-web-app>

この例では、AppEngineのアプリケーション名としてyourappnameを指定しています。このため、配備後はhttp://yourappname.appspot.comでアプリケーションを実行することができます。

配備

以上で設定は完了です。 配備ボタンを押して、アプリケーションを配備します。

まとめ

g3アプリケーションをGoogle AppEngine上で動作させる上で一番難しいのが必要なJARファイルの複写のところです。それ以外はごく普通のAppEngineアプリケーションの設定を行うだけです。

サーブレットとしてorg.google.g3.servlet.AppEngineServletを使用することと、サーブレットパラメタg3.applicationにg3アプリケーションのクラス名を記述するのがポイントです。

2010年10月3日日曜日

g3 version 0.2

メッセージング・フレームワークg3のバージョン0.2を公開した。


  • g3-0.2-bin.zip:配布バイナリ。unzipして使用。
  • g3-0.2-jar-with-dependencies.jar:java -jar g3-0.2-jar-with-dependencies.jarで直接使用。

g3 0.2では、Google AppEngineをサポートした。同じg3アプリケーションが、LAMP上でもAppEngine上でも動作する。

また、データストアやWeb UIの改良も行っている。

明日から、各機能の使い方を順に説明していく。

2010年10月2日土曜日

Hello Web & CLI

HelloWorldやHelloCliでは、"Hello World"という文字列を生成するだけだったので、Webアプリケーションとしては最低限動くというレベルだった。
g3アプリケーションHelloWebでは、Web用にHTMLを生成することで、この問題を解決することができた。
その一方で、HelloWebをコマンドラインから実行してもHTMLが出力されることになる。
Webから実行した場合はHTML、コマンドラインから実行した場合は文字列を出力する、といったように起動方法によって出力するフォーマットを変えたいところである。
この目的を達成するためにHelloWebを拡張したものが以下のHelloWebCliである。

HelloWebCli.sdoc
package org.goldenport.g3.app

import org.goldenport.g3._
import org.goldenport.g3.messages._

class HelloWebCli extends G3Application {
  agent('hello) {
    case _ => "Hello World"
  }

  html('web, "Hello") {
<body>
<h1>Hello</h1>

<g.string/>

</body>
  }

  start invoke("hello")
  port("/") invoke("hello") invoke("web")
}

コマンドラインから起動したときはstartチャネルが、Webからパス「/」をアクセスしたときはportチャネル「/」が実行されることを利用して、startチャネルにはコマンドラインから実行した時の処理、portチャネル「/」にはWebから実行した時の処理を記述している。
ただし、コマンドラインからでもWebからでもアプリケーションロジックは変わらず、出力フォーマットのみが変わるようにしたいので、アプリケーションロジックのみを実行するagentチャネル「hello」を定義している。agentチャネル「hello」は文字列"Hello World"を返す。
コマンドラインからstartチャネルが呼び出されると、agentチャネル「hello」が呼び出され、その結果返された文字列"Hello World"がコンソールに出力される。
一方、portチャネル「/」が呼び出されると、つまりWebのパス「/」が呼び出されると、invokeエージェント経由でagentチャネル「hello」が呼び出される。その"Hello World"の文字列が返されるけれど、この文字列がパイプラインを経由してhtmlチャネル「web」に渡されて、HTMLが生成され、最終的にこのHTMLがHTTPのレスポンスとしてクライアントに返される。このようにパイプライン上にメッセージを流していくのが、g3のプログラミングモデルである。
htmlチャネル「web」ではHTML文書内に「<g.string/>」のタグが記述されている。このタグはhtmlチャネルの入力となった文字列に置換される。この場合は"Hello World"ということになる。

実行


それでは、org.goldenport.g3.app.HelloWebCliを実行してみよう。HelloWebは、g3フレームワークに同梱しているので直接実行することができる。
g3.serverスイッチを用いてJettyベースのWebアプリケーションとして実行することができる。

$ g3 -g3.application:org.goldenport.g3.app.HelloWebCli -g3.server

トップページにアクセすると以下のようにチャネルwebで定義したWebページにチャネルhelloで生成した文字列"Hello World"を埋め込んだものが表示される。



コマンドで実行


次はHelloWebCliをコマンドラインから実行してみよう。
以下のように、コンソールには「Hello World」の文字列が出力される。

$ g3 -g3.application:org.goldenport.g3.app.HelloWebCli
Hello World

2010年10月1日金曜日

htmlチャネル

HelloWorldプログラムでは、コマンドとWebの両方で同じプログラムが実行できた。ただし、出力する情報が"Hello World"の文字列であり、Webアプリケーションとしては最低限の仕事というところ。きちんとレイアウトしたHTMLを出力したいところである。

HelloWorld.sdoc
package org.goldenport.g3.app

import org.goldenport.g3._
import org.goldenport.g3.messages._

class HelloWorld extends G3Application {
  port("/") agent {
    case _ => "Hello World"
  }
}

このような目的には、様々な情報を埋め込んでレイアウトしたHTMLを出力するためのチャネルであるhtmlチャネルを使用する。
g3アプリケーションHelloWeb.scalaでは、htmlチャネルを用いてHTMLページを定義している。
portチャネル「/」が呼び出されると、つまりWebのパス「/」が呼び出されるとinvokeエージェント経由でhtmlチャネル「hello」が呼び出され、htmlチャネル「hello」が生成したHTMLが返され、最終的にクライアントに返される。
htmlチャネルでは、ScalaのXMLリテラルを使用してHTML文書を直接記述する。また、HTMLのhead要素はhtmlチャネル側で生成するのでbody要素のみを定義すればよい。

HelloWeb.scala
package org.goldenport.g3.app

import org.goldenport.g3._
import org.goldenport.g3.messages._

class HelloWeb extends G3Application {
  html('hello, "Hello") {
<body>
<h1>Hello</h1>

Hello World!

</body>
  }

  port("/") invoke("hello")
}


実行


それでは、org.goldenport.g3.app.HelloWebを実行してみよう。HelloWebは、g3フレームワークに同梱しているので直接実行することができる。
g3.serverスイッチを用いてJettyベースのWebアプリケーションとして実行することができる。

$ g3 -g3.application:org.goldenport.g3.app.HelloWeb -g3.server

トップページにアクセすると以下のようにチャネルhelloで定義したWebページが表示される。

2010年9月30日木曜日

startチャネル

以下のHelloWorldプログラムでは、コマンドとWebの両方で同じプログラムが実行できた。

一方で、コマンドからのみ起動したいプログラムを記述したい場合もある。


HelloWorld.sdoc
package org.goldenport.g3.app

import org.goldenport.g3._
import org.goldenport.g3.messages._

class HelloWorld extends G3Application {
  port("/") agent {
    case _ => "Hello World"
  }
}

そのような場合に使用するのがstartチャネルである。startチャネルを使用したHelloWorldプログラムが以下のHelloCli.scala。

startチャネルから"Hello World"を出力するエージェントを呼び出している。


HelloCli.scala
package org.goldenport.g3.app

import org.goldenport.g3._
import org.goldenport.g3.messages._

class HelloCli extends G3Application {
  start agent {
    case _ => "Hello World"
  }
}


実行


それでは、org.goldenport.g3.app.HelloCliを実行してみよう。HelloCliは、g3フレームワークに同梱しているので直接実行することができる。

以下のように-g3.applicationスイッチでg3アプリケーションorg.goldenport.g3.app.HelloCliを指定して実行する。引数にポート「/」は指定していない。

実行の結果、"Hello World"がコンソールに出力された。


$ g3 -g3.application:org.goldenport.g3.app.HelloCli
Hello

2010年9月29日水曜日

Webアプリケーション

g3は、RESTアーキテクチャを一つの軸にしている。具体的には、メッセージフローとしてチャネルやエージェント間を流れるメッセージとしてRESTのGETやPOST、そのコンテンツとしてAtomFeedやHTMLを用いている。
g3では、GETやPOSTに対してHTMLページを返信することで、通常のWebアプリケーション的なUIを構築することができる。
具体例として、3つのWebページを配信するWebアプリケーションUsageをみていこう。

Usage.scala
package org.goldenport.g3.app

import org.goldenport.g3._
import org.goldenport.g3.messages._

class Usage extends G3Application {
  html('top, "g3 Getting Started") {
<body>
<h1>g3 Getting Started</h1>

Getting Started.
</body>
}

  html('cli, "CLI") {
<body>
<h1>Command Line Interface</h1>

Command Line Interface.
</body>
}

  html('web, "Web") {
<body>
<h1>Web</h1>

Using g3 on web browser.
</body>
}

  port("/") invoke("top")
  port("/cli") invoke("cli")
  port("/web") invoke("web")
}

org.goldenport.g3.app.Usageは、ポート「/」、「/cli」、「/web」、チャネル「top」、「cli」、「web」から構成されている。チャネル「top」、「cli」、「web」はいずれもHTMLチャネルで、HTMLページを生成するチャネルである。
htmlチャネルでは、第1引数にチャネル名、第2引数にページタイトル、第3引数にHTMLページに埋め込むHTML断片をXMLリテラルで記述する。HTMLのheader要素などはHTMLチャネルが生成する。
また、ポート「/」、「/cli」、「/web」は、それぞれチャネル「top」、「cli」、「web」を呼び出すようになっている。このため、たとえばポート「/」にアクセスするとチャネル「top」が呼び出され、チャネル「top」が生成したHTMLが返され、最終的にポート「/」の結果としてクライアントに返されることになる。

実行


それでは、org.goldenport.g3.app.Usageを実行してみよう。
Usageは、g3フレームワークに同梱しているので直接実行することができる。
まず、Webアプリケーションとして。
Usageは、以下のようにg3.serverスイッチを用いてJettyベースのWebアプリケーションとして実行することができる。

$ g3 -g3.application:org.goldenport.g3.app.Usage -g3.server

トップページにアクセすると以下のようにチャネルtopで指定したWebページが表示される。



「/cli」や「/web」にアクセすると以下のようにそれぞれ、チャネルcli、チャネルwebで指定したWebページが表示される。





コマンドで実行


Usageはコマンドから実行することもできる。
以下のようにg3.serverスイッチを指定せず、引数に「/」を指定することで、ポート「/」をコマンドから実行することができる。
実行結果として、HTMLページがコンソールに出力されている。

$ g3 -g3.application:org.goldenport.g3.app.Usage /
<html><head><title>g3 Getting Started</title></head><body>
<h1>g3 Getting Started</h1>

Getting Started.
</body></html>

ポート「/cli」、「/web」に対する実行結果は以下の通りとなる。

$ g3 -g3.application:org.goldenport.g3.app.Usage /cli
<html><head><title>CLI</title></head><body>
<h1>Command Line Interface</h1>

Command Line Interface.
</body></html>


$ g3 -g3.application:org.goldenport.g3.app.Usage /web
<html><head><title>Web</title></head><body>
<h1>Web</h1>

Using g3 on web browser.
</body></html>

2010年9月28日火曜日

HelloWorld

Hello Worldのプログラムとして以下のものを用意した。

HelloWorld.sdoc
package org.goldenport.g3.app

import org.goldenport.g3._
import org.goldenport.g3.messages._

class HelloWorld extends G3Application {
  port("/") agent {
    case _ => "Hello World"
  }
}

g3はメッセージフロー・フレームワークであり、g3プログラムはメッセージ・フローとして記述する。
HelloWorldでは、「port("/")」の後ろに「agent {…}」を記述している。これは、チャネル「port」の出力メッセージをエージェント「agent」の入力とし、エージェント「agent」の出力メッセージをチャネル「port」の出力とするという意味である。
チャネルportは、外部に公開されたチャネルでコマンドラインやHTTPで外部からアクセスすることができる。
エージェントagentは、メッセージを処理するScalaプログラムを記述するためのエージェント。Scalaのケースシーケンス句(PartialFunction)を記述する。Hello Worldでは、「すべての場合に"Hello World"」を出力する、というScalaプログラムが記述されている。
まとめると、Hello Worldは、外部に公開されたチャネル「/」にアクセスがあると、"Hello World"を返すという処理を行うプログラムである。


コマンドで実行


org.goldenport.g3.app.HelloWorldは、本体のjarに入れているので、特に設定なしで、そのまま実行することができる。
-g3.applicationスイッチにorg.goldenport.g3.app.HelloWorldを指定すると、g3フレームワーク上でorg.goldenport.g3.app.HelloWorldが実行される。-g3.applicationスイッチに続く「/」はorg.goldenport.g3.app.HelloWorldに対する引数で、実行するポートを指定している。この場合は、port("/")が実行されることになる。

$ g3 -g3.application:org.goldenport.g3.app.HelloWorld /
Hello World

実行の結果、「Hello World」がコンソールに表示された。

Webで実行


g3は、以下のように-g3.serverスイッチを指定することで、Jettyを用いてWebアプリケーションとして実行することができる。

$ g3 -g3.application:org.goldenport.g3.app.HelloWorld -g3.server

Jettyが起動され、g3サービスがWebとして公開される。使用するポートは7865である。
localhost:7865にブラウザでアクセスするとブラウザの画面に「Hello World」の文字が出力される。
g3はサーブレットを用意しているので、g3に組込みのJettyを用いなくても、サーブレットを直接TomcatなどのWebコンテナに登録して動作させることもできる。

ポイント


  • g3アプリケーションは、メッセージフローでプログラムを記述する。
  • g3アプリケーションは、そのままコマンドラインとWebアプリケーションのいずれの方式でも動作させることができる。

2010年9月27日月曜日

インストール

g3バージョン0.1は以下の場所で公開している。


配布物は以下の2つ。いずれかをインストールする。

  • g3-0.1-bin.zip:配布バイナリ。unzipして使用。
  • g3-0.1-jar-with-dependencies.jar:java -jar g3-0.1-jar-with-dependencies.jarで直接使用。

g3-0.1-bin.zip

g3-0.1-bin.zipは、ファイル一式をzipで固めたもの。jarコマンドで解凍して、g3-0.1/bin/g3を実行可能にすればインストールは完了。

$ jar xvf g3-0.1-bin.zip

パスを通した後、以下のように実行できればOK。

$ g3-0.1/bin/g3
Copyright(c) 2010 ASAMI, Tomoharu. All rights reserved.
g3 Version 0.1 (20100926)

Usage: g3 [-options] [args...]
  for more information, use -help option

今の所、ドキュメントやサンプルプログラムが整備されていないけど、いずれかのバージョンで同梱されることになる予定。

g3-0.1-jar-with-dependencies.jar

g3-0.1-jar-with-dependencies.jarは、関連するライブラリもすべてリンクした実行可能jar形式。以下のように実行できればOK。

$ java -jar g3-0.1-jar-with-dependencies.jar 
Copyright(c) 2010 ASAMI, Tomoharu. All rights reserved.
g3 Version 0.1 (20100926)

Usage: g3 [-options] [args...]
  for more information, use -help option

簡単に試したい時や、ドキュメントやサンプルプログラムが不要の時はこちらが便利。

2010年9月26日日曜日

g3フレームワークをリリース

メッセージング・フレームワークg3のバージョン0.1を公開した。


  • g3-0.1-bin.zip:配布バイナリ。unzipして使用。
  • g3-0.1-jar-with-dependencies.jar:java -jar g3-0.1-jar-with-dependencies.jarで直接使用。

今の所、ファーストインプレッション的なレベルでドキュメントもないのだけど、色々とアイデアがあるので随時機能拡張していく予定である。ドキュメントはこのBlogで書きためていく。まずは、具体的な使い方、Hello Worldといったものが必要だけど、これは、また明日。

背景

2008年後半からScalaを導入し、フレームワークGoldenportとモデルコンパイラSimpleModelerを作ってきた。GoldenportはSimpleModelerとg3の土台として使用している。

Google AppEngineを素材にして、SimpleModelerによるクラウドアプリケーションの生成を試みていたのだけれど、Google AppEngine(のようなPaaS)は汎用性が高いことの裏返しで仮想機械としての抽象度が低いため、プログラムの直接の生成対象としては、あまり適していないことが分かった。このギャップを埋めるためには何らかのフレームワークの導入が必要である。

このフレームワークに関して、メッセージフローというアイデアを軸にScala DSLを活用した面白い実現方法を思いついたのが昨年の秋。今年に入ってから、少しずつ実装を進め、夏休みに集中的にプログラミングしてなんとか公開レベルまで到達することができた。これが今回公開したg3フレームワークである。

クラウド上で動作するアプリケーションでは、大規模データ・大規模計算の活用に加え、故障・遅延への対応が必須となるため、イベント、メッセージングといった技術を軸にしてアプリケーション・アーキテクチャが大転換することになるだろう。

この分野ではデータフロー、EDA(Event Driven Architecture)、リアクティブシステム、CEP(Complex Event Processing)、Streaming Programming Model、MOM(Message Oriented Middleware)、ESB(Enterprise Service Bus)といった概念や製品を思い浮かべることができる。

この流れの中に、メッセージフローがあるわけだけれど、それぞれの用語とどのような位置関係になるのか、ボク自身もまだ整理しきれていない。本で読むのと、自分でプログダクトを作って手を動かすのでは理解の深さが異なってくる。g3の実装をすすめることによって、これらの技術分野の中でのメッセージフローやg3フレームワークの立ち位置が徐々に明らかになると思われるので、楽しみにしている。

サンプル

g3フレームワークでは、Scala DSLで、プログラムを記述する。現時点で動作する機能のデモ的なサンプルとして以下のものが参考になると思う。

  • AtomDb.scala: TwitterからAtomフィードを受信してRDBMS(Derby)に格納するWebアプリケーション。
  • ReadingLog.scala:読書ログの格納と参照をRDBMS(Derby)に対して行うWebアプリケーション。

この2つのサンプルプログラムについてはブログで追々解説していく。

AtomDb.scala
class AtomDb extends G3Application with UseRecord {
  val kind = 'feed
  val schema = Schema(AutoIdField,
                      'twitterid,
                      'title,
                      ('date, XDate),
                      ('content, XString))
  val url = "http://twitter.com/statuses/public_timeline.atom"

  datastore('db, uri="jdbc:derby:target/g3/derbyDB;create=true",
            driver="org.apache.derby.jdbc.EmbeddedDriver",
            username="user1", password="user1")
  service('feed, url)

  html('viewtop, "Atom to Database") {
<body>
<h1>Atom to Database</h1>

<ul>
  <li><a href="/init">Initialize Database</a></li>
  <li><a href="/list">List Feeds</a></li>
  <li><a href="/update">Collect Feeds</a></li>
</ul>

</body>
  }

  channel('viewinitrun) invoke("init") agent {
    case _ => Html("Initialize Result", 
<body>
<h1>Initialize Result</h1>

Success!

<ul>
  <li><a href="/">Return Menu</a></li>
</ul>

</body>
    )
  }

  channel('viewlist) invoke("list") agent {
    case rs: RecordSet => Html("Feed List",
<body>
<h1>Feed List</h1>

<g.list/>

<ul>
  <li><a href="/">Return Menu</a></li>
</ul>

</body>, schema
    ) += rs
  }

  channel('viewupdaterun) invoke("update") agent {
    case _ => Html("Collect done",
<body>
<h1>Entry</h1>

Atom feeds are updated.

<ul>
  <li><a href="/">Return Menu</a></li>
</ul>

</body>, schema
    )
  }

  agents('init)(MsgSeq(Drop(kind), Create(kind, schema))) invoke("db")

  agents('list)(Query(RecordQuery(AllFieldSlot(kind)))) invoke("db")

  channel('update) invoke("feed") agent {
    case AtomFeed(feed) => {
      Post(kind,
           feed.toRecordSet(entry => {
             Record('twitterid -> entry.id,
                    'title -> entry.title,
                    'date -> entry.updated,
                    'content -> entry.contentText)
           }))
    }
  } invoke("db")

  port("/") invoke("viewtop")
  port("/init") invoke("viewinitrun")
  port("/list") invoke("viewlist")
  port("/update") invoke("viewupdaterun")
  port("/service/init") invoke("init")
  port("/service/list") invoke("list")
  port("/service/update") invoke("update")
}
ReadingLog.scala
class ReadingLog extends G3Application with UseRecord {
  val kind = 'book
  val schema = Schema(IdField,
                      'name,
                      ('date, XDate),
                      ('comment, XString))

  datastore('db, uri="jdbc:derby:target/g3/derbyDB;create=true",
            driver="org.apache.derby.jdbc.EmbeddedDriver",
            username="user1", password="user1")

  html('viewtop, "Reading Log") {
<body>
<h1>Reading Log Menu</h1>

<ul>
  <li><a href="/init">Initialize Reading Log</a></li>
  <li><a href="/list">List Readling Log</a></li>
  <li><a href="/entry">Write Readling Log</a></li>
</ul>

</body>
  }

  html('viewinit, "Initialize") {
<body>
<h1>Initialize Reading Log</h1>

<ul>
  <li><a href="/init/run">OK?</a></li>
  <li><a href="/">Return Menu</a></li>
</ul>

</body>
  }

  channel('viewinitrun) invoke("init") agent {
    case _ => Html("Initialize Result", 
<body>
<h1>Initialize Result</h1>

Success!

<ul>
  <li><a href="/">Return Menu</a></li>
</ul>

</body>
    )
  }

  channel('viewlist) invoke("list") agent {
    case rs: RecordSet => Html("Reading List",
<body>
<h1>Reading List</h1>

<g.list/>

<ul>
  <li><a href="/">Return Menu</a></li>
</ul>

</body>, schema
    ) += rs
  }

  html('viewentry, "Entry", schema = schema) {
<body>
<h1>Entry</h1>

Please enter book information.

<g.input action="/entry/confirm" label="SUBMIT"/>

<ul>
  <li><a href="/">Return Menu</a></li>
</ul>

</body>
  }

  html('viewentryconfirm, "Confirm Entry", schema = schema) {
<body>
<h1>Confirm Entry</h1>

Are you ok?

<g.confirm action="/entry/run" label="SUBMIT"/>

<ul>
  <li><a href="/">Return Menu</a></li>
</ul>

</body>
  }

  channel('viewentryrun) invoke("entry") agent {
    case _ => Html("Entry done",
<body>
<h1>Entry</h1>

Reading log is written.

<g.detail/>

<ul>
  <li><a href="/">Return Menu</a></li>
</ul>

</body>, schema
    )
  }

  agents('init)(MsgSeq(Drop(kind), Create(kind, schema))) invoke("db")

  agents('list)(Query(RecordQuery(AllFieldSlot(kind)))) invoke("db")

  agentc('entry) {ctx: G3AgentContext => {
    case post: Post => post.transform(kind, schema, ctx.context)
  }} invoke("db")

  port("/") invoke("viewtop")
  port("/init") invoke("viewinit")
  port("/init/run") invoke("viewinitrun")
  port("/list") invoke("viewlist")
  port("/entry") invoke("viewentry")
  port("/entry/confirm") invoke("viewentryconfirm")
  port("/entry/run") invoke("viewentryrun")
  port("/service/init") invoke("init")
  port("/service/list") invoke("list")
  port("/service/entry") invoke("entry")
}

2010年8月31日火曜日

現在の活動

8月も今日で終わり。今年も2/3に、今年度も半分に近づきつつあり、おおむね半分が過ぎたというところで活動の内容をまとめておくことにした。
現在行っている活動は大きくまとめると以下の3つである。

  • SimpleModeling - クラウドアプリケーション向けのメタモデル
  • SimpleModeler - SimpleModeling用のScala DSLモデルコンパイラ
  • g3フレームワーク - メッセージフローフレームワーク



元々SimpleModelingは、wakhok時代に教育用に整備したオブジェクトモデリング手法で、『上流工程UMLモデリング』や『マインドマップではじめるモデリング講座』という形で本にまとめることができた。従来型のオブジェクトモデリングをベースにwakhokで得られた知見などをもとに拡張している。

SimpleModelingのもう一つの大きな目的はモデル駆動開発で使用できるオブジェクトモデルのプロファイルである。DSLで記述することができ、かつプログラムに落としこむことができることを念頭にメタモデルを定めている。

このSimpleModeling用のモデルコンパイラとして開発したのがSimpleModelerである。
SimpleModelerはScalaをDSLのホスト言語として使用しており、SimpleModeler自身もScalaで開発している。
SimpleModelerでは、Scala DSLから仕様書とプログラムを生成する。仕様書はクラス図や状態機械図、状態遷移表を埋め込んだHTMLを生成することができる。

SimpleModelingとSimpleModelerが一通り目処がついたところで、クラウドがそろそろ来そうという感触を得たのが一昨年の秋。SimpleModelerでGoogle App Engine/Python用のコード生成が動き始めた頃、Google App Engine/Javaが公開された。
それ以降はGoogle App Engine/Java向けのコード生成を中心にSimpleModelerの開発を進めている。

Google App Engineを一通り経験した後、SimpleModelingをクラウド向けに拡張する本格的に開始したのが昨年の夏、秋。11月にはMicrosoftのPDCに参加させてもらってAzureの技術に刺激を受けた。

クラウド向けの拡張項目についてはいずれブログでも説明する予定だけど、今の所(1)ドメインモデルの拡張(2)メッセージフローの導入の2点。

アプリケーションの振舞いモデルを主にメッセージフローで記述してみようというアプローチなんだけど、このアプローチを取るためには現在利用できるクラウド・プラットフォームとは距離がありすぎる。

そこで、この距離を埋めるために開発を始めたのがメッセージフローフレームワークであるg3フレームワークである。g3フレームワークの開発は今年に入ってから。
g3フレームワークはScala DSLで記述したメッセージフローを実行する。

最近は、このg3フレームワーク上でメッセージフローモデルとドメインモデルを連携させるための仕組みづくりの開発を行っている。

以上の3つの活動はそれぞれ面白い論点があるので、ブログで順にまとめていきたいと思っている。

2010年7月12日月曜日

マインドマップ・モデリング

先週の木金に函館で久々にマインドマップ・モデリングの講習を行った。

組み込みエンジニアなど実装周りを担当されているエンジニアの再教育事業でのモデリング教育。
UMLの文法を講義して終わり、ということにはしたくなかったので、wakhok時代に開発したモデリング教育の技法であるマインドマップ・モデリングを行うことにした。


一日目にマインドマップ・モデリングの文法や簡単な演習を行った後、二日目に実際の雑誌記事を用いて演習を行った。この二日目の演習が本番である。日本語の文章とモデルのマッピングの勘所をつかんでもらうという狙いがある。

今回は旬の話題ということで週刊 東洋経済 2010年 7/3号の特集『激烈!メディア覇権戦争』から『電子書籍は「本」を救うのか』をテーマに選んでみた。

以下はボクがサンプルで作ってみたマインドマップ・モデル。
モデル作成の過程を三段階で示している。

まず最初に記事中から単語を抜き出してマインドマップ・モデリングが定義しているBOIの下に配置する。
それぞれの枝は通常のマインドマップの技法を用いて自由に情報を整理する。


次の段階ではマインドマップ・モデリングの文法に従って用語の整理を行う。
用語の名寄せ・統合と用語間の関係を整理して、基本的な構造を抽出するのが目的である。


そして、以下が最終型。時間の関係があって完成というところまではいけなかったけど、大体の構造まで持ってくることができた。
全段階で得られた基本構造をベースにして、モデルのリファインを行う。
ここで重要なのが物語(ユースケース=利用事例)と出来事(イベント)によって記事の意図をモデルに注入する点である。
物語と出来事によって、モデルに動的な側面が加わる。そして、モデルを動作させるのに必要な静的構造の文脈が確定する。


こういったモデリングをUMLでももちろん行うことができるけど、仕掛けが大掛かりになりすぎて、演習という限られた時間では実現が困難というのが経験則。
この問題を解決するために開発した技法がマインドマップ・モデリングというわけである。

マインドマップ・モデリングのモデルは一般的なOOをベースにしている(正確にはボクが開発したSimpleModelingというプロファイルをメタモデルにしている)ので、このまま半機械的にクラス図やユースケース図に変換することが可能である。

今回もUMLによるモデリングはほとんど経験のないという受講者の方が多数おられたが、適切なマインドマップ・モデルを作成されていた。
いずれUMLを使うようになり、UMLによるモデリングを行う必要が出てくれば、今回の講習の成果を活かしていただけると思う。

久々にマインドマップ・モデリングをやってみてモデリングは楽しいなぁ、と再認識。
プログラミングと直結する設計モデルの作成はどちらかというと無駄な作業だと思うけど(Scalaを使って直接コーディングするかDSL化するのがよい)、ふわふわとした状況を具体的な形にしていく概念モデリングという作業はクラウド時代になっても引き続き重要な技術であり続けるだろう。

現在は新しく登場したプラットフォームであるクラウドの基盤技術に業界の関心が集中しているけど、いずれモデリングの方にも関心が移ってくることになるはずである。
そういったことも意識しつつ、今はマインドマップ・モデリングをどのように改修すればクラウドに対応できるのかといったことを切り口にしてクラウド時代のモデリングについてつらつらと考えている。

2010年6月24日木曜日

ボクらのScala

明日6月25日に拙著のScala入門書『ボクらのScala』が出版されます。


副題は「次世代Java徹底入門」となっています。
題と副題は編集のYさんがつけたものですが、なかなかよい感触です。
少し冒険的な副題ですが、二年ほどScalaをいろいろと触ってみてのボクの実感とも一致しています。
内容をみてYさんもそのように感じられたのだと思います。

Scalaは色々な言語のよいところを絶妙のバランスで配合しており、プログラミング効率も高く、高い実用性を備えています。その上で、DSL、マルチコア、クラウドといったこれからの応用を実現するために必要な機能が整備されているのが美点です。
今すぐ便利であるのはもちろん、これからの10年を乗り切るための基盤ともなる言語といえるでしょう。

ただ、言語仕様やライブラリの使い方を一度にすべて把握しようとすると初学者には敷居が高いので、普通のプログラミングをする上で普通に必要な言語仕様やライブラリを、学習段階に合わせて説明していくというアプローチを取りました。

400ページに収めるために、大幅に原稿を削りました。
原稿を書く側からすると辛いところですが、逆に読者の側からは内容がコンパクトになって読みやすくなったのではないかと思います。
割愛した原稿は、このブログで随時アップしていこうと思っています。

2010年5月4日火曜日

Scala DSL

クラウド時代に向けて:

  1. SimpleModel:オブジェクト・メタモデル。クラウド・アプリケーション向けの拡張中。
  2. SimpleModeler:SimpleModelのモデルコンパイラ。
  3. g3:メッセージフロー・フレームワーク。

の開発を行っているわけだけれど、これらの技術のキーテクノロジとなるのがDSL(Domain Specific Language)である。

SimpleModelはメタモデルだけれど、Scala DSLで簡潔に記述できることが要件の一つになっている。そして、SimpleModelのモデルコンパイラがSimpleModeler。SimpleModelerはSimpleModelを記述したScala DSLの集まりをソフトウェアリポジトリとして使用する。

またg3は、Scala DSLで記述したメッセージフロー・モデルに基づいて動作する。

このように、このところはScala DSLを中心に活動しているのだけれど、ここにいたるまで色々と紆余曲折があった。

1998年に登場したXMLがJavaと分散環境のミッシングリンクを埋めるキーテクノロジーであると感じ、Java&XMLを中心に活動していた時期がある。

分散環境では、分散ノード間でのオブジェクトの共有や移送は理論的には不可能ではないにしても相当ハードルが高く、当面は実運用的には不可能と考えてよい、というのが分散OSや分散オブジェクトの教訓。

このため、分散アプリケーションが取るべき現実解は、異機種間での疎結合のサービスを構造化された値の送受信で連携させるということになるのではないか。そしてこの「異機種間で送受信する構造化された値」を記述する技術が当時のJavaには欠けており、ここにXMLはすっぽりとはまるのではないか。

そういったブレークスルーの予感もあって、XMLアプリケーションであるSmartDoc(1998)とRelaxer(2000)を開発したのだけれど、これらのアプリケーションを通して、いわゆる「one source multi use」の有効性を体感することができた。

当時はまだ、DSL(Domain Specific Language)という用語はなかったか、あるいは一般的ではなかったのだけれど、用途ごとの専用言語をXML上に構築して「one source multi use」の運用を行うこと、つまり今の言い方ではDSLが今後の技術の主流になると思えた。

ただ、XMLをDSLのホスト言語として使用することを試行錯誤する中で、XMLは文書をマークアップするにはとても良いのだけれど、定義ファイルやプログラムといった用途に使用するのには向いていないことが分かってきた。

そこで、プログラミング言語をホスト言語にすることを考えたのが次の段階。最初に候補となったのはGroovy。これは2004年頃。Javaベースであり、メタな操作も可能なのでDSLのホスト言語としてうってつけであると思ったわけである。

しかし、いくつか問題があってGroovyを採用するのは断念した。

当時のGroovyは、非常に遅かったのが一点。ただ、こういった問題は時間が解決するので、DSLの用途ではそれほど大きな問題ではない。

木構造を扱う抽象構文的な拡張の柔軟性が低い、という問題もあった。これは、他に良いところがあれば我慢できる範囲。(なにぶん古い話なので今は違った状況になっていると思う。)

しかし、これは大きな問題だと思ったのは、動的言語であるためにDSLの構文にコンパイラ相当のエラーチェックをかけることができない、ということ。これはDSLを使ってモデルを作成していく際の効率を著しく低下させる。

たとえば、フレームワークの設定ファイルに識別子のミススペルがあることが原因で、フレームワークが意味不明のエラーで落ちる、というような事が起きる。

DSL処理系の開発も難しくなる。DSL処理系をクイックハックで作る分には楽でよいのだけれど、本格的なものにしようとするとコンパイラ相当のエラーチェックを自分で実装しなければならなくなる。

そんな中で、アノテーション技術が登場したこともあり、JavaをDSLのホスト言語とすることに決めて開発を進めていた。

しかし、JavaをDSLのホスト言語として使用した場合、メタな情報はjava.lang.reflectionとアノテーションで部分的に取り出すことができるだけなので用途が限られるという問題がある。Javaの文法を構文木として取得するのが非常に難しい。EclipseのASTを使うという案もあったのだけれど、Eclipseに依存してしまうので断念した。

以上のようなこともあり、Javaでは木構造のデータ構造を簡潔に記述するための文法を定義するのが難しい。Javaで書いたロジックをそのまま取り出すことも難しい。

もちろん、GWTのような荒業もあるけれど、これはハードルが高すぎて一般的な技術には成り得ない。

また、Javaではテキスト情報をDSLの一部として記述するのが一苦労。コメントに記述したものを、自作パーサで読み込むといったようなことをやったりしていた。

そして最後に行きついたのがScalaということになる。これが、2008年の7月。

Scalaは、高階関数と字句上の様々なトリックを併用することで、簡潔で強力なDSLを簡単に構築することができる。また、生文字リテラルやXMLリテラルがあるのでテキスト情報の記述も簡単。ケースクラスや抽出子によるリテラル追加機能もよい。しかも、型推論のついた静的型付け言語。

まさに、DSLのために生まれてきた言語である。

たとえば、以前にご紹介したg3フレームワークが使用する以下のScala DSL。Enterprise Integration PatternsのSplitパターンとAggregateパターンを使用したメッセージフローを記述している。これをXMLやJavaで記述しようとすると、大変な事になりそうだ。実装もややこしくなる。

Split.scala
class Split extends G3Application {
  start(List(1, 2, 3, 4, 5)) split() agent {
    case x: Int => x + 100
  } aggregate()
}

しかし、Scalaだととてもすんなりいくのである。実装も、簡単というわけではないけれど、XMLやJavaをDSLにした時のことを考えるとはるかに容易になる。

いずれにしても、こういったDSLの構築技術が、クラウドに限らずこれからのソフトウェア開発のキーテクノロジーになると思っている。よほどの本格的な用途でない限り、ホスト言語の上に構築する内部DSLを使うことになるので、内部DSLが最重要技術ということである。

内部DSLのホスト言語としては、現在のところRuby、Groovy、Scalaが有力だと思われるけれど、やっぱりDSLは静的型付けがよいかな、ということでボクの選択はScalaということになる。もちろん、これは各自の好みで決めてよいところ。DSL処理系の開発効率も重要なので、プログラミングに慣れている言語を選択するのがよいでしょう。

Scala DSLアプリケーションであるSimpleModelerやg3の開発を通して、二年近くScala DSLプログラミングしてきて、多少ノウハウも蓄積されてきた。そのようなこともあり、Scala DSLに関するノウハウを5月18日に行われるJJUG CCCで『Scala DSLの作り方』というセッションで発表させていただけることになった。

興味のある方はぜひどうぞ。

2010年5月3日月曜日

クラウド・モデリング

5月14日に行われる『Hadoopを中心とした分散環境での開発方法論・モデリング・設計手法等についての座談会』という座談会に参加させていただくことになった。

よい機会なので、クラウドをターゲットにして取り組んできたモデリング手法、ツール、フレームワークなどをつらつらと整理しているところ。

オブジェクト指向モデリングは、本来、振舞いモデルを記述する能力が高い所が特徴であり強みでもあるはずだけれど、今のところ有効活用されているとは言い難い。

伝統的な基幹システムも、新興のWebシステムも基本的には画面とデータベース間の転記が基本アプリケーション・アーキテクチャとなっていることが多く、この場合には従来型のデータモデリングで十分だったということかもしれない。

三段(3 tier)構成のシステムアーキテクチャといった設計レベルでのモデリングもあるけれど、アーキテクチャレベルの鳥瞰図があれば、あとは直接プログラミングした方が話が早い。

しかし、クラウドでは故障、遅延、規模といった問題が従前より重要な課題となって浮上するため、今までのようにはいかなくなるだろう。

故障、遅延、規模の問題をデータベースシステムを中心としたミドルウェア層で吸収することはもはやできなくなるわけで、性能と一貫性のバランスを取るために、アプリケーションの用途に応じて、アプリケーション側で調整を行う必要が出てくる。いずれこういった問題を吸収するクラウド・ミドルウェアが出てくることになるとは思うけど、これは10年スパンで考えていくことである。

利用者側への見せ方、エクスペリエンスにも影響が出てくる。たとえば、今までだったらフォームの完了ボタンが完了したらデータベースには確実に書かれているはずだったけど、クラウドでは後で書いときます、という約束が行われるだけとなることが多くなる。そうなると、利用者側のアクションとして、書かれたことの確認や、実は書き損なっていたのでリカバリを利用者自身にお願いするというようなユースケースが普通に出てくる。

こういった問題を取り扱うには並列、分散、非同期といった要因を上位のモデリング段階で扱わなくてはならなくなるはず。そのモデリングのためのメカニズムとしてオブジェクト指向が再評価されることになるだろう、というのが最初のアイデア。そして、システム構築という観点から見るとMQ的なキューを中心としたシステムアーキテクチャ、アプリケーションアーキテクチャになるだろう、というのが二つ目のアイデア。

当時考えていたこれらのアイデアを昨年の1月にクラウド研究会でお話をさせていただいた流れから、UNIXマガジン誌に寄稿した記事を『クラウドの技術』の一編として再録していただいた。

この2つのアイデアを、従来から考えているモデリング手法に取り込む方針で作業を進めている。

従来システムに対するオブジェクト指向のモデリング手法はSimpleModeling、MindmapModelingという形でまとめ、一昨年の夏に出版させていただいた。

SimpleModelingとMindmapModelingは、内部的には共通のメタモデルSimpleModelを使用しており、相互運用可能である。MindmapModelingでラフなドメインモデルやユースケースモデルを抽出し、SimpleModelingで本格的なモデリングに入っていくという流れを想定している。

SimpleModelでは、イベントを軸に静的モデルと動的モデルを相互連携させる点がポイントとなっている。SVOがモデリングの基本になる。Vがイベント。イベントがアクター(S)とResource(O)の状態遷移を束ねる。ユースケースも物語をイベントの列として記述することで、静的モデルと動的モデルの両方にシームレスに連結できる。

クラウド・アプリケーションの場合でも、この枠組は引き続き有効ではないか、というのが今のところの考え。とはいえ、静的モデル、動的モデルの双方に相応の拡張が必要になる。

静的モデルでは、いわゆるBASEトランザクションに対応したデータモデリングが必要になってくる。ここでSVOの枠組みを自然に拡張していくことで対応できるのではないか、と考えている。具体的には、関連の各種プロパティの解釈の精度を上げることと、イベント・エンティティにBASE向けの属性を取り入れるといったことである。

動的モデルでは、新しくメッセージ・フローという概念の導入がミッシングリンクを埋める鍵になるのではないかと考えている。オブジェクト指向に限らず、コントロール・フローやデーター・フローをモデル化する図は色々と用意されているのだけれど、コントロール・フローとデータ・フローを包含して記述するための図というのが案外ない。しかし、クラウド・アプリケーションのモデリングでは、コントロール・フローとデータ・フローを同じ文脈上で記述することが、有効なのではないか仮説を立てているところである。

この仮説に基づいて、メッセージフロー図の文法、メッセージフローのDSL、そしてメッセージフローの実行系としてのフレームワークを開発しているのが現在のステータス。メッセージフローのDSLとフレームワークが一段落したら、一昨年から昨年にかけて開発したScala DSLコンパイラSimpleModelerを、メッセージフロー対応する構想となっている。

先は長そうだけど、ぼちぼちやっていくつもり。

2010年4月10日土曜日

(仮称)クラウド研究会@札幌

4月4日に「第1回(仮称)クラウド研究会@札幌」で、以下の3つのテーマでお話をさせていただきました。幹事の中村さん(@nakayoshix)には大変お世話になりました。どうもありがとうございました。
  • Android + Mule ESB + Twitter + OpenX + Scala + EC2
  • SimpleModeler + AppEngine
  • g3フレームワーク
昨年の秋ぐらいに、SimpleModelerのAppEngine/JDO周り、静的モデル周りが一段落。まだまだ完成というわけではないんだけど、バランス的にそろそろ動的モデルを攻めてみようということで、ここ半年ほどはクラウド・アプリケーションにおける動的モデルの位置付けと実現方法について検討と試作を行ってきた。
11月にはPDC09に参加する機会があり、Windows Azureについて学ぶことができたのも大きな収穫だった。
システムの動的モデルでは、エンティティの状態遷移モデルがひとつの基本になるけど、システム全体の挙動をモデル化する手法は確立されていない。ユースケース・モデルとシステム実装を繋ぐ部分は依然として手作業になる。
もちろん、手作業といってもモデリング側も色々なモデルを用意しているし、システム側もJavaEEやSpringといったアプリケーション・フレームワークがかなりの仕事をしてくれるので、相当手間を省けるようにはなっている。
しかし、クラウド時代に入ってモデリング側の道具立ても、システム側のアプリケーション・フレームワークもゼロベースで再構築しなければならない状況になっているのは明らか。クラウド時代には非同期、並列、分散、故障、遅延を正面から取り扱わないといけない。アプリケーション・モデルではこれらの要因を捨象して、実装時に頑張るというわけにはいかないだろう。
そういった時代認識もあり、次世代のコンピューティング環境、その土台となるモデル体系とシステム基盤、アプリケーション・フレームワークについて、微力ながら色々と考えた結果到達した結論が「メッセージ・フロー」である。
メッセージ・フローを軸にしてユースケースからエンティティをつなぐ動的モデルの体系を構築し、できるだけインピーダンスミスマッチが発生しない形でDSLで記述しフレームワーク上で動作させる方法について、朧気ながら形がみえてきたような気がしており、その実現のためのDSL、DSLコンパイラ、フレームワークの開発が最近のボクのテーマになっている。
まだまだ、仕掛り中の状態なんだけど、今回はよい機会をいただいたので中間報告という形で発表させていただいた。

Android + Mule ESB + Twitter + OpenX + Scala + EC2

「Android + Mule ESB + Twitter + OpenX + Scala + EC2」ではボクが試行アプリケーションとして作成したMule ESBベースのアプリケーションを素材にメッセージ・フロー・アーキテクチャによるアプリケーションの考え方などを説明した。
ポイントとなるのは、以下のメッセージ・フロー図。コントロール・フローやデータ・フローを記述するための図はあるけど、メッセージ・フローを記述するための図が案外ないので、ボクのニーズに合わせて文法を定義した。詳細はいずれこのブログでも紹介しようと思っている。



Visioで作った図なんだけど、OpenOfficeにはない矢印を使っているためOpenOfficeでは描けないことが最近判明した。OpenOfficeでかける記法に修正する予定である。

SimpleModeler + AppEngine

「SimpleModeler + AppEngine」は、現在開発中のScalaベースのDSLモデルコンパイラSimpleModelerの紹介。
現在のところエンティティ・モデルからUMLクラス図とAppEngine/Javaのプログラムを生成できる。
動的モデルは、状態遷移図をUMLステートマシン図と状態遷移表に変換するところまで。
1月22日に開催されたAppEngine Ja Night #4で発表させてもらった時の資料「クラウド・アプリケーション:DSL駆動アプローチ:SimpleModelerの試み」を使って説明を行った。AppEngine Ja Night #4では、時間的な問題で半分まで行かなかったのだけど、今回は最後まで通して説明することができた。

g3フレームワーク

「g3フレームワーク」は、現在開発中のメッセージ・フロー・ベースのアプリケーション・フレームワークである。専用のScala DSLで記述したアプリケーションを直接実行する。
メッセージ・フローの実行基盤としてはApache CamelのScala DSLに期待していたんだけど、内部構造がやや弱いような印象もあり、進捗もはかばかしくないようにみえる。また、Apach CamelそのものもAppEngineで動かすのは無理があるような感じでもあるので、Apache Camelの利用は断念して独自にDSLとフレームワークを開発することにした。
g3フレームワークとDSLは、以下の3つの用途をカバーすることを念頭に動作セマンティクスとDSLを設計している。
  • Google AppEngine Java上で動作する
  • Mule ESBなどのESB上で動作する
  • シェルからコマンドとして実行できる
今の所、コマンドとして実行するという機能範囲を実装している。
ある程度形がついたところでAppEngine上に載せる予定。こちらは、TaskQueueを用いて直接実行させることを想定している。
並行してMule ESBへの対応も行いたいと考えている。こちらはDSLからXMLの定義ファイルを生成する形を想定している。Mule ESBを用いてEC2とWindows Azure上で動作させることができるようになることを期待している。
最終的には、DSLで記述したモデルから、用途に応じてAppEngine, EC2, Windows Azure向けのプログラムを生成し、適材適所でAppEngine, EC2, Windows Azureを選択できるようにするのが目標である。AppEngine, EC2, Windows Azureの混在環境でも有効に機能するはず。
説明/デモで用いたDSLは以下のもの。
リスト1[TwitterScan3.scala](ユーザ名、パスワードのところは改変)はTwitterのTLをAtomFeedで読んできてg3フレームワークのAtomFeedオブジェクトとして受け取るというもの。Scalaのcase sequence(PartialFunction)を使っているのがポイント。この構造にすることでリアクティブなアプリケーションを簡単に記述することができる。
TwitterScan3.scala
package org.goldenport.g3.app

import org.goldenport.g3._
import org.goldenport.g3.atom._

class TwitterScan3 extends G3Application {
service('demogon, "http://[user]:[passward]@twitter.com/statuses/user_timeline/demogon.atom")

  start invoke("demogon") agent {
    case AtomFeed(feed) => feed.toString
    case _ => "???"
  } agent {
    x => println("==> " + x)
    x
  }
}
リスト2[Split4.scala]は、Enterprise Integration PatternsのSplitterとAggregatorを記述したもの。現在の実装はSplitterがListの各要素ごとに分割したものを送信して、Aggregatorが集約してListに再構築する。Splitterは関数でカスタマイズできるようにする予定。
Split4.scala
package org.goldenport.g3.app

import org.goldenport.g3._

class Split4 extends G3Application {
  agent('compute) {
    case x: Int => x + 100
  } aggregate()

  start(List(1, 2, 3, 4, 5)) split() publish("compute")
}

参考:デモ結果

参考のために(長くなるので最後に付録的に)デモの実行結果を載せておきます。スナップショットが表示されているだけなので、中身を知らないと意味が分かりませんが参考ということで。
以下、TwitterScan3とSplit4のデモの様子。TwitterScan3は当日はデモが失敗したので初公開。TwitterScan3のデモは表示が長いのでSplit4、TwitterScan3の順番に並べている。
Split4デモ結果
> run org.goldenport.g3.app.Split4
[info] 
[info] == compile ==
[info] Source analysis: 1 new/modified, 13 indirectly \
    invalidated, 0 removed.
[info] Compiling main sources...
[warn] there were unchecked warnings; re-run with -unchecked for \
    details
[warn] one warning found
[info] Compilation successful.
[info]   Post-analysis: 257 classes.
[info] == compile ==
[info] 
[info] == copy-resources ==
[info] == copy-resources ==
[info] 
[info] == run ==
[info] Running org.goldenport.g3.Main \
    org.goldenport.g3.app.Split4
G3Pipe.do_Process1(class \
    org.goldenport.g3.G3StartNode):List(WrappedArray(org.goldenport.g3.app.Split4)) \
G3StartNode.do_Process: \
    WrappedArray(org.goldenport.g3.app.Split4)
G3Publish.do_Process
G3Context.send
lookup = [compute], Some(org.goldenport.g3.G3AgentNode@1d01844a)
G3Pipe.do_Process1(class org.goldenport.g3.G3AgentNode):List(1)
G3Pipe.do_Process2(class org.goldenport.g3.G3AgentNode):1/List()
G3AgentNode.do_Process: 1
r = 101
terminal = List(1)
G3Publish.do_Process
G3Context.send
lookup = [compute], Some(org.goldenport.g3.G3AgentNode@1d01844a)
G3Pipe.do_Process1(class org.goldenport.g3.G3AgentNode):List(2)
G3Pipe.do_Process2(class org.goldenport.g3.G3AgentNode):2/List()
G3AgentNode.do_Process: 2
r = 102
terminal = List(2)
G3Publish.do_Process
G3Context.send
lookup = [compute], Some(org.goldenport.g3.G3AgentNode@1d01844a)
G3Pipe.do_Process1(class org.goldenport.g3.G3AgentNode):List(3)
G3Pipe.do_Process2(class org.goldenport.g3.G3AgentNode):3/List()
G3AgentNode.do_Process: 3
r = 103
terminal = List(3)
G3Publish.do_Process
G3Context.send
lookup = [compute], Some(org.goldenport.g3.G3AgentNode@1d01844a)
G3Pipe.do_Process1(class org.goldenport.g3.G3AgentNode):List(4)
G3Pipe.do_Process2(class org.goldenport.g3.G3AgentNode):4/List()
G3AgentNode.do_Process: 4
r = 104
terminal = List(4)
G3Publish.do_Process
G3Context.send
lookup = [compute], Some(org.goldenport.g3.G3AgentNode@1d01844a)
G3Pipe.do_Process1(class org.goldenport.g3.G3AgentNode):List(5)
G3Pipe.do_Process2(class org.goldenport.g3.G3AgentNode):5/List()
G3AgentNode.do_Process: 5
r = 105
terminal = List(List(101, 102, 103, 104, 105))
terminal = List(5)
[info] == run ==
[success] Successful.
[info] 
[info] Total time: 2 s, completed 2010/04/10 6:49:13
TwitterScan3結果
> run org.goldenport.g3.app.TwitterScan3
[info] 
[info] == copy-resources ==
[info] == copy-resources ==
[info] 
[info] == compile ==
[info] Source analysis: 0 new/modified, 0 indirectly invalidated, \
    0 removed.
[info] Compiling main sources...
[info] Nothing to compile.
[info]   Post-analysis: 257 classes.
[info] == compile ==
[info] 
[info] == run ==
[info] Running org.goldenport.g3.Main \
    org.goldenport.g3.app.TwitterScan3
G3Pipe.do_Process1(class \
    org.goldenport.g3.G3StartNode):List(WrappedArray(org.goldenport.g3.app.TwitterScan3)) \
G3Pipe.do_Process2(class \
    org.goldenport.g3.G3StartNode):WrappedArray(org.goldenport.g3.app.TwitterScan3)/List() \
G3StartNode.do_Process: \
    WrappedArray(org.goldenport.g3.app.TwitterScan3)
G3Invoke.do_Process
G3Context.invoke
lookup = [demogon], \
    Some(org.goldenport.g3.G3ServiceNode@53015c53)
G3Pipe.do_Process1(class org.goldenport.g3.G3ServiceNode):List()
G3Pipe.do_Process2(class \
    org.goldenport.g3.G3ServiceNode):List()/List()
G3ServiceNode.do_Process: List()
2010/04/10 6:44:21 \
    org.apache.http.client.protocol.ResponseProcessCookies \
    processCookies
?x??: Invalid cookie header: "Set-Cookie: guest_id=1270849461120; \
    path=/; expires=Sun, 09 May 2010 21:44:21 GMT". Unable to \
    parse expires attribute: Sun, 09 May 2010 21:44:21 GMT
terminal = List(<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" \
    xmlns:georss="http://www.georss.org/georss" \
    xmlns="http://www.w3.org/2005/Atom">
  <title>Twitter / demogon</title>
  <id>tag:twitter.com,2007:Status</id>
<link type="text/html" href="http://twitter.com/demogon" \
    rel="alternate"/>
<link type="application/atom+xml" \
    href="http://twitter.com/statuses/user_timeline/demogon.atom" \
    rel="self"/>
  <updated>2010-04-09T21:44:21+00:00</updated>
<subtitle>Twitter updates from \
    &#20986;&#33538;&#27177;&#22826;&#37070; / \
    demogon.</subtitle>
    <entry>
<title>demogon: \
    &#33258;&#30001;&#12364;&#19992;&#12395;&#12388;&#12356;&#12383;&#12290;&#12524;&#12473;&#12488;&#12521;&#12531;&#12364;&#12356;&#12356;&#12290;</title> \
<content type="html">demogon: \
    &#33258;&#30001;&#12364;&#19992;&#12395;&#12388;&#12356;&#12383;&#12290;&#12524;&#12473;&#12488;&#12521;&#12531;&#12364;&#12356;&#12356;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554547226</id>
      <published>2010-02-24T01:56:05+00:00</published>
      <updated>2010-02-24T01:56:05+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554547226" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
    <entry>
<title>demogon: \
    &#12420;&#12387;&#12401;&#12426;&#33258;&#30001;&#12364;&#19992;&#12398;&#12452;&#12479;&#12522;&#12450;&#12531;&#12394;&#12435;&#12363;&#12393;&#12358;&#12290;</title> \
<content type="html">demogon: \
    &#12420;&#12387;&#12401;&#12426;&#33258;&#30001;&#12364;&#19992;&#12398;&#12452;&#12479;&#12522;&#12450;&#12531;&#12394;&#12435;&#12363;&#12393;&#12358;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554480122</id>
      <published>2010-02-24T01:54:33+00:00</published>
      <updated>2010-02-24T01:54:33+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554480122" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
    <entry>
<title>demogon: \
    &#27494;&#34101;&#23567;&#26441;&#12395;&#30528;&#12356;&#12383;&#12290;&#19968;&#26479;&#12420;&#12429;&#12358;&#12290;</title> \
<content type="html">demogon: \
    &#27494;&#34101;&#23567;&#26441;&#12395;&#30528;&#12356;&#12383;&#12290;&#19968;&#26479;&#12420;&#12429;&#12358;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554430782</id>
      <published>2010-02-24T01:53:26+00:00</published>
      <updated>2010-02-24T01:53:26+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554430782" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
</feed>
)
G3Pipe.do_Process1(class org.goldenport.g3.G3Agent):List(<?xml \
    version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" \
    xmlns:georss="http://www.georss.org/georss" \
    xmlns="http://www.w3.org/2005/Atom">
  <title>Twitter / demogon</title>
  <id>tag:twitter.com,2007:Status</id>
<link type="text/html" href="http://twitter.com/demogon" \
    rel="alternate"/>
<link type="application/atom+xml" \
    href="http://twitter.com/statuses/user_timeline/demogon.atom" \
    rel="self"/>
  <updated>2010-04-09T21:44:21+00:00</updated>
<subtitle>Twitter updates from \
    &#20986;&#33538;&#27177;&#22826;&#37070; / \
    demogon.</subtitle>
    <entry>
<title>demogon: \
    &#33258;&#30001;&#12364;&#19992;&#12395;&#12388;&#12356;&#12383;&#12290;&#12524;&#12473;&#12488;&#12521;&#12531;&#12364;&#12356;&#12356;&#12290;</title> \
<content type="html">demogon: \
    &#33258;&#30001;&#12364;&#19992;&#12395;&#12388;&#12356;&#12383;&#12290;&#12524;&#12473;&#12488;&#12521;&#12531;&#12364;&#12356;&#12356;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554547226</id>
      <published>2010-02-24T01:56:05+00:00</published>
      <updated>2010-02-24T01:56:05+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554547226" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
    <entry>
<title>demogon: \
    &#12420;&#12387;&#12401;&#12426;&#33258;&#30001;&#12364;&#19992;&#12398;&#12452;&#12479;&#12522;&#12450;&#12531;&#12394;&#12435;&#12363;&#12393;&#12358;&#12290;</title> \
<content type="html">demogon: \
    &#12420;&#12387;&#12401;&#12426;&#33258;&#30001;&#12364;&#19992;&#12398;&#12452;&#12479;&#12522;&#12450;&#12531;&#12394;&#12435;&#12363;&#12393;&#12358;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554480122</id>
      <published>2010-02-24T01:54:33+00:00</published>
      <updated>2010-02-24T01:54:33+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554480122" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
    <entry>
<title>demogon: \
    &#27494;&#34101;&#23567;&#26441;&#12395;&#30528;&#12356;&#12383;&#12290;&#19968;&#26479;&#12420;&#12429;&#12358;&#12290;</title> \
<content type="html">demogon: \
    &#27494;&#34101;&#23567;&#26441;&#12395;&#30528;&#12356;&#12383;&#12290;&#19968;&#26479;&#12420;&#12429;&#12358;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554430782</id>
      <published>2010-02-24T01:53:26+00:00</published>
      <updated>2010-02-24T01:53:26+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554430782" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
</feed>
)
G3Pipe.do_Process2(class org.goldenport.g3.G3Agent):<?xml \
    version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" \
    xmlns:georss="http://www.georss.org/georss" \
    xmlns="http://www.w3.org/2005/Atom">
  <title>Twitter / demogon</title>
  <id>tag:twitter.com,2007:Status</id>
<link type="text/html" href="http://twitter.com/demogon" \
    rel="alternate"/>
<link type="application/atom+xml" \
    href="http://twitter.com/statuses/user_timeline/demogon.atom" \
    rel="self"/>
  <updated>2010-04-09T21:44:21+00:00</updated>
<subtitle>Twitter updates from \
    &#20986;&#33538;&#27177;&#22826;&#37070; / \
    demogon.</subtitle>
    <entry>
<title>demogon: \
    &#33258;&#30001;&#12364;&#19992;&#12395;&#12388;&#12356;&#12383;&#12290;&#12524;&#12473;&#12488;&#12521;&#12531;&#12364;&#12356;&#12356;&#12290;</title> \
<content type="html">demogon: \
    &#33258;&#30001;&#12364;&#19992;&#12395;&#12388;&#12356;&#12383;&#12290;&#12524;&#12473;&#12488;&#12521;&#12531;&#12364;&#12356;&#12356;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554547226</id>
      <published>2010-02-24T01:56:05+00:00</published>
      <updated>2010-02-24T01:56:05+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554547226" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
    <entry>
<title>demogon: \
    &#12420;&#12387;&#12401;&#12426;&#33258;&#30001;&#12364;&#19992;&#12398;&#12452;&#12479;&#12522;&#12450;&#12531;&#12394;&#12435;&#12363;&#12393;&#12358;&#12290;</title> \
<content type="html">demogon: \
    &#12420;&#12387;&#12401;&#12426;&#33258;&#30001;&#12364;&#19992;&#12398;&#12452;&#12479;&#12522;&#12450;&#12531;&#12394;&#12435;&#12363;&#12393;&#12358;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554480122</id>
      <published>2010-02-24T01:54:33+00:00</published>
      <updated>2010-02-24T01:54:33+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554480122" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
    <entry>
<title>demogon: \
    &#27494;&#34101;&#23567;&#26441;&#12395;&#30528;&#12356;&#12383;&#12290;&#19968;&#26479;&#12420;&#12429;&#12358;&#12290;</title> \
<content type="html">demogon: \
    &#27494;&#34101;&#23567;&#26441;&#12395;&#30528;&#12356;&#12383;&#12290;&#19968;&#26479;&#12420;&#12429;&#12358;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554430782</id>
      <published>2010-02-24T01:53:26+00:00</published>
      <updated>2010-02-24T01:53:26+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554430782" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
</feed>
/List()
content = List()
content = List(demogon: ???R???u???????B???X?g???????????B)
xxx = demogon: ???R???u???????B???X?g???????????B
AtomContent = List(demogon: ???R???u???????B???X?g???????????B)
content = List(demogon: ???????R???u??C?^???A????????B)
xxx = demogon: ???????R???u??C?^???A????????B
AtomContent = List(demogon: ???????R???u??C?^???A????????B)
content = List(demogon: ????????????????B??t????B)
xxx = demogon: ????????????????B??t????B
AtomContent = List(demogon: ????????????????B??t????B)
G3Pipe.do_Process1(class \
    org.goldenport.g3.G3Agent):List(<feed><id>tag:twitter.com,2007:Status</id><title>Twitter \
    / demogon</title><updated>Sat Apr 10 06:44:21 JST \
    2010</updated><entry><title>demogon: \
    ???R???u???????B???X?g???????????B</title><link/><link/><content>demogon: \
 \
    ???R???u???????B???X?g???????????B</content></entry><entry><title>demogon: \
 \
    ???????R???u??C?^???A????????B</title><link/><link/><content>demogon: \
 \
    ???????R???u??C?^???A????????B</content></entry><entry><title>demogon: \
    ????????????????B??t????B</title><link/><link/><content>demogon: \
    ????????????????B??t????B</content></entry></feed>)
G3Pipe.do_Process2(class \
    org.goldenport.g3.G3Agent):<feed><id>tag:twitter.com,2007:Status</id><title>Twitter \
    / demogon</title><updated>Sat Apr 10 06:44:21 JST \
    2010</updated><entry><title>demogon: \
    ???R???u???????B???X?g???????????B</title><link/><link/><content>demogon: \
 \
    ???R???u???????B???X?g???????????B</content></entry><entry><title>demogon: \
 \
    ???????R???u??C?^???A????????B</title><link/><link/><content>demogon: \
 \
    ???????R???u??C?^???A????????B</content></entry><entry><title>demogon: \
    ????????????????B??t????B</title><link/><link/><content>demogon: \
    ????????????????B??t????B</content></entry></feed>/List()
==> <feed><id>tag:twitter.com,2007:Status</id><title>Twitter / \
    demogon</title><updated>Sat Apr 10 06:44:21 JST \
    2010</updated><entry><title>demogon: \
    ???R???u???????B???X?g???????????B</title><link/><link/><content>demogon: \
 \
    ???R???u???????B???X?g???????????B</content></entry><entry><title>demogon: \
 \
    ???????R???u??C?^???A????????B</title><link/><link/><content>demogon: \
 \
    ???????R???u??C?^???A????????B</content></entry><entry><title>demogon: \
    ????????????????B??t????B</title><link/><link/><content>demogon: \
    ????????????????B??t????B</content></entry></feed>
terminal = \
    List(<feed><id>tag:twitter.com,2007:Status</id><title>Twitter \
    / demogon</title><updated>Sat Apr 10 06:44:21 JST \
    2010</updated><entry><title>demogon: \
    ???R???u???????B???X?g???????????B</title><link/><link/><content>demogon: \
 \
    ???R???u???????B???X?g???????????B</content></entry><entry><title>demogon: \
 \
    ???????R???u??C?^???A????????B</title><link/><link/><content>demogon: \
 \
    ???????R???u??C?^???A????????B</content></entry><entry><title>demogon: \
    ????????????????B??t????B</title><link/><link/><content>demogon: \
    ????????????????B??t????B</content></entry></feed>)
[info] == run ==
[success] Successful.
[info] 
[info] Total time: 2 s, completed 2010/04/10 6:44:22

2010年3月26日金曜日

By-name Parameters

先々週に、メインで使っていたThinkPadがとうとう壊れてしまった。ファンの故障なので修理できないこともなさそうだったけど、5年程使っているマシンでディスクも50GBと少なく、メモリ2GBは今となってはEclipse&Scalaの組み合わせには到底足りないので、観念して新しいものを買うことにした。

最近、周りの人のMac率が非常に高いこともあり、スペックが魅力的だったのでiMacにしてみた。到着したのが先週の金曜。ちょうど一週間経ったけど、やっと作業環境が整備できてきた。(そういえば、ブログもこの記事がiMacからの初投稿になる。)

CPUはCore i7。4コアで、Hyper Threading機能により擬似的に8コアになる。今後、マルチコアを前提としたプログラミングモデルに移行することが予想されるので、検証の基盤として期待している。

ということで、8コアの振舞いを見たいのと、ScalaのActorを試したいということもあり簡単な並列ソートプログラムを作って動かしてみた。このプログラムについてはいずれ取り上げるかもしれないけど、今回は並列ソートプログラムの性能測定用に作った以下の小さなプログラムがテーマ。

Tmr.scala
object Tmr {
  def run(proc: => Unit) {
    System.gc
    val start = System.currentTimeMillis
    proc
    val end = System.currentTimeMillis
    println("elapse(ms) = " + (end - start))
  }
}

このプログラムはScalaの特徴的な機能であるBy-name Parameterを使っている。By-name Parameterは関数リテラル/クロージャと字句的なトリックを使って、メタな機構を導入せずに擬似的な制御構文の拡張を可能にした、まさにスケーラブルを目指すプログラミング言語Scalaの影の主役ともいうべき重要な機能である。大事なのはrunメソッドのパラメタ「proc: => Unit」の所。通常の関数パラメタだとパラメタがない場合「proc: () => Unit」となるところを、パラメタリストを省略している。この省略記法をScalaではBy-name parameterと呼んでいる。

Tmr.scalaは小さなプログラムなので、ひと目で何をしているか分かると思う。

でもできることはすごい。

基本的な使い方は次のようになる。この場合は小さなソートが5msで終了している。

scala> Tmr.run(List(2, 1, 3).sort(_ < _))
elapse = 5

何となく、見逃してしまうかもしれないけど、関数リテラルや高階関数を持たないJavaなどプログラミング言語ではこういう書き方はできない。なぜなら、Tmr.runメソッドに制御が移る前に、引数の式が評価されてしまうから。地味だけど、関数型言語ならではの機能なのである。

試しに1000msスリープする式を引数にTmr.runメソッドを実行してみると経過時間が1003秒となった。妥当なところである。

scala> Tmr.run(Thread.sleep(1000))
elapse = 1003

ここまでの機能だけだと「影の主役」はちょっと大げさ。本当に感心するのは、以下のように引数として制御構造のボディのような複数の文の列を書くことができることである。このことによって擬似的な制御構文が追加可能になっている。

scala> Tmr.run {
     |   val list = List(2, 1, 3)
     |   list.sort(_ < _)
     | }
elapse = 2

この記法を支えているのは2つの特例。まず、パラメタが1つのパラメタリストは「(」「)」だけでなく、「{」「}」が使えるという特例がある。また、先ほど説明したようにBy-name Parameterはパラメタがない関数リテラルでパラメタリストを省略することができるという特例である。この2つの特例が字句上の工夫で、組み合わせて使用すると擬似的に制御構文を追加することができる。

こういった字句上の工夫というかトリックが色々と入っているのがScalaの特徴で、一筋縄ではいかないところである。プログラミング言語は、こういった字句上の工夫が使い勝手に大きく影響するということが、Scalaを使ってみるとよくわかる。

2010年3月16日火曜日

Android IO Utility

最近Androidを触る機会があり、いろいろと試してみている。

本当はAndroidもScalaで作りたいんだけど、色々と問題があるみたいなので今のところはあきらめてJavaで書いている。とはいえ、Eclipseのコード補完、リファクタリングはやはり便利で、フレームワークにプラグインするためのハンドラを書くことが中心のプログラムではScala+Emacsよりも便利である。Androidもこの範疇に入る。

Androidを触って感じたのは、全く組込みや制御系といった気がしないとうことである。体感的には普通のUNIX&Java&GUI&Webクライアントアプリを作っている手触り。

画面が小さいのとネットワークが遅いのとメモリが少ないのとキー入力がやりにくいのとGPSなどのデバイスがたくさんついているのが違うところ。でも、プログラミングモデルは普通のJavaでよい。昔の制御系みたいな職人芸は要らないから、アイデアしだいというところが、Androidがエンジニアに受けている理由のひとつだろう。もちろん、本格的なアプリケーションを作る段になれば、色々な技術が必要になるけれど、間口が広いというのはよいことである。

Androidは、通常ファイルの読み書きができるからAppEngineのプログラミングモデルと比べるとずいぶん楽である。ファイルをBLOB/CLOB的な使い方もできるし、JSONを使ってちょっとした情報を保存しておくこともできる。

モバイルアプリケーションなので、pause/resumeの状態遷移が発生した時の状態引継ぎが必須の作業になる。さらに、アプリケーションが強制終了されても、前回のセッションの状態に復元できることが望ましいので、その意味でもアプリケーションの状態保存のために手軽に使えるデータの保存場所が必要になる。この目的にJSONと通常ファイルの組み合わせが便利なのである。

また、ActivityやServiceといったエージェント間や同一エージェント内でも異なったスレッド間ではIPC的な通信になる。こういったエージェント間での情報受け渡しや情報文脈共有に不揮発性一時データを使う場合、RDBMSを使うのが本格的だけど、JSONをファイルに格納して受け渡すのが手軽で使いやすい。

もちろん、サーバーとの通信はJSONベースでよい。

以上の点から、AndroidではJSONによるデータの取り回しを前提にしたアーキテクチャにするのが筋がよさそうと感じたわけである。org.json.JSONObjectが基本で提供されているのも大きい。

JavaでXMLを使うのは少し煩雑だし、性能的にもいい所はないので、特別な要件がなければあまりXMLにこだわらないのがよいだろう。

そんなこともあり、AndroidIOUtilityというファイル入出力のユーティリティクラスを作った。

AndroidIOUtility.java
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;

import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;

public class AndroidIOUtility {
public static JSONObject loadJsonFromFile(String filename, Context context) \
    throws IOException, JSONException {
        return new JSONObject(loadStringFromFile(filename, context));
    }

public static void saveJsonFromFile(String filename, JSONObject json, \
    Context context) throws IOException, JSONException {
        OutputStream out = null;
        try {
            out = context.openFileOutput(filename, Context.MODE_PRIVATE);
OutputStreamWriter writer = new OutputStreamWriter(out, "utf-8");
            writer.append(json.toString(2));
            writer.flush();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {}
            }
        }
    }

public static String loadStringFromFile(String filename, Context context) \
    throws IOException {
        InputStream in = null;
        try {
            in = context.openFileInput(filename);
            InputStreamReader reader = new InputStreamReader(in, "utf-8");
            StringBuilder builder = new StringBuilder();
            char[] buf = new char[4096];
            int size;
            while ((size = reader.read(buf)) != -1) {
                builder.append(buf, 0, size);
            }
            return builder.toString();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {}
            }
        }
    }

public static void saveStringToFile(String string, String filename, Context \
    context) throws IOException {
        OutputStream out = null;
        Writer writer = null;
        try {
            out = context.openFileOutput(filename, Context.MODE_PRIVATE);
            writer = new OutputStreamWriter(out, "utf-8");
            writer.append(string);
            writer.flush();
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                } else if (out != null) {
                    out.close();
                }
            } catch (IOException ee) {}
        }
    }

public static void saveInputStreamToFile(InputStream in, String filename, \
    Context context) throws IOException {
        OutputStream out = null;
        try {
            out = context.openFileOutput(filename, Context.MODE_PRIVATE);
            byte[] buf = new byte[8192];
            int size;
            while ((size = in.read(buf)) != -1) {
                out.write(buf, 0, size);
            }
            out.flush();
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException ee) {}
        }
    }

public static void saveResourceToFile(int resourceId, String filename, \
    Context context) throws IOException {
        InputStream in = null;
        try {
            in = context.getResources().openRawResource(resourceId);
            saveInputStreamToFile(in, filename, context);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException ee) {}
        }
    }

public static void truncateFile(String filename, Context context) throws \
    IOException {
        OutputStream out = null;
        try {
            out = context.openFileOutput(filename, Context.MODE_PRIVATE);
            out.write(new byte[0]); // XXX needs check
            out.flush();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {}
            }
        }        
    }
}

内容は簡単なファイル入出力なので難しいところはない。それより、Android向けのアプリケーション・アーキテクチャを意識した機能セットという意味で見てもらえると参考になるかもしれない。

1つはJSONの入出力を基本に考えていること。理由は前述したとおり。

また、画像なども通常ファイルとしてキャッシュしたり受け渡ししたりすることになるので、生バイナリデータの入出力も必要になる。

saveResourceToFileメソッドは、リソースに格納した画像データをServiceへの受け渡し目的でファイルに書き出す必要があったので作った。リソースからのデータの取り出しも色々なパターンがありそうなので、目的に応じて用意するとよいだろう。

文字コードはUTF-8決め打ちにしている。もちろん、他の文字コードの読み書きが必要なケースも出てくるだろうけど、自アプリケーション内での通信や不揮発性一時データの保存に用途を絞ればUTF-8決め打ちが簡潔である。

アプリケーションデータはSDCardに置いておくのが作法のようなので、それむけのメソッドもいずれ作ることになると思う。ファイル入出力周りはGoogleのGuavaが便利そうなので、これを使うように改造してもよいかもしれない。