メッセージング・フレームワーク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")
}