ラベル g3 の投稿を表示しています。 すべての投稿を表示
ラベル g3 の投稿を表示しています。 すべての投稿を表示

2012年5月9日水曜日

データフローDSL考 (4) - g3

データフローDSLという観点で、今までScalaで2つの内部DSLをつくってきました。一つはモデル記述の私家版Asakusa DSL、もう一つはg3向けのフレームワークAPIです。

今回はg3向けのフレームワークAPIについて考えます。

g3については、このブログでも色々書いてきました。詳細は以下のリンクを参照してください。

g3

g3はクラウドアプリケーションのサーバーサイドで動作するサービス部向けのアプリケーションフレームワークです。

元々は、SimpleModelerでクラウドアプリケーションの生成を行う際に、現状では適切なフレームワークが見つからなかったので、「威力偵察」的な意味もあって自分で作ってみることにしたものです。

以下の特徴を備えています。

  • REST指向
  • イベント駆動
  • サービス・バス
  • Atom Publishing指向
  • マルチ・プラットフォーム
  • 運用のスケーラビリティ
  • パイプライン・プログラミング
  • 並行処理
  • HTML生成やRDBMSアクセスをドライバモジュールでモジュール化
  • WebSocket
REST指向、イベント駆動、サービス・バス

クラウドアプリケーション・フレームワークとして、まず欲しかったのはREST指向のイベント駆動&サービス・バスです。

RESTイベントをハンドリングするというより、すべてのイベントをRESTイベントとして抽象化して、この抽象RESTイベントでサービスを駆動します。サービスはサービス・バス上で動作し、チャネルを介して疎結合&非同期に連携します。

エンタープライズの世界ではESB(Enterprise Service Bus)が広く用いられています。ESBはアプリケーションレベルの少し粒度の大きいモジュールをターゲットにしたもので、現状ではXMLを用いて組立て情報を記述するのが一般的です。また、モジュールの連携にはMQを想定しています。EIP(Enterprise Integration Patterns)もこのESB上での構築を想定したものです。

g3の提供するサービス・バスは、ESB的なものをよりシンプルにインメモリ指向で使うことを指向しています。ただし、PaaSと連携することで、クラウドスケールのESBとして使用することもできるようなアーキテクチャにしています。

たとえば、Google AppEngineとの連携は現状でも可能です。Google AppEngineはPaaSの先行事例なので、アーキテクチャの確認の意味もあり、g3では当初から対応しています。

まだ未実装ですが、技術的にはJMS経由でMQを介して他のESBとの連携も可能なはずです。

Atom Publishing指向

抽象REST指向をさらに進めて、抽象Atom Publishing指向の要素を加えています。通常は抽象Atomとしてデータ操作して、必要に応じて抽象度の低い抽象RESTデータを操作することを指向しています。

マルチプラットフォーム&運用のスケーラビリティ

g3はマルチプラットフォームと運用のスケーラビリティも念頭において設計しています。抽象RESTイベントという切り口で、外部イベントと内部処理を疎結合にしているのがポイントです。

  • スタンドアロンコマンドとしても動作
  • Servlet
  • Google AppEngine

g3は、CLIのインタフェースを抽象RESTイベントに変換するフロントエンドを持っているので、スタンドアロンのコマンドでも動作できるようになっています。

g3は、普通のServletとして動作させることができます。このため、TomcatやJava EE環境でそのまま利用できます。

g3は、Google AppEngine上で動作させることもできます。g3は、AppEngineのスケーラビリティを活かすようなアーキテクチャになっています。AppEngineのPaaS機能によって、クラウドスケールでのスケーラビリティを得ることができるようになるはずです。

パイプライン・プログラミング&並行処理

チャネルで抽象RESTイベントが発生しますが、この抽象RESTイベントをパイプライン上上で処理するのがg3の眼目の一つです。狭義では、一連のエントリ「データフローDSL考」で話題にしている項目です。

私家版Asakusa DSLは、モデル記述用とということもあって静的型付けにしていますが、g3はイベント駆動のアプリケーションAPIということもあるので動的型付けにして、パイプラインを上を流れるデータをPartialFunctionでマッチしたもののみ処理を行うプログラミング・モデルにしてみました。このプログラミング・モデルでは、複数種類のイベント(正常系イベントと異常系イベントなど)を同じパイプライン上に流すことが可能になります。

「データフローDSL考」シリーズで説明してきたとおり、単純なパイプラインでは、複雑なデータフロー・モデルを記述することはできません。

g3では、この問題を解決するために、サービス・バスを用います。チャネルから抽象RESTイベントで駆動されたパイプラインは、パイプライン上で加工処理を行いながら、他のチャネルに対して新たな抽象RESTイベントを同期発行または非同期発行していきます。パイプライン上での加工処理が完了後、RESTイベントを同期発行した主体に処理結果を返します。

このように、サービス・バスを経由して他のパイプラインと連携するメカニズムを導入することによってパイプライン・モデルの欠点を克服しています。

サービス・バスによってパイプライン・プログラミング・モデルの問題を解決することを含めて、広義のデータフローDSLと考えることができます。g3では、この広義のデータフローDSLという観点でも、パイプライン・プログラミング・モデルがうまく機能することを確認できました。

問題は、非同期発行した処理を回収するための同期機構です。この同期機構を実現するために、g3の内部で重たい処理をしているのですが、g3のユースケースを勘案した上での全体のバランスからするとちょっと重たすぎた感じがあり、改良ポイントと考えています。

HTML生成やRDBMSアクセスをドライバモジュールでモジュール化&WebSocket

抽象RESTイベント、サービス・バス、パイプライン・プログラミングのメカニズムの上でモジュールによる機能拡張のメカニズムを構築しました。色々な機能をこのセマンティクス&DSL的にうまく埋め込めることが確認できました。

評価

g3は、アプリケーションAPIとしてのデータフローDSLとしては色々チャレンジしてみた項目も含めて、概ねうまくいっているかなというのが自己評価です。ポイントは、パイプライン・プログラミング・モデルとサービス・バスをScala DSLでうまく結び付けることができた点です。

ただ、色々と使ってみて以下の点が問題を認識しています。

  • 非同期同期機構が重たすぎる(前述)
  • 可能であれば静的型付け化したい
  • モデル記述DSL(SimpleModeler)との統合がしたい
新技術

g3を作りはじめた2010年初頭からは、Scalaでも色々な技術革新がありました。g3もこれらの技術を取り込んで、DSLをさらに最適化していく必要があります。

  • 型クラス
  • Monadicプログラミングの進化(Scalaz)
  • Scala DSL技術の進化(Unfiltered など)
  • 並行プログラミングの進化(Akka、Scalaz Promiseなど)
  • マクロ(Scala 2.10予定)
  • Play 2.0

型クラスを用いると、静的型付けのセマンティクスでパイプラインを駆動できるのではないかという期待があります。また、モデル記述DSLとの統合も可能かもしれません。型クラスの登場によって、いろいろな可能性が広がってきました。

Monadicプログラミング、Scala DSL技術、並行プログラミングは地道に進化してきており、2010年年初段階とは別世界の感があります。これらの進化を取り込んで、より強力なプログラミング・モデルを構築する必要があります。

さらに、Scala 2.10ではマクロ機能の導入が予定されています。このマクロはC的なテキスト置換のマクロではなく、ASTレベルでモデル操作ができるものです。これはかなり強力で、DSL技術に大きく寄与することになるでしょう。

プラットフォームとしては、Play 2.0が非常に強力なので、これもターゲットに加えて行きたいところです。

今後の展開

g3も2010年年初段階の技術ベースとしては、いい感じになっていると思いますが、最新技術の視座からみると少し古びてきているようです。

特に、型クラスやMonadicプログラミングの技術を使うことで、より簡潔で強力なプログラミング・モデルを構築できる可能性が高く、この進化を取り込んでいくことが急務です。

並行プログラミングも、フレームワーク内で独自に行うよりもAkkaなどの機構をうまくプログラマが活用できる形に持って行く方が適切です。

また、可能であれば静的型付けやモデルDSLとの統合なども行っていきたいところです。

以上の点から、g3をオーバーホールして、次世代向けのアプリケーション・フレームワークを作ることを考えています。技術的にはScala 2.10のマクロ機構のインパクトが極めて大きそうなので、この技術の評価ができた後に取り組むことになりそうです。

2011年12月7日水曜日

SimpleModelerServiceのアーキテクチャ

昨日公開したXMind→クラス図変換サービスは、g3上に構築したSimpleModelerServiceというRESTサービスです。
このRESTサービスはScalaでプログラミングし、WAR形式にパッケージングしたものをGlassFish上で動作させています。
Java系のPaaS標準コンテナは、WAR形式をJavaEEのWebプロファイル+αを動作させるものになると予想されるので、この環境をScala&自作フレームワークで試してみるというという目的もありました。

SimpleModelerServiceは以下の4つのモジュールから構成されています。

Goldenport
アプリケーションフレームワーク
g3
クラウドアプリケーションフレームワーク
SimpleModeler
モデルコンパイラ
SimpleModelerService
SimpleModelerサービス

SimpleModelerServiceが最上位にあるクラウドアプリケーションの本体で、g3上に構築されています。SimpleModelerServiceはgoldenport上に構築されたスタンドアロンアプリケーションであるSimpleModelerをRESTサービス化します。

SimpleModelerServiceの開発規模は以下の表になります。Scalaが約121.6Ks、Javaが約10.8Ksです。Scalaのコーディング量が100Kステップを超えてきておりちょっと感慨深いものがあります。

開発規模
モジュールScala(Ks)Java(Ks)
Goldenport16.89.1
g322.61.7
SimpleModeler82.1N/A
SimpleModelerService0.1N/A

SimpleModelerService on g3

SimpleModelerServiceでは、クラウドアプリケーション用フレームワークとして開発してきたg3を実応用に初めて適用することができました。

g3はScala DSLでアプリケーションを記述しますが、SimpleModelerServiceは以下のものになります。

class SimpleModelerService extends G3Application {
  title = "SimpleModeler"
  summary = 
SimpleModeler service produces various artifacts from a SimpleModeling model.
port("/diagram", Description( "Diagram", "SimpleModeler Diagram Service",
SimpleModeler diagram service produces a class diagram from a mindmap modeled by MindmapModeling.
, Schema( (Symbol("source.package"), XString, MZeroOne), ('_1, XBase64Binary))) ) agentpf { case p: Post => Post("diagram", p) } invoke('sm) goldenport('sm, SimpleModelerDescriptor) }

port("/diagram")はHTTPリクエストを受け取るポートを指定しています。
URIの断片"/diagram"にマッチしたHTTPリクエストがこのポートで受信され、パイプラインで以下の処理が実行されていきます。

  1. agentpfでPostリクエストを加工するPartialFunctionを実行。
  2. invokeでチャネルsmを呼出し。

その下にあるgoldenport('sm, SimpleModelerDescriptor)は、チャネルsmをGoldenportアプリケーションSimpleModelerに割り当てる設定です。SimpleModelerのGoldenport DSLであるSimpleModelerDescriptorを指定しています。

port("/diagram")からinvokeでこのsmチャネル経由でSimpleModelerが実行され、その実行結果がport("/diagram")の実行結果としてクライアントに返されます。

HTTPのプロトコル処理、FormやJSON、AtomPub, MIMEといったデータ入出力、ファイルのアップロード処理はg3フレームワークが行います。アプリケーションは、GetやPostといったメッセージに対する関数型的な転換処理の連鎖として、アプリケーションロジックを記述することができます。また、Goldenport上に構築したスタンドアロンアプリケーションは、シームレスに接続できるようになっており、ほぼそのままRESTサービス化することができます。

SimpleModeler on Goldenport

モデルコンパイラはアプリケーションフレームワークGoldenport上に構築しています。そのアーキテクチャを定義するDSLが以下のSimpleModelerDescriptorです。

class SimpleModelerDescriptor extends GApplicationDescriptor {
  name = "SimpleModeler"
  version = "0.3.0"
  version_build = "20111206"
  copyright_years = "2008-2011"
  copyright_owner = "ASAMI, Tomoharu"
  command_name = "sm"
  //
  classpath("target/classes")
  importers(ScalaDslImporter)
  entities(CsvEntity, XMindEntity, OpmlEntity, ExcelTableEntity, 
      OrgmodeEntity, YamlEntity)
  services(ProjectRealmGeneratorService,
    ImportService,
    ConvertService,
    HtmlRealmGeneratorService,
//    JavaRealmGeneratorService,
    GrailsRealmGeneratorService,
    GaeRealmGeneratorService,
    GaeoRealmGeneratorService,
    GaeJavaRealmGeneratorService,
    AndroidGeneratorService,
    G3GeneratorService,
    AsakusaGeneratorService,
    DiagramGeneratorService)
}

SimpleModelerDescriptorでは概ね以下のような定義を行っています。

  • importersで外部データの移入器としてScalaDslImporterを指定。
  • entitiesでCsvEntityやXMindEntityなどを指定。サフィックスがcsvやxmlになっているファイルを入力すると、自動的にCSVEntityやXMindEntityに変換される。
  • servicesでDiagramGeneratorServiceやAndroidGeneratorServiceなどを指定。パラメタにより自動的に適合するサービスが起動される。

Goldenportが外部入出力のもろもろをハンドリングしてくれるので、アプリケーションロジックの記述に専念することができます。g3を併用することでRESTサービスまでシームレスに接続できるようになりました。

ScalaとDSL

DSLの用途は大きく(1)モデルなどの静的な情報を記述、(2)フレームワークのAPIを記述、の2つに分けられます。

SimpleModelerがモデル記述に使っているScala DSLは前者の例です。

一方、本記事で取り上げたSimpleModelerService(g3)、SimpleModeler(Goldenport)は後者の例です。SimplModeler(Goldenport)のDSLは、Javaでも何とか実現できそうですが、SimpleModelerServiceのDSLはJavaではこのような記述は難しく、DSLを得意とするScalaの美点がよく出ています。

Scalaは元々、JavaでのDSL実現に限界を感じていたため、DSL用の言語として採用したのですが、十分満足できる結果を得られました。Scalaで100K超のコーディングを行った事になりますが、Javaと比べてプログラミングが圧倒的に楽ということも体感できました。

Goldenportやg3のようなDSLベースのAPIを持つフレームワークやコンポーネントがこれからどんどん出てくることが予想されるので、Scalaプログラミングの生産性はますます向上してくることになるでしょう。また、メニーコア時代の並行プログラミングは関数型言語を中心に広がっていきそうです。今回確認できたようにGlassFishのようなJava用のクラウドコンテナ(の候補)にもまったくシームレスに載せることができるのは、Javaとの互換性を軸に据えているScalaならではです。色々考えていくとScalaはかなり便利な言語で、クラウドアプリケーション向けプログラミング言語の最右翼かなと実感しています。

2011年7月19日火曜日

SimpleModeler (クラウド温泉@小樽)

クラウド温泉@小樽では、モデルコンパイラSimpleModelerからg3フレームワーク上で動作するクラウドサービス、g4フレームワーク上で動作するAndroidクライアントを自動生成し、Androidクライアントとクラウドサービスが連携動作するデモを行う予定です。



まずScala DSLでモデルを記述します。(デモではより効果を狙ってCSVでモデル記述するかもしれません。)
このScala DSLからSimpleModelerを使って、Android用クライアントとGoogle App Engine/Tomcat(Glassfish)の両方で動作するRESTサービスを生成します。
生成された、AndroidクライアントとRESTサービスは連携して、データに対するCRUD処理を行うことができます。

Android用クライアントはg4フレームワーク上で動作します。g4フレームワークはGoogle GuiceによるDIをベースにしたAndroidアプリケーションフレームワークです。

サーバープログラムはg3フレームワーク上で動作します。g3フレームワークはGoogle App Engineと、TomcatやGlassfishなどの通常のServletコンテナ上で動作します。ServletコンテナはEC2を始め、一般的なクラウドプラットフォーム上で動作します。このため、g3フレームワークを用いることでクラウドプラットフォーム上で可搬性のあるアプリケーションを作成することができます。

デモの目的

デモでは、見栄えや分りやすさの点からプログラミングレスで自動生成したアプリケーションがそのまま動作することを主眼とします。
しかし、このデモはモデル駆動開発でアプリケーションプログラムが自動生成されるということを主張するのが趣旨ではありません。現在の所、CRUDのような特定の定形処理以外はアプリケーションの自動生成は、まだまだ実用化には程遠いからです。

それでは、SimpleModelerによるプログラムの自動生成は何をコンセプトとしているのでしょう。
それは、ひとことで言うと「ドメインライブラリの自動コーディング」です。

ドメインモデル

オブジェクトモデリングでは大きく分析モデル(またはPIM, Platform Indenpendent Model)と設計モデル(またはPSM, Platform Specific Model)の2つのモデルを作成します。これはモデルの抽象度が分類の軸になっています。
分析モデルに対して、動作ターゲットのプラットフォームと非機能要求を加えて、実際に動作するプログラムの設計図となる設計モデルとなります。

モデルの分類の軸は他にも色々ありますが、筆者が有効と考えているものにアプリケーションモデルとドメインモデルの軸があります。
ドメインモデルは、アプリケーションの問題領域の構造をモデル化したものです。主に静的モデルにフォーカスしたモデルで、静的構造中心、モデルのライフサイクルが長い、自動生成の対象になりやすいという特性があります。
一方、アプリケーションモデルはドメインモデルを使用して、アプリケーション利用者の目的を達成するための仕組みをモデル化したものです。主に動的モデルにフォーカスしたモデルで、アプリケーションモデルは、振舞い中心、モデルのライフサイクルが短い、自動生成の対象になりにくいという特性があります。



ドメインモデルは、静的構造(クラス図)中心となりますが、動的モデルとして状態機械、ルールモデル、アクション言語を併用します。(アプリケーションモデルでは、ユースケース、インタラクション、コラボレーションなどを使用します。)

静的構造図が中心のモデルの場合、オブジェクト指向言語を使うと設計モデルとプログラムのセマンティクスギャップが非常に少ないので、設計モデルを飛ばして直接プログラミングすることも可能です。短期開発では、その方が効率もよく、プログラムの品質もよくなるでしょう。また、持続性を重視する製品開発でも、静的構造のみでよければER図などのデータモデリングで十分です。
一方、状態機械やルールモデルなど、プログラミング言語とのセマンティクスギャップが大きいモデルを使用する場合は短期開発であっても引き続き設計モデルが有効です。

ドメインライブラリ

現状では、ドメインモデルを作成しても手作業でプログラミングすることが多く、モデルを作成しても作業量は減りません。プログラミング側での改修がモデルに反映されなくなり、長期的にはモデルが死んでしまうという問題もあります。

ドメインモデルからRDBMSのDDLや、O/Rマッパーのコード生成などを行うツールはあるので、データモデリングの範囲では自動生成を取り込んだ運用を行うことは可能です。
しかし、動的モデルを包含したオブジェクトモデル全体のスコープでは、ドメインモデルに限定しても、プログラムの自動生成はまだまだ発展途上といえるでしょう。
しかし、ドメインモデルを実用的に記述できる範囲でドメインモデルのプロファイルを定めておくことは可能です。筆者はこの目的でドメインモデル(とアプリケーションモデル)のプロファイルをまとめています。詳しくはこちらをどうぞ。(『上流工程UMLモデリング』、『マインドマップではじめるモデリング講座』)

ドメインモデルの重要な特性の一つは、自動生成に適しているということです。決められたパーツを使ってドメインモデルを記述するという運用にすれば、分析モデルから、設計モデルをバイパスしてほぼ完全に実装を自動生成することができます。また設計モデルも仕様書という形で生成可能です。もちろん、100%完全は難しいでしょうが、適切な拡張ポイントを生成することで、部分的なプログラミングで補完可能にできるでしょう。

SimpleModelerは、この実現を目指しています。

ドメインライブラリとアプリケーションフレームワーク

ドメインライブラリを単体で生成するのも十分に有効ですが、適切なアプリケーションフレームワークがあれば、この枠組みの中に組み込んでしまえば、アプリケーション開発をより効率的に行うことができます。
アプリケーションフレームワークの上にドメインライブラリをビルトインしたものを土台にしてアプリケーション開発を進めることができます。

クラウドアプリケーション向けにg3フレームワーク、Androidアプリケーション向けにg4フレームワークを開発したので、これらのフレームワーク上にビルトインできるドメインライブラリを生成します。







自動コーディング

モデル駆動開発について以下のような疑問をしばしば耳にします。
  • モデルからプログラム全体を生成するのは無理ではないか。
  • モデルから自動生成したプログラムを直接修正した時に、モデルに反映できないと、以降の開発にモデルを使うことができない。
  • モデルレベルでデバッグできないとバグ修正ができない。
つまりモデル駆動に対してプログラミング言語のコンパイラ的な運用を期待しているわけです。これは一つの理想ではあるのですが、現段階の技術レベルでは難しく、ここに判断基準を置いてしまうと、自動生成は時期尚早という判断になってしまいます。
しかし、ドメインモデルに範囲を限定すれば、相当量のコードの自動生成が可能なので、これをまったく無視するのはあまりにももったいない。
そこで、コンパイラモデルに代わって、プログラムの自動生成をソフトウェア開発の枠組に取り込む切り口として考えているのが『クラスライブラリの「自動コーディング」』というコンセプトです。

言うまでもなくソフトウェア開発では、多数のクラスライブラリを併用して開発を進めるのが普通です。多くのクラスライブラリはオープンソースであり、APIリファレンスとソースコードの両方を参照して利用できます。
基本的にはAPI仕様をみて使いますが、詳細仕様を知りたい時にはソースコードも参照します。デバッグ時にはクラスライブラリのソースも取り込んでブレークポイントを張ったり、変数の値を確認したりします。
クラスライブラリのバグ修正は追加は、短期対応と長期対応の2つのフェーズで行われます。まず、短期対応として、必要に応じてパッチを当てたりして修正します。
その後、バグの修正や機能追加は所定の手続きをへてクラスライブラリに反映されます。長期対応として、この修正が取り込まれた新しいバージョンのクラスライブラリを、改めてアプリケーションに取り込みます。

ここで、発想を少し変えてみましょう。
事前に誰かが用意したクラスライブラリでも、プログラムが自動生成したクラスライブラリでも、利用者からは全く同じです。
つまり、クラスライブラリとしてとして運用するのであれば、プログラムの自動生成を用いても、今までの開発と何ら変わるところはないはずです。

次は、ニーズの面から考えてみましょう。
プログラミングをするときに、特定のドメイン向けのクラスライブラリが整備されているととても効率的です。
しかし、特定のドメイン向けのドメインクラスライブラリは、よほど共通して使用される大きなドメインでないと事前に誰かが用意しているということはありません。
このため、ソフトウェア開発の初期段階では、ドメインモデルをコードとして実装する作業を黙々と続けることになります。あるいは、画面とデータベースをつなぐ処理として、画面のイベントハンドラーの中にドメイン処理が重複して繰り返し生産されることになります。
前者では、ドメインモデルのコーディングが開発全体のボトルネックになりますし、後者では、アプリケーションの保守性、拡張性に問題が出てきます。
この問題を、ドメインクラスライブラリの自動コーディングが解決することができます。前述したようにドメインモデルはほぼ完全な自動生成が可能なので、ドメインライブラリを自動生成するのは実用範囲です。そこで、ドメインモデルをドメインクラスライブラリとして"自動コーディング"してしまおう、というわけです。
出来合いの標準品ではなく、自分向けのクラスライブラリですから、アプリケーションのニーズにもぴったり合います。使い方は、ソースコードが提供されている通常のクラスライブラリと全く同じです。

クラウド温泉@小樽

SimpleModelerのコンセプトが「ドメインライブラリの自動コーディング」であることを説明しました。クラウド温泉@小樽では、SimpleModelerのデモをネタに、ドメインライブラリの自動コーディングというコンセプト、実現性、応用について議論できればと思っています。
また、クラウドアプリケーション、スマートデバイスによって、ドメインモデリングの技術にも色々な影響があることが予想されます。このあたりも面白い議論ができそうです。

2011年7月9日土曜日

g3 (クラウド温泉@小樽)

8月27日(土)/28日(日)に開催されるクラウド温泉@小樽に向けて、現時点でのg3/g4/SimpleModelerの技術について整理しておこうと思います。

Scala DSLベースのモデルコンパイラSimpleModelerでは、当初Glassfish(Java EE)、Spring、Google App Engineといったクラウドプラットフォームあるいはその上で利用するフレームワークのAPI上に直接プログラムを生成することを目指していました。

実際に、Google App Engineでのプログラム生成を実装したところ、生成するプログラム側で相当な作り込みが必要となり、ある意味フレームワークそのものを生成するような形になってしまうことが分かりました。結局、共通部品として必要なものは、自動生成ではなくフレームワークとして独立して提供するのが筋ということですね。

クラウドアプリケーション向けのフレームワークとしては、既存のフレームワークを流用するのが有力な選択肢です。Java EEやSpringといったエンタープライズ向けのWebフレームワーク、あるいはMule ESBやCamelといったESBについても比較検討したのですが、ボクが必要としている機能とはまだま距離があるということで、新規に開発することにしました。

こうして開発したのがg3フレームワークです。


目標

g3を一言で表現すると『Scala DSLベースREST指向粗粒度非同期コンポーネントフレームワーク』です。


  • フレームワークのアプリケーションインタフェースにはScala DSLを用いる。
  • 粗粒度のコンポーネントを非同期で連携してアプリケーションを構築する。
  • コンポーネント間の連携にはRESTを用いる。
開発時に念頭においていたのはEIP(Enterprise Integration Patterns)です。EIPによって非同期コンポーネントをAtomPubのフィードメッセージで連携するアーキテクチャがクラウドアプリケーションの一つの形ではないかというのが基本アイデアになっています。

プログラミングモデルとDSL

クラウドアプリケーションのアプリケーションアーキテクチャとしてどのようなものが適切かというのはまだまだ未知の領域ですが、ボクが仮説として考えているのが並行動作する粗粒度コンポーネントをメッセージングで接続する、コンポーネントベースのアーキテクチャです。 粗粒度コンポーネント間の連携プロトコルとしてRESTセマンティクスを用いることで、プログラミングモデル上、インターネット空間とシームレスに接続することができます。

Scala DSL

このアーキテクチャを取るときの論点の一つが、コンポーネントの組立てをいかに簡単に記述するのかという点です。このようなコンポーネントの組立てにXMLを用いるのが従来の手法ですが、以下の問題があります。まず、XMLによる定義は記述が煩雑になってしまうこと。また、プログラミング言語とのシームレスな連携ができないので、プログラミング言語とXMLの二本立ての管理になってしまうという問題もあります。関連して、定義ファイルにプログラミング的な技法、たとえばマクロを導入して定義の共通部をまとめることにできるようにする、いったことを実現することも困難です。 この問題を解決するために採用したのが、Scalaをホスト言語にしたDSLです。専用言語であるDSLを用いることで、メッセージングによるコンポーネントの連携を簡潔に記述することができます。 また、Scalaを通してScalaだけでなくJavaやその他JavaVM上で動作するプログラミング言語で記述されたプログラムと連携することができます。

REST指向

REST指向は、Webプロトコルとのシームレスな連携、フレーム内リソース識別のURI化、コンポーネント連携に用いるメッセージをAtomフィードベースにしていることにより実現しています。

イベント駆動

クラウドアプリケーションは非同期に発生するイベントを受けて動作するイベント駆動で処理する構造がアプリケーションの基本アーキテクチャになるというのがボクの仮説の一つです。 比喩的には、割り込みハンドラーとシステムアクティビティでアプリケーションを構築する組込み機器のような構造をイメージしています。 イベント駆動はまだ実現できていませんが、その前提となるメッセージの到着を起点に処理を駆動するメカニズムは実現しています。エンティティとして永続管理するビジネスイベントと、揮発性のシステムイベントをどのように再構成して、フレームワークのアーキテクチャ上に位置付けていくのか考慮中です。

状態機械

イベントの発生を受けて、発生したイベントとリソースの状態の組に対して、適合するアクションを実行するというのが、オブジェクト指向の動的モデルの基本的な考えです。 イベント駆動型のアプリケーションでは、この状態機械による動的モデルの記述と実行が重要な意味を持ってくることになります。残念ながら現状のオブジェクト指向言語では、状態機械のサポートを行っていないのでプログラムで直接記述することができません。 このメカニズムをフレームワークで実現したいというのもg3の目的の一つです。 プログラミング言語でサポートされていない必須機能をフレームワークで実現するわけですが、Scala DSLによるアプリケーションインタフェースを用いるので、ある意味新しい専用言語を導入するのと同じインパクトがあります。 状態機械の記述方式と上流モデルとの連携方法について、前述のイベント駆動のメカニズム、記述方法と合わせて検討中です。

スケーラビリティ

クラウドアプリケーションでは、スケーラビリティも重要な要件です。すべてのアプリケーションがフルにスケーラビリティを追求する必要はありませんが、必要に応じてスケーラビリティを確保する手段をモデリング、アプリケーションアーキテクチャ、フレームワークといった様々な層の技術の中に事前に織り込んでおく必要があります。 g3はスケーラビリティ確保の手段として非同期メッセージング、KVSのサポートを行っています。 非同期メッセージングとして前述したようにREST指向のメッセージを粗粒度コンポーネント間でパイプライン的に流通させるモデルをとっています。現在はまだ実現できていませんが、CPS(Continuous Passing Style;継続渡しスタイル)的な処理の記述や、メッセージキューを媒介にした非同期処理などを取り込める構造になっています。 また、データストアのスケーラビリティについてはKVSを包含したデータアクセス基盤を用意しました。 KVSも、Redisのように本当のKey/Value対のデータストアもありますしGoogle App Engine DataStoreのようにISAM的なデータ構造を持っているものもあります。以上の点から、KVSといってもISAMデータ構造、要するに単純な表形式のデータ構造を扱うようにしています。 クラウドアプリケーションでは、一貫性重視の用途にはRDBMS、スケーラビリティ重視の用途にはKVSを使い分けることになります。その場合、RDBMSとKVS間でデータを持ちまわる処理も普通に行われるのでKVSとRDBMSに対する統一アクセス法が欲しくなります。 また、このアクセス法はREST指向アーキテクチャ上で使用することになるので、RESTとのシームレースに連携できることが必要です。 g3では上記の要件を満たす統一アクセス法をサポートしました。REST指向アーキテクチャに沿って普通に処理を記述するだけで、特に意識することなくKVSを使用することができます。(もちろん、KVSをスケールさせるためにはキーの選定など特別な考慮が必要になりますが、疎通レベルでは簡単に使えるというのも重要です。) なお、SQLを直接使用したアクセスも可能ですし、Javaを直接使ってJDBCを使用することもできるので、必要に応じて使い分けすることもできます。

各種機能

実用上有用な機能として以下の機能を実装しました。
  • Web Socket
  • Ext-JS対応
  • 各種クラウドサービス用ドライバ(Twitter, Google Calendar, Dropbox, Evernote)
こういった、各種Web技術、クラウドサービスの取り込みは、必要に応じて順次行っていく予定です。

スコープ外の機能

g3の提供する機能という切り口とは別に、g3がスコープ外としている機能、後回しにしている機能が分かれば、g3の目的・意図が明確になると思います。 以下の機能はフレームワークの直接のスコープ外としています。
  • Web MVCフレームワーク
  • テンプレートエンジン
Web MVCフレームワークやテンプレートエンジンは、まだまだ改良すべき点はあるでしょうが、基本的には枯れた技術であり、再発明する必要性が薄いことが一つ。それより重要なのが、HTML5/CSS3によるAjaxやiOS/Androidといったスマートデバイスの興隆によって、技術の重要性が低くなりそうということです。 レガシー技術はそのまま再利用することとして、イベント駆動や非同期コンポーネントフレームワークといった既存の技術ではカバーしていない技術の実現を中心に開発を進めています。 通常のWebアプリケーションを開発する場合にg3を用いる場合には、既存のWebフレームワークと併用することになります。

次の段階で取り組むもの

以下の機能は、次の段階で取り組む予定にしています。
  • DI(Dependency Injection)
  • OSGi
  • JMX
  • Android
  • TDD/BDD
実用化フェーズでは必要なのは分かっているものの、基本機能が定まっていない段階で取り組むのは時期尚早ということで、将来の拡張を意識しつつも当面の開発項目からは落としています。

2011年6月29日水曜日

クラウド温泉@小樽

正式の広報はまだですが、8月27日(土)/28日(日)にクラウド温泉@小樽が開催される予定です。温泉に浸かりながら、クラウドを肴に語り合いましょう、という企画です。

ここ数年取り組んでいた、ScalaによるDSL駆動開発について、開発していたツールが形になってきたので、クラウド温泉でお披露目する予定です。

DSL駆動開発の軸となるのがScala DSLコンパイラSimpleModelerです。当初は、SimpleModelerでモデルからクラウドプラットフォームのAPI上に対して直接クラウドアプリケーションの生成を試行していたわけですが、適切なアプリケーションフレームワークがないと自動生成もままならない、ということが分かりクラウドプラットフォーム向けにRESTメッセージングフレームワークであるg3を開発しました。

g3はAtomPubメッセージによるRESTを軸としてコンポーネントの連携動作を行うイベント駆動フレームワークです。

g3はデータストアアクセスを含めてTomcatなどのWebサーバとGoogle App Engineの両方で動作します。できるだけ同じプログラムを色々なクラウド・プラットフォームで動作させることがg3の目的の一つです。

また、クラウド時代にはUIが、伝統的なWeb UIからAjaxベースのGUIやスマートデバイスに移行することになります。このところ仕事でAndroidに取り組んでいたこともあり、Android向けのプログラム生成の基盤となるフレームワークg4を作ってみました。

Androidのアプリケーションアーキテクチャは、以前「Androidのアーキテクチャ」で考えてみましたが、これをさらに発展させプログラムの自動生成に対応したものを実装しました。

クラウド温泉のセッションでは、SimpleModelerからg3を使ったGoogle App Engineアプリケーションと、g4を使ったAndroidアプリケーションの自動生成のデモを行う予定です。自動生成したAndroidアプリケーションとGoogle App Engineはもちろん連携動作します。

といいつつ、g3アプリケーション(App Engine)とg4アプリケーション(Android)の自動生成はこれから作るので(汗)、実際にデモまで辿りつけるかは分かりませんが、クラウド温泉を励みにして取り組んでいく予定です。

2011年4月12日火曜日

ドメインルール

前回はエンティティ間の静的な関係(relationship)を定義する以下のドメインモデルを作成しました。モデル駆動開発によるコードの自動生成でも、この範囲の自動生成はすでに実用化または実用化の射程距離内です。



ドメインモデルの重要な軸は(このモデルが典型的な例ですが)問題領域の静的な構造の記述です。従来のアプローチは、データベースに格納するデータとデータ間の構造が中心でしたが、ServiceEventやServiceResourceの導入によって、クラウド上のイベントやリソースもスコープ内に取り込もうというのがここまでの試みです。
クラウドアプリケーションのニーズに対応した形で自動生成の対象範囲が広がりますが、データの範囲をスコープにしているという意味では従来路線を踏襲した漸進的なアプローチともいえます。もう一段、コードの生成範囲を広げられないでしょうか。
モデル駆動開発の問題点の一つは、オブジェクトモデルとしてロジックを宣言的、形式的に記述する方法が確立されていない点です。UMLできちんと記述できるのは状態機械図まで。シーケンス図/コミュニケーション図(旧コラボレーション図)は一応演繹的にも記述できることになっていますが、実用上はインタラクションのインスタンスを具体例として記述するものと考えた方がよさそうです。OCL(Object Constraint Language)は有用ですが、文字通り制約を記述するための言語なので適用範囲が限られます。MDA(Model Driven Architecture)の基盤となるxUML(Executable UML)はこの部分をAction Language (MDA的にはAction Semanticsとその実現言語)で補完していますが、Action Languageは状態機械との連動がオブジェクト指向的には進化であるとはいえ(大多数のオブジェクト指向型言語がそうであるように)手続き型折衷オブジェクト指向プログラミング言語みたいなものであり、現時点の技術では手続き型プログラミング言語を導入しないとロジック記述の問題は解決しないと考えてよさそうです。(いずれこの部分を関数型言語や論理型言語が埋めていくことになるでしょうが、まだまだ先の話です。)
どちみちプログラミング言語的な記述が必要なのであれば、ActionLanguageを使うより、使い慣れているScalaやJava(その他お好みの言語)でロジックは書きたいところです。
そこで、コードの自動生成の対象範囲を広げるための仕掛けとしてSimpleModelingが用意しているドメインモデルのモデル要素がドメインルール(ステレオタイプDomainRule)です。ドメインルールは、ドメインに存在するロジック、責務の中でルールとして扱うと適切なものをオブジェクト化したものです。ドメインルールは以前から導入済みですが、クラウド時代でも引き続き有効なドメインオブジェクトです。
ドメインモデルの中でロジックを記述する方法としては、エンティティオブジェクトのオペレーションとして定義する方法もあります。ドメインルールとはケースバイケースで使い分けることになります。
オペレーションとドメインルールの選択は以下の項目を目安にするとよいでしょう。

  • アプリケーションのカスタマイズ項目になりそうなものはドメインルール。
  • 複数のドメインオブジェクトを横断的に操作するものはドメインルール。
ドメインルールを追加したドメインモデルは以下の通りです。


以下の3つのドメインルールを追加しています。
注目発言ピックアップ
Twitterからツイートを検出するルール
タスク生成
タスクを生成するルール
対応選択
Twitterのツイートから対応を選択するルール
DomainRule対応選択は、DomainRuleタスク生成と合成(composition aggregation)の関係になっており、タスク生成の一部として動作します。
この例では、ドメインルールからドメインイベントやドメインリソースに対する使用(use dependency)を行っていますが、逆にドメインイベントやドメインリソースからドメインルールへの使用(use dependency)もありえます。例えば、消費税計算ルールを請求書発行イベントから使用するといったケースです。
ドメインルールは、ドメインモデルの中でルールを特定するプレースホルダーの役割を担います。前述したようにUMLではロジックを記述できないので、ドメインモデルでできることはここまでという割り切りです。
ただし、ステレオタイプやタグ付き値、関連端(association end)といった修飾パラメタによって生成するコードを特定することができる可能性があります。
たとえば、DomainRuleタスク生成はfactoryというステレオタイプが付いていますが、適切なパラメタを与えればファクトリオブジェクトの実装に落とし込むことは技術的に可能でしょう。
実行プラットフォーム上で動作するフレームワークによって、相当な種類のドメインルールに対応する機能の受け口を実現できるはずです。このようにプラットフォームで対応済みのルールをドメインモデル上で使用することで、ドメインモデルからコード生成できる範囲が格段に広がるでしょう。
また、自動生成には至らなかった場合でも、ロジックのプレースフォルダーとしての存在意義は十分にあります。少なくても、アプリケーションに組み込むためのSPI(Service Provider Interface)といったものの自動生成は可能で、これだけでもずいぶん違うはずです。
ボクが開発している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でプッシュ配信する、というようなアプリケーションを簡単に作れるでしょう。

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

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>