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")
}