2011年4月8日金曜日

メタモデルとステレオタイプ

PIM段階のドメインモデルで、自前管理のオブジェクトと他サービス管理のオブジェクトの区別をするのか、その区別はPSM段階まで留保しておくのか。
一意的に決めてしまうのは難しい問題で、ケースバイケースで選択したいところです。短手番でサービスをマッシュアップしてアプリケーションを構築する用途では前者のニーズが高そうですし、長期間にわたって持続的にシステム構築を続けていく、基幹システムでは後者のニーズが高いでしょう。
SimpleModelingは、どちらかというと前者の用途をターゲットにしているので、前者向けに特化したメタモデルを整備しても良いのですが、場合によっては後者の応用にも対応できるのに越したことはありません。
そういったことをつらつらと考えている中で、思いついたのは以下の図のメタモデルです。



以下のメタオブジェクトが定義されています。

Entity
アプリケーションが永続的に使用するオブジェクト
DomainResource
Resourceタイプのドメインオブジェクト
ManagedEntity
アプリケーションの管理下にあるEntity
ServiceEntity
他サービスの管理下にあるEntity
ManagedDomainResource
アプリケーションの管理下にあるDomainResource
ServiceDomainResource
他サービスの管理下にあるDomainResource
ServiceResource
ServiceDomainResourceの別名

EntityとDomainResourceが元々あったメタオブジェクト。SimpleModelingのメタオブジェクト全体でアプリケーションが永続的に使用するオブジェクトがEntity。Entityの一種でドメインモデルのリソースオブジェクトがDomainResourceとなります。
ManagedEntityとServiceEntityは、EntityにミックスインしてEntityの所属を記述するために追加したメタオブジェクトです。
そして、DomainResourceかつManagedEntityなドメインオブジェクトがManagedDomainResource、DomainResourceかつServiceEntityなドメインオブジェクトがServiceDomainResourceです。
さらに、ServiceDomainResourceの別名としてServiceResourceを用意します。
このメタモデルをステレオタイプで表現したクラス図表現は以下のようになります。


このステレオタイプの使用方法は以下の2パターンを念頭においています。

  1. DomainResourceはManagedDomainResourceの省略形として用い、ServiceResourceと組合せて使うことで、PIM段階で自前エンティティと他サービスエンティティを区別しながらモデリングを行う。
  2. PIM段階ではDomainResourceとしておき、PSM段階でManagedEntityやServiceEntityを追加し定義して、自前エンティティと他サービスエンティティを区別する。
具体例でみていきましょう。
(1)ServiceResourceを使う場合は以下のようになります。DomainResourceと同様に拡張を行ったDomainEventも使用しています。Tweet発生にはタグ付き値にservice=twitterを定義して、TwitterによるTweet発生であることを記述しています。(ボクが使っているJudeではタグ付き値がクラスシンボルに表示されないようなので、コメントで定義しています。)


次は、(2)ManagedEntity/ServiceEntityを使う場合です。
まず、PIM段階では以下のようにDomainResourceやDomainEventを使用していきます。具体的な実現方法はここでは記述しません。



これをPSM段階で具体的なサービスの活用を加えたものが以下のものです。Tweet発生にServiceEntityを加え、タスク起票とタスクにManagedEntityを加えています。また、Tweet発生にはタグ付き値にservice=twitterを定義して、TwitterによるTweet発生であることを記述しています。


この例はTwitterのツイートの取り込みを念頭においているものなので、「(1)ServiceResourceを使う」のが適している感じです。
もちろん、アプリケーションの構想段階でドメインオブジェクトのどの部分を外部サービスのものを借りてくるか未定の場合には「(2)ManagedEntity/ServiceEntityを使う」記述方法が適しているでしょう。

2011年4月7日木曜日

ドメインモデルとクラウドリソース

前回「ドメインオブジェクトとデータストアのマッピング」の検討は、SimpleModelingが元々定義しているドメインオブジェクトを検討対象にしています。つまり、クラウド登場前のドメインオブジェクトですね。伝統的なドメインモデルを、クラウドアプリケーションが扱わないといけない多種多様なデータストアにどのように格納管理するのかというのがテーマです。

それとは別の次元で、クラウド時代に入ってドメインモデルのスコープやドメインオブジェクトのモデル化対象をオーバーホールして見直す必要があると感じています。

クラウド時代以前のドメインモデルは基本的に自前でデータベースで管理するオブジェクトが中心で、システム外のオブジェクトはアクターという形で特別扱いして凌ぐ形になっています。

一方、クラウドアプリケーションの場合、複数のサービスをマッシュアップして構築するのが主流となるため、利用者のメンタルモデルに登場するオブジェクト、すなわちアプリケーションが扱うオブジェクトは、必ずしも自前のデータベースで管理するオブジェクトとは限りません。場合によっては、ほとんどが外部オブジェクトということになるでしょう。そのようなケースでこれらをアクターとしてモデル化するのは明らかに不適切です。

この問題に対応するには以下の2案が考えられます。

  1. ドメインモデル(PIM段階)を概念モデル的な抽象度の高いモデルと位置づけ、設計時(PSM段階)に自前データベースか他サービス上のリソースかをモデル化する。
  2. ドメインモデルの初期作成段階(PIM段階)からサービス上のリソースを専用のオブジェクトで表現する。

(PIM=Platform Independent Model=プラットフォーム独立モデル、PSM=Platform Specific Model=プラットフォーム固有モデル)

つまり、ドメインモデルを構成するオブジェクトとしてクラウド上のリソースを一級市民として扱うのか否かという問題ですね。

メタモデルとしての汎用性、拡張性という意味では前者(クラウドリソースをドメインモデルの一級市民として扱わない/PSM段階で導入)が美しいと思います。

しかし、クラウド時代にはクラウドサービスの活用がアプリケーションの中心的な関心事の一つであり、利用者自身も使用しているリソースとリソースを提供しているサービスの関係を意識するケースも多くなるため、PIM段階で自前データとクラウドリソースを明確に区別しておいたほうがなにかと得るものが多そうです。

また、設計実装との連続性という意味では、自前とサービス活用ではアプリケーションの構築のアプローチが異なってきます。PIMレベルのドメインモデルを構築した段階で、ざっくりとした工数見積もりができることも重要で、そういう意味でも自前オブジェクトとクラウドリソースをPIM段階で分離しておくのが実用的なアプローチであると考えています。

また、前者の美しいアプローチで起きそうな問題点として、自前リソースとクラウドリソースのアクセススピードの問題もあります。これは、複数のサービスの共通部分を抽出、汎化して、適切な抽象度のインタフェース経由で提供することができても、性能差がありすぎると事実上同じようには使えないという問題です。

たとえば、WebDavをExplore(Windows)やFinder(Mac)の配下に置いてExcelファイルなどをクリックで開いて使う使い方はとても便利ですが、UNIX shellの上からfindしたりとかそういう細かい使い方をしようとすると速度が遅すぎて心が折れてしまいます。後者のような使い方の場合、サービスに処理プログラム(クロージャ的な物)を投げてサービス側で処理を実行するエージェント的なアプローチ(レトロな言い方ではRJE(Remote Job Entry)かな)の方がより適切です。(WebDavは残念ながらこういう使い方はできませんが、これからのクラウドサービスではこういった利用方法への対応も重要な課題になると思います。)

また、細かいところでは故障頻度にも相当大きな差が出てきます。故障発生確率の大小で利用者への見せ方が変わってきます。加えて、障害発生時の可用性の問題も重要です。マッシュアップしている多数の外部サービスの一つが落ちただけで、自サービスがまるごと動かなくなるのは、アプリケーション的にはかなり恥ずかしいので、自サービスの根幹部と周縁部を切り分け適切に責務分割を行うことが必要です。

こういった要件の実現はアプリケーションアーキテクチャの根幹に関わってくるので、PSM段階から意識を始めるのはやや遅い感じで、PIM段階で意識の上に上げて起きたいところです。

今の所、以上のようなことを考えながら、机上で試行錯誤している状況です。たとえばドメインオブジェクトの種別DomainResourceを自前リソースと位置づけ、クラウド側のカウンターパートにCloudResourceやServiceResourceといったオブジェクトを新規に導入するのか、というようなことを検討しています。

2011年4月6日水曜日

ドメインオブジェクトとデータストアのマッピング

SimpleModelingと呼んでいるモデリング手法をクラウド向けに拡張する作業を行っています。
その一つの柱がドメインモデルの拡張です。ドメインモデルの拡張の一つのアプローチは、オブジェクト種別ごとの実現方法の確立です。モデルから設計/実装へ落し込むときの指針が一つも目標ですが、最終的にはDSLコンパイラによる自動コーディングをターゲットにしています。
SimpleModelingではドメインモデルを構成するオブジェクトに対して用途に応じた分類行い、プロファイルとして定義しています。代表的なオブジェクトは以下のものです。
Actor
システム外に存在する(リアル)オブジェクトの代理オブジェクト。(例:利用者)
Event
システムで発生する出来事。
Resource
システムが管理するリソース。
Powertype
ドメインオブジェクトのパワータイプ。(実装時には区分コードなどで実現される)
Rule
ドメインで使用される規則。
これらのオブジェクトの種別は、今までのソフトウェア開発でも有効ですが、クラウドアプリケーションではその価値がさらに高まります。というのは、クラウドアプリケーションではデータストアの選択が一つの大きな関心事になるからです。今までの企業システム、Webシステムでは大規模あるいは特殊要件がない限りはデータベースとしてRDBMSを一つだけ用いるということが普通です。このため、ドメインモデルとしてどのようなものを作るにしても、どうRDBMSに落し込むのかという設計へのマッピングが重要でした。しかし、クラウドアプリケーションでは複数のデータストアを用途に応じて使い分ける必要があります。一番大きいのがトランザクションやデータモデルの柔軟性というRDBMSの持つ特性が、スケーラビリティと相反する関係になる点です。この他にも半構造データの取り扱いや、分析処理向け性能特性など、必要に応じてデータストアを使い分けていく必要があります。このため、クラウドアプリケーションではRDBMS一刀流では用途が限定されてしまいます。用途に応じてデータストアを使い分けていく必要があります。

データストアの種類

SimpleModelingのドメインモデル拡張での一つのアイデアは、ドメインモデルのオブジェクト種別ごとにデータストアとのマッピングの相性を定義することが有効ではないかということです。設計の指針にもなりますし、DSLコンパイラでのコード生成でも活用できます。
データストアの種類として以下のものを想定しています。
RDBMS
汎用的なデータストア。トランザクション。SQLによる高度な問合せ。複雑なデータ構造。スケーラビリティは低い。
分散ディレクトリ
システムデータ管理向け。利用者情報などシステム管理データにアプリケーションが相乗りする使用方法。ほとんど参照でまれに更新されるデータに使用。トランザクション、SQL的な高度な問合せ、複雑なデータ構造はない。スケーラビリティは高い。
分散キャッシュ
キャッシュデータの格納向け。ほとんど参照でまれに更新されるデータに使用。永続性はない。トランザクション、SQL的な高度な問合せ、複雑なデータ構造はない。スケーラビリティは高い。
KVS
汎用的なデータストア。トランザクション、SQLによる高度な問合せ、複雑なデータ構造はない。スケーラビリティは高い。
カラムナDB
分析処理向け。全件走査に強い。スケーラビリティは高い。トランザクション、SQLによる高度な問合せ、複雑なデータ構造については要調査。
文書DB
半構造データの格納。その他の特性は要調査。
プログラム埋込み
データをプログラムに埋込んで配備。高速動作。エラー要因、配備の複雑度を低減。データとアプリケーションのライフサイクルが一致する場合、特に有効。
「プログラム埋込み」もデータストアの一種として考えています。データをプログラムに埋め込んで配備する手法は、従来のアプリケーション開発では邪道ですが、クラウド上では案外侮れない手法です。
クラウドプラットフォームでは、プログラムがシステム上に多数存在する物理マシンに配備されるわけですが、この配備のメカニズムに乗って同時にデータも配備できるというメリットがあります。また、DSLコンパイラを使うと、プログラム開発とデータ定義を別々に行い、配備の段階で集約するといったことも可能になるので、データをプログラム内に埋め込むことのデメリットを緩和することができます。
この他のデータストアとしてはCDN(Content Dlivery Network)が考えられますが、HTMLページなどの静的な成果物の配備が主な用途なので考慮の対象外にしています。(場合によっては対象に加えるかもしれません。)

マッピング

ドメインオブジェクトとデータストアのマッピングは以下の図のものを考えています。用途を問わずすべてのデータをRDBMSを入れるというアプローチも当然あるわけですが、ここでは用途別により適したデータベースという視点で分類しています。




詳細は後日。

2011年4月4日月曜日

クラウドアプリケーションのモデリング考

オブジェクトモデリングは、元々の源流がシミュレーション向けプログラミング言語SIMULAにあることからも分かる通り、人間が認知する世界観を基本構成要素とするモデリング手法です。
ビジネスモデリング、要求分析、システム分析、システム設計、実装(プログラミング)の各作業フェーズで同じモデル(オブジェクトとオブジェクト間の関係)を持ちまわることによってフェーズ間のインピーダンスミスマッチを低減することができ、要求モデルと実装の乖離を防ぎ、反復型の開発を可能にします。

ただ、オブジェクトモデリングでモデリングの要件を全て満たせるのかというと、まだまだ力不足です。
1つは、人間の認知モデルをベースにしているため、形式的な処理が難しいということです。オブジェクト間の関係は宣言的に記述することができますが、これらのオブジェクトによって構築される協調関係を宣言的に記述したり、形式的に取り扱って検証や合成、最適化といったことはできません。
1つは、ビジネスモデリングから実装までモデルを持ちまわる事ができるのが問題領域の静的構造に限られること。動的モデルについては状態機械の範囲で部分的に持ちまわることができるだけです。これは、前述の「オブジェクトによって構築される協調関係を宣言的に記述できない」という問題に起因しています。

このため、オブジェクトモデリングというと「問題領域の静的構造」に焦点が絞られることになりがちで、たとえば、これをどう実装するのかという切り口のドメイン駆動設計のような方向に技術が伸びていきます。
それ自身は適切なアプローチですが、オブジェクトモデリング全体としてはシステムの振舞いをオブジェクト間の協調としてモデル化していく方向への拡張をしていかないと、汎用的なモデリング手法として適用範囲を広げることは難しいでしょう。

振舞いモデルについては、(単なるお絵描きではない)データして活用可能なモデルを作成することができないとなると、モデリング作業そのものが無駄な作業といえなくもありません。
「問題領域の静的構造」だけモデリングするのであれば、DOAのアプローチで十分ですし、軽量開発の場合ER図だけ用意すれば十分でしょう。
そして、振舞いモデルをモデリングできないのであれば、ER図をかいた後、いきなりプログラミングが効率のよい開発手法となります。

オブジェクトモデリングの柱の一つはユースケースです。ユースケースは「物語」によって利用者要求の暗黙知を抽出し、シナリオ分析技術によってオブジェクトの静的構造と協調を構築する技術とボクは理解しています。
振舞いモデルを宣言的、形式的に扱えない弱点を持つオブジェクトモデリングですが、このユースケースがあることによって「問題領域の静的構造」だけのモデリング手法ではなく、総合的なモデリング手法として汎用的に使用できる技術体系となっているといえます。
ただし、このユースケースはかなり難しい技術で、うまく使いこなせないのであれば背伸びしてオブジェクトモデリングをするより、ER図+プログラミングの方が効率よく精度の高いシステム構築ができるでしょう。

オブジェクトモデリングの現状分析はこんな感じですが、これをクラウドアプリケーションに対して、どう適用していくのかというのが、ここ数年考えてきたことです。
まだまだ、試行錯誤の段階ですがいくつか切り口があります。

1つは、手堅いモデリング技術であるドメインモデルをクラウドアプリケーション向けにチューンするアプローチ。
ソーシャルグラフをどのようにモデル化するのかというアナリシスパターン的な切り口、モデル構造の雛形を定めるメタモデルやプロファイルの切り口、クラウド的なトランザクションやBigDataといったプラットフォームに落し込む手法の整備が必要です。

また、ユースケースをクラウドアプリケーションに適用する手法についても整備が必要です。
従来型のユースケースは、利用者とシステムのショートトランザクション粒度のインタラクションがターゲットでしたが、クラウド環境では非同期処理、並行処理、ロングトランザクションといった要因が重要になってくるので、そのままの形では適用が難しいでしょう。
また、UX(User Experience)技術の興隆によって、ユースケースの適用範囲も変わってきます。
従来のユースケースの運用では、ドメインモデルとの接続箇所が曖昧で、方法論としての整備も遅れていたので、この点の充実も併せて必要でしょう。

加えて、システムの振舞いモデル(オブジェクト間の協調モデル)に対してのアプローチを考える必要があります。
ここは元々従来もうまくいっていないところなので、クラウドアプリケーションでいきなり解決することは考えられません。
とはいえ、非同期、並列、分散がより重要なパーツとなってくるクラウドアプリケーションなので、何らかの対応策は考えたいところです。

アプリケーションの振舞いのアーキテクチャの側面では、要求駆動からイベント駆動への移行も重要です。
要求駆動は、たとえば画面にフォームを入力しエンターキーを押すとその延長で同期型でリクエストが発行されSQLが実行され結果が返されるといったアーキテクチャです。
それに対して、イベント駆動はクラウド上で発生する様々なイベントをアプリケーションがイベントハンドラで受けとり、都度小さな処理を自身のリソースに対して行ったり、協調動作する相方にメッセージを投げたりして処理を進めていくアーキテクチャです。
前者はシェルからCLIで動作させる単発のコマンド的な振舞い、後者は各種イベントを割り込みハンドラで拾いながら動作する制御システム的な振舞いです。
アプリケーションの振舞いが相当複雑化することが容易に推測できます。

大規模データへの対応では、DFD(データフロー図)的なアプローチが再び表舞台に出てきそうです。
エンタープライズ系の開発ではDFDは今でもよく利用されていると思いますが、実装時に手組みプログラミングになるのでその効果は限定的です。
クラウドアプリケーションで事情が変わってくるのは、大規模データの扱いにおいて手組みのプログラミングではなく、DFD的なモデルからHadoopのような大規模データ処理向けのコードを自動生成するアプローチが実用化されるかもしれないという点によります。
ここは、単なる転記を超えたプログラムが必要となるところで、手組みのプログラミングでは、Hadoopのようなフレームワーク向けに合わせる接合部のコーディングが煩雑、最適化やデバッグが難しいという問題があり、これは自動生成のコストをかけても十分に実用化できそうです。
この方面では、SQL的、関係演算的な切り口の言語で大規模データを扱うというアプローチもあり、動向が注目されます。

つらつらと書いてきましたが、現在は、こういった論点についてScala DSLモデルコンパイラSimpleModelerとサービスマッシュアップフレームワークg3を開発しながら試行錯誤を続けています。
ちょうど立ち止まっておさらいするのによいタイミングだと思うので、途中結果をブログにまとめよう思います。

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でプッシュ配信する、というようなアプリケーションを簡単に作れるでしょう。