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ページが表示される。