2011年3月31日木曜日

Asakusa Scala DSL

基幹バッチをターゲットにしたHadoopフレームワークAsakusaのScala DSLにトライしています。

DSLの素案が固まってきたので、SimpleModelerに組み込んでみました。

DSLを検証するために使用したモデルは、Asakusaのホワイトペーパーにある以下の図7です。
比較的シンプルですが、基幹バッチのデーターフローをモデルとして記述しています。


Asakusaでは、このようなデータフローをJava DSLで記述するわけですが、ここにScala DSLを用いるとより簡潔に記述できるのではないかと考えているわけです。

このデーターフローをScala DSLで記述したものが以下の会計処理バッチ.scalaです。
具体的な説明は追々していこうと思いますが、ここではぱっと見た感じ、簡潔に明快な形で記述できているような印象を持って頂けるとうれしいと思います。

このScala DSLの設計のポイントはScalaの型パラメータを使って、型安全にモデルを記述している点です。
少しの記述ミスでもコンパイラがエラーで教えてくれる点が、Scalaのような静的型付け言語をDSLのホスト言語として使用するメリットですが、型パラメータを使用することでこの長所を最大限に活かすことができます。

package sample

import org.simplemodeling.dsl.domain._
import org.simplemodeling.dsl.flow._

class 仕入明細データ extends DomainResource
class 仕入返品データ extends DomainResource
class 費用振替データ extends DomainResource
class 売価変更データ extends DomainResource

class 修正在庫振替TRN extends DataSet
class 修正未収収益TRN extends DataSet
class 修正在庫移動TRN extends DataSet
class 未払計上TRN extends DataSet

class 仕入TRN extends DataSet
class 在庫振替TRN extends DataSet
class 在庫移動TRN extends DataSet
class 未収収益TRN extends DataSet

class 計上済仕入TRN extends DataSet
class 計上済未収収益TRN extends DataSet
class 計上済未払費用TRN extends DataSet
class 更新済買掛残高TRN extends DataSet

class 請求エラーTRN extends DataSet
class 支払不可消込TRN extends DataSet
class 支払可消込TRN extends DataSet
class 照合済支払費用TRN extends DataSet
class 照合済未収収益TRN extends DataSet
class 照合済仕入TRN extends DataSet
class 照合済請求TRN extends DataSet

class 仕入データ extends DataSource4[仕入明細データ, 仕入返品データ,
                                     費用振替データ, 売価変更データ]
class 修正データ extends DataSource4[修正在庫振替TRN, 修正未収収益TRN,
                                     修正在庫移動TRN, 未払計上TRN]
class 売価変更在庫変更TRN extends DataSet
class 仕入データTRN extends DataSource4[仕入TRN, 在庫振替TRN,
                                        在庫移動TRN, 未収収益TRN]
class 残高更新TRN extends DataSource4[計上済仕入TRN, 計上済未収収益TRN,
                                      計上済未払費用TRN, 更新済買掛残高TRN]
class 請求TRN extends DataSet
class 会計データTRN extends DataSource7[請求エラーTRN, 支払不可消込TRN, 支払可消込TRN,
                                        照合済支払費用TRN, 照合済未収収益TRN,
                                        照合済仕入TRN, 照合済請求TRN]

case class 仕入データ取り込み(cout: Port[売価変更在庫変更TRN]) extends Operator12[仕入データ, 仕入データTRN, 売価変更在庫変更TRN](cout)
case class 残高更新(cin: Port[修正データ]) extends Operator21[仕入データTRN, 修正データ, 残高更新TRN](cin)
case class 照合処理(cin: Port[請求TRN]) extends Operator21[残高更新TRN, 請求TRN, 会計データTRN](cin)

// 図7 改善された会計処理バッチの処理フロー
case class 会計処理バッチ extends Flow32[仕入データ, 修正データ, 請求TRN,
                                    会計データTRN, 売価変更在庫変更TRN] {
  start op12(仕入データ取り込み(out2)) op21(残高更新(in2)) op21(照合処理(in3)) end
}

このScala DSLからSimpleModelerで生成したフロー図が以下のものです。
データフローの概要をつかむのに十分な図が得られていると思います。


このような図が生成できるということは、SimpleModelerの内部でデータフローのグラフ構造が適切に構築できたことを示しています。
このグラフ構造を使って、モデル検証を行ったり、モデルのクロスリファレンスを作成したり、さらにAsakusa Java DSLの自動生成を行うことが可能のはずです。

Asakusaでは、各種DSL向けにAsakusa Hadoopコンパイラの内部モデル(Asakusa IR?)が提供されるとのことなので、それを待ってAsakusa Hadoopコンパイラの連携部を作成していく予定です。

2011年2月28日月曜日

Access-Control-Allow-Origin

WebブラウザからRESTをアクセスするプログラムを開発するときに、困るのがクロスオリジンの問題。
Webページ上で動作するJavaScriptプログラムは、Webページをダウンロードしたサイトにしかアクセスができない、というアレです。

プログラム開発中では、作ったプログラムをわざわざサーバーに上げるといったような作業が必要となるため、とても不便です。
また、運用時でもREST APIを一般に公開する場合には、大きな制約になります。

この問題は従来JSONPで回避してきましたが、最近はAccess-Control-Allow-Originという手法があるのを知り、g3に実装してみました。


Access-Control-Allow-Originはブラウザ側でのクロスオリジンの選択をするために必要な情報を、HTTPのヘッダにクロスオリジン情報を付加するものです。
ブラウザは、従来はクロスオリジンであればすべて拒否していたのを、ヘッダに許可情報がある場合はクロスオリジンを許可するという動きになります。

具体的には、GET, POST, PUT, DELETEではリプライのヘッダにAccess-Control-Allow-Originヘッダを設定します。

Servletの該当箇所は以下のようになります。(g3の実装なのでScalaです。)

resp.setHeader("Access-Control-Allow-Origin", "*")

GET, POST, PUT, DELETEにAccess-Control-Allow-Originをつけるだけでよいと勘違いしていて、ちょっとはまったのですが、実はOPTIONSでリソースアクセスに対するクロスオリジン定義を返さないといけないのですね。
以下の情報をヘッダに設定します。

Access-Control-Allow-Origin
クロスオリジンを許すURL。すべて許す場合は「*」。
Access-Control-Allow-Methods
Access-Control-Request-Methodに指定されたメソッドを返すのが丁寧っぽいですが、公開してるメソッドをすべてを毎回返すようにしても大丈夫のようです。
Access-Control-Allow-Header
Access-Control-Request-Headersで指定されている文字列を返します。ブラウザがチェック用に使っているみたいです。
Access-Control-Max-Age
このOPTIONSの設定の有効時間。この時間が過ぎると再度OPTIONSでクロスオリジン定義を取りにきます。

Servlet該当箇所は以下のようになります。

resp.setHeader("Access-Control-Allow-Origin", origin)
      resp.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, DELETE, OPTIONS")
      resp.setHeader("Access-Control-Allow-Headers", req.getHeader("Access-Control-Request-Headers"))
      resp.setHeader("Access-Control-Max-Age", accessControlMaxAge.toString)

2011年1月31日月曜日

Web Socketなど

とある事情があって、g3にHTML5のWeb SocketやServer-Sent Event機能を追加してみました。
Web SocketはJettyの機能を利用しています。

今回使ったWeb SocketとServer-Sent Eventを使ったg3アプリケーションは以下のものです。

class Html5Service extends G3Application {
  port("/") html(<p>OK</p>)

  port("/sse") agent {
    case _ => {
      EventStream(System.currentTimeMillis.toString, 1000)
    }
  }

  port("/ws/chat") agent {
    case msg: Post => {
      new Post("/chat", msg.content)
    }
  } invoke("wschat")

  websocket('wschat, "/chat")
  websocket('wstimer, "/timer")

  timer("") agent {
    case msg: Post => {
      Post("/timer", "Time: " + System.currentTimeMillis)(
    }
  } invoke("wstimer")
}

動作概念図はこんな感じ。


「/」(e.g. http://example.com/demo/)にアクセスが来ると「OK」が表示されます。これは動作確認のため。

「/sse」(e.g. http://example.com/demo/sse)にアクセスが来ると、以下のようなServer-Sent Eventを返します。MIMEタイプはtext/event-streamです。

retry: 1000

data: 1292615603413

実現方式は簡単で、新たにtext/event-streamなメッセージEventStreamを追加しました。Server-Sent Eventの仕様はシンプルなので、サーバー側の仕組みもいたってシンプルです。
ただ、残念なことに現在の所、ブラウザが想定しているフォーマットと少しずれているのか、Safariではうまく動きませんでした。仕組みは簡単なので、原因が分かれば修正も簡単にできるでしょう。

「/ws/chat」は、チャットの入力となるHttpのURIです。g3のWebSocketチャネル「wschat」を経由して、WebSocketのポート「/ws/chat」にデータを出力しています。

WebSocketのポートとして、「/ws/chat」と「/ws/timer」が公開されています。「/ws/chat」は前述のように、HTMLのURIの「/ws/chat」からループバックして接続されています。このループバックによってチャット機能を実現しています。

「/ws/timer」は、タイマーからg3のWebSocketチャネル「wstimer」を経由して、タイマーからの入力を受取り、WebSocketに送信しています。

g3は、RESTのセマンティクスを軸にコンポーネントを疎結合して、非同期メッセージング、イベント駆動で動作させるフレームワークですが、WebSocketやServer-Sent Eventもシームレスに統合できることが確認できました。
上記のプログラムは、チャネル間を配線しただけの簡単なものですが、アプリケーションロジックをチャネルのエージェントとして配備することでより複雑な処理ができるようになります。
たとえば、一定時間ごとにEvernoteにアクセスして、取得した結果をWeb SocketやServer-Sent Eventでプッシュ配信する、というようなアプリケーションを簡単に作れるでしょう。

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