« 2005年05月 | メイン | 2005年07月 »

2005年06月23日 (木)

RESTfulアプリケーションとCookie [テクニカル]

おおっ、RESTful Application関連のレポートを発見。しかも2年前。

REST版ショッピングカート

方向性はおおむね同意。そして、クッキー使ってもいいんじゃない? たしかにパフォーマンス低下やプライバシー漏洩ってのはあるけど、RESTの原則からいうと、クッキーによってresponseを変えてはならない、ってだけだから、クッキーを手軽なローカルレジストリとして使うのはアリなような気がする。ドメイン別だから便利だし。もちろん、都度サーバに送られたクッキーは使わない。

それとも、ブラウザが開いている間はオンメモリで保持しておいて、ブラウザが閉じる(もしくは別のサイトに移る)ときに、ローカルのクッキーに書き出すっていうことはできないかな。そしてまた開いたときにクッキーから読み出す。そうすれば不要なデータは流れなくなる。

もっというと、クッキーに依存してresponse変えちゃってもいいんじゃないかと思う。広い意味のContent Negotiationとみる。つまり、RESTはURIとHTTP Methodによってリソースが決定されるが、Accept-Languageなどのヘッダによるリソース選択は認めている(と思う、たぶん)。ただしリソースの意味が変わらない範囲において。ならば、クッキーのデータもリソース選択の一要素と認めてもいいんじゃないか、という考え。これはサーバで状態を保持しているわけではないので完全にStatelessである。

ちょっとまだ考えが浅いかも。というか論文とかこのへん読めば答えが書いてあるような気がするのだが、英語読むのめんどい…。その気になったら読むことにしよう…。

追記: 上記の「REST版ショッピングカート」と同じ市山氏のレポートより、クッキーがRESTに合わない理由。

クッキーで反映される前のビューを保存するためにブラウザの履歴機能が使われると、ブラウザーのアプリケーション状態はクッキーに保存された状態と一致しない

確かに。でもこれじゃコンテントネゴシエーションもNGになってしまわないか? うーむ。

投稿者 4bit : 15:36 | コメント (40) | トラックバック (1)

AnnotationとPOJO [テクニカル]

設計と実装の狭間で。 - AJOってどうよ?』を読んで。ちょっと極端な考え方かもしれませんが。

POJOという言葉が、現在どういう意義・意図を持って使われているのかが重要。言葉の発祥としてはどうやらEJBのカウンター的概念として作った言葉らしい(Martin Fowler's Blikiなど参照)。よって複雑さの解消という意義ももちろんあるだろう。しかし、現在では主に「実装依存性の解消」という意義がクローズアップされていると考える。これはTDDやDIにもつながる考え方である。

簡単な例だと、「ライブラリのような外部の特定クラスからの継承をしない」というのがある。クラスの継承は基底クラスへの依存性を生む。つまり外部のライブラリの実装に強く依存したクラスとなってしまうからだ。だからといって、継承しないといっても、極端な例を挙げると、

class Foo
  private bar;
  public Foo() {
    bar = new BarImpl(); // BarImplは外部ライブラリの実装クラス
  }
  ...
}

こんなことをしていると完全にダメだ。BarImplという特定の実装クラスに依存しているからである。これでは結局testabilityが失われ、POJOとしての意義をなさないのではないかと考える。ここまでくるとPOJOという言葉がふさわしいかどうかわからない。だがそういう意義をもって使われている言葉だと私は理解している。

本題。Annotationをつけることは、実装依存性と全く関係がない。「オブジェクトが期待される全ての機能」とは、クラスにコードとして書かれているものがすべてであって、Annotationによって実現される付加的機能はあくまでAnnotationを処理する外部のライブラリによって実現されるのである。そしてそれらの間には(強い)依存性はない。よって、AnnotationをつけたクラスのオブジェクトはPOJOと呼んで全く差し支えない。

投稿者 4bit : 11:54 | コメント (0) | トラックバック (0)

2005年06月21日 (火)

RESTful Web Applicationの可能性 [テクニカル]

RESTについて、前に先輩のsatoshi氏と話をしていたのだが、共通の感想が「WebブラウザからPUT, DELETEメソッドが使えればいいのに…」ということだった。HTML form要素のmethod属性は、仕様として"GET"と"POST"しか許されていない。実装もそうなっているようだ。例えばGekkoの実装の場合「Tociyuki::Diary - Gekkoのform@methodはgetとpostだけを受け付ける

そこでPUT, DELETEなしでWebアプリケーションのURI設計をするとしたらどうなるかなと思い、ちょっと調べてみると、W3CでXFormsという規格があり、他のメソッドも使えるようになっている。XFormsは複雑すぎるということからWeb Forms 2.0という規格も提案されている。

そういえば、最近流行りのAjaxのXMLHttpRequest使ったらできるんじゃないの?

と思って、適当なhtmlファイルを作って試してみたら、見事にIEとFirefoxの両方でPUTもDELETEも成功!(任意のメソッドOK) すばらしい。スクリプト必須ではあるが、現時点でもRESTfulアプリケーションの可能性が見えてきた。

参考リンク:
HTML 作成者のための XForms 入門
Web Forms 2.0 Working Draft

ちなみに作ったhtmlはこんなの。

<script language="javascript" type="text/javascript">
function createXMLHttpRequest() {
   return this.XMLHttpRequest ? 
      new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
}
var req = createXMLHttpRequest();
function send_request(method) {
  req.open(method, "httpmethod.html", true)
  req.send(null)
}
</script>
<input type="button" value="GET" onclick="send_request('GET')"/>
<input type="button" value="POST" onclick="send_request('POST')"/>
<input type="button" value="PUT" onclick="send_request('PUT')"/>
<input type="button" value="DELETE" onclick="send_request('DELETE')"/>

なぜかIEでGETとPOSTだけ成功しなかったのだが、たぶんコードの書き方がどこかおかしいのだろう。GETとPOSTなら普通のフォームでできるし。

Web ApplicationをRESTfulに近づけていくことで、Web Serviceとの垣根をなくす方向を目指したい(すでに一部のBlogシステムはそれに近い)。そのためにはRESTを意識したWeb Application Frameworkが必要。

アプリケーション内部のデータ(コンテンツ?)をすべて統一的な形(XML?)で定義できるようにし、すべてをリソースとみなしてURIを与える。プログラム内でも基本的にそのリソース識別子によってアクセスする。

すべてのデータがXMLもしくはHTMLで定義できるのであれば、それをプログラムで直接扱える形としてのオブジェクトと相互変換することが必要。Object-XMLマッピングの領域。O/R Mappingの技術に近く、実装にアノテーションなどの技術が使えそう。概念的にCMSに近いものになってきそうな気もする。

しかし、Roy Fielding曰く、『Web がうまく機能したのはシステムのインターフェースに object-specific な属性を採用しなかったことにあると僕は考える。だからマーケティングキャンペーンが単にそう言っているからといって、object-specific な属性がWebアーキテクチャの一部になることを許すのは、バカげているだけでなく、私たちが懸命に作ろうとしているシステムの未来に反している』。

んー、つまりWebの世界をOOでラップするのはやめれってことじゃないのかなぁ。僕の言っているのは、レイヤーの境界ではいちいち変換しろってことだから、Fielding氏の主張には反してないと思うのだが。
ほんとうは元の論文を読むべきなんだけど、英語なのでちょっとつらい。。。

メモ:「リソース指向」と「アクティビティー指向」という言葉を使って相違点を探る記事。
http://www-6.ibm.com/jp/developerworks/webservices/041119/j_ws-restvsoap.html

投稿者 4bit : 15:27 | コメント (0) | トラックバック (0)

2005年06月20日 (月)

LAMP [テクニカル]

いつのまにかLAMPのPが、PHPだけじゃなくてPerlとPythonも含む事になってる。えらい拡大解釈じゃないか?と思ったらWikipediaの解説でもそうなってた。もしかして頭文字がPじゃないという理由だけでRubyは仲間はずれですか? (LAMPのP)

同じこと思ってた…。LAMPってPHPだろ~。というかRubyだけ無意味に疎外されてる感じがしてイヤだなぁ。僕の中ではPerlとPHP、PythonとRubyが近いグループだと思ってるんだけど。そして前者はステ。

投稿者 4bit : 19:50 | コメント (0) | トラックバック (0)

2005年06月10日 (金)

metadataライブラリ [テクニカル]

[ruby-talk:93813] Re: Extensible meta-data ? に倣って、メタデータ処理ライブラリを書いてみた。ただし、クラスへのメタデータ付加は必ずselfを指定するようにする。やっぱりちょっとダサいけどしょうがないか。。。

使い方の適当な例は以下。ハンドラの設定部がポイント。

require 'annotation'
MetadataInterfaceName.def('META')
 
# ハンドラの例…属性を定義する
# ハンドラ設定は、メタデータ記述の前に行わなくてはいけない点に注意
# (ただし、定数アノテーション対応のため、記述後からのハンドラ起動機構も設ける予定)
MetadataHash.set_handler(:Attribute) do |owner, elem, data|
  case data
  when :reader, :writer, :accessor
    owner.__send__(:public)
    owner.__send__('attr_' + data.to_s, elem)
  end
end
 
class Foo
  META self, :Entity, :Table => 'foo'
  def foo
    puts "bar"
  end
  META :foo, :Method => "metadata", :Role => "bar"
  META :hoge, :Attribute => :accessor, :Role => "bar2"
end
 
p Foo.metadata
# => {[:hoge, :Role]=>"bar2", [Foo, :Table]=>"foo", [:foo, :Method]=>"metadata", 
#     [:hoge, :Attribute]=>:accessor, [:foo, :Role]=>"bar", [Foo, :Entity]=>true}
p Foo.metadata.fetch_element(:foo)
# => {:Method=>"metadata", :Role=>"bar"}
p Foo.metadata.fetch_name(:Role)
# => {:hoge=>"bar2", :foo=>"bar"}
p Foo.instance_methods - Object.instance_methods
# => ["hoge", "foo", "hoge="]

ちょっと長いけどソースはこちら。

class DoubleKeyHash < Hash
  def initialize(ifnone = nil, &block)
    super
    @key1key2 = {}
    @key2key1 = {}
  end
  def [](*keys)
    if keys.size == 1 and keys[0].kind_of?(Array)
      super(keys[0])
    elsif keys.size == 2
      super(keys)
    else
      raise ArgumentError
    end
  end
  def []=(*args)
    if args.size == 2
      raise ArgumentError unless args[0].kind_of?(Array) and args[0].size == 2
      add_keys(args[0][0], args[0][1])
      double_key_store(args[0][0], args[0][1], args[1])
    elsif args.size == 3
      add_keys(args[0], args[1])
      double_key_store(args[0], args[1], args[2])
    else
      raise ArgumentError
    end
  end
  def delete(key, &block)
    super
    remove_keys(key[0], key[1])
  end
  def update(other, &block)
    raise TypeError unless other.kind_of?(DoubleKeyHash)
    @key1key2.update(other.instance_eval('@key1key2'))
    @key2key1.update(other.instance_eval('@key2key1'))
    super
  end
  def fetch1(key1)
    ret = {}
    @key1key2.fetch(key1, []).each do |key2|
      ret[key2] = fetch([key1, key2])
    end
    ret
  end
  def fetch2(key2)
    ret = {}
    @key2key1.fetch(key2, []).each do |key1|
      ret[key1] = fetch([key1, key2])
    end
    ret
  end
  def double_key_store(key1, key2, value)
    store([key1, key2], value)
  end
  
  private
  def add_keys(key1, key2)
    @key1key2[key1] ||= []
    @key1key2[key1] << key2
    @key2key1[key2] ||= []
    @key2key1[key2] << key1
  end
  def remove_keys(key1, key2)
    if @key1key2.has_key?(key1)
      @key1key2[key1].delete(key2)
      @key1key2.delete(key1) if @key1key2[key1].empty?
    end
    if @key2key1.has_key?(key2)
      @key2key1[key2].delete(key1)
      @key2key1.delete(key2) if @key2key1[key2].empty?
    end
  end
end
 
class MetadataHash < DoubleKeyHash
  @@handler = {}
  @@default_handler = nil
  attr_reader :owner
  def initialize(owner_class)
    super()
    @owner = owner_class
  end
  def double_key_store(elem, name, data)
    super
    if @@handler.has_key?(name)
      @@handler[name].call(owner, elem, data)
    elsif @@default_handler
      @@default_handler.call(owner, elem, name, data)
    end
  end
  def update(other, &block)
    raise TypeError unless other.kind_of?(MetadataHash)
    super
    # handler calling is still not supported
  end
  def fetch_element(elem)
    fetch1(elem)
  end
  def fetch_name(name)
    fetch2(name)
  end
  def self.handler
    @@handler
  end
  def self.set_handler(name, &block)
    @@handler[name] = block
  end
  def self.default_handler
    @@default_handler
  end
  def self.set_default_handler(&block)
    @@default_handler = block
  end
end
 
module MetadataInterface
  def meta(elem, *values)
    md = __send__(MetadataInterfaceName.metadata)
    values.each do |value|
      if value.kind_of?(Hash)
        value.each do |name, data|
          md[elem, name] = data
        end
      else
        md[elem, value] = true
      end
    end
  end
  
  def metadata
    @metadata ||= MetadataHash.new(self)
  end
end
 
class MetadataInterfaceName
  # 名前の再定義
  # MetadataInterfaceName.def('META') のように用いる
  @@meta = :meta
  @@metadata = :metadata
  class << self
    def meta
      @@meta
    end
    def metadata
      @@metadata
    end
    def def(name)
      MetadataInterface.__send__(:alias_method, name, @@meta)
      MetadataInterface.__send__(:remove_method, @@meta)
      @@meta = name
    end
    def def_data(name)
      MetadataInterface.__send__(:alias_method, name, @@metadata)
      MetadataInterface.__send__(:remove_method, @@metadata)
      @@metadata = name
    end
  end
end
 
class Module
  include MetadataInterface
end

ちなみに、metadata定義をC#のattribute流儀の [...] にできないかと思って

MetadataInterfaceName.def('[]')

とやってみたが、[]はselfをつけないと配列リテラルとみなされるためNG。ざんねん。

投稿者 4bit : 19:30 | コメント (0) | トラックバック (0)

2005年06月09日 (木)

Rubyにannotationを定義するとしたら? [テクニカル]

きのうのエントリはちょっと意味不明だったが、結局言いたいことは「Rubyにアノテーションを定義するとしたら、どのような書式がよいか?」ということだ。

同じことを考えている人はいるもので、Annotations in Rubyではチョチョイと1時間でハックしたよ、というコードが載っている。うん、これぐらいシンプルでいいのだ。こういうコードならいくらでも書ける。

で、問題はアノテーション定義の書式。上の場合では

ann :description => "IP address of the mail server", :tip => "Use 'localhost' if you have a good box, sister!"
attr_accessor :server

と使うようだが、直後の属性だけに適用するという規則がどうも不自然。やはりModuleクラスのメソッドを使うならば、publicなどと同じく「引数なしのときは今後このクラスまたはモジュール定義内で新規に定義されるメソッドに同一アノテーションを設定し、引数が与えられた時には引数によって指定されたメソッドにアノテーションを設定する」というのがRuby流儀と考えるのが自然かなぁ。

ってことで、いろいろ考えてみたが、いいアイデアが思いつかず。アイデア求む。

投稿者 4bit : 18:08 | コメント (0) | トラックバック (0)

2005年06月08日 (水)

annotation (a.k.a. metadata) 再考 [テクニカル]

Rubyの attr_* はannotationにほかならない。内部ではメソッド定義でしかないにもかかわらず、「属性」という意味づけが与えられているのだから(RDocなど)。つまり、メソッドに attribute というannotationをつけていることに等しい。

で、Rubyで統一的な annotation を与える書式はどうするのがいいだろうか。やはりRubyでは可読性の高いModuleクラスのメソッドとして定義するべきなのか。

実は暗黙のうちにmetadataを使っているライブラリは多い。

e.g. StrongTyping module:

  attr_accessor_typed String, :foo, :bar

うーん、ダサい。

metadataの意味では、foo, barというメソッドに attribute=>readwrite, type=>String というmetadataを与えていると解釈できる。ならちゃんと並列に同じ記法で書けなきゃ。

Java流なら:


meta_attribute :RW
meta_type String
meta_hoge :key1=>'value1', :key2=>'value2'
def foo
...
end

って感じか。ちょっとダサいかな。。あと意味もわかりにくい。meta_*っていうメソッド名もかぶりそうだし。

もう1つの例。

e.g. ActiveRecord


class Entry < ActiveRecord::Base
def self.table_name; entry; end
belongs_to :blog
belongs_to :author, :foreign_key => 'entry_author_id'
has_one :trackback, :foreign_key => 'trackback_entry_id'
has_many :comments, :foreign_key => 'comment_entry_id'
has_and_belongs_to_many :tbpings, :finder_sql => 'SELECT t.* FROM mt_trackback j, mt_tbping t WHERE t.tbping_tb_id = j.trackback_id AND j.trackback_entry_id= #{entry_id} ORDER BY t.tbping_id'
end

metadata的解釈では、クラスEntryにいろいろmetadataをつけてることになる。Java的アプローチだとこれらはメソッドにつくことになる。さらにオプション項目があればネストしたアノテーションとなる。

メソッドにつけたほうがきめ細かく処理できるはずだが、Rubyではメソッドも自動生成することが多いため、うまい方法がない。必ずdefを書くようにすればできそうだが。(それでもメソッド定義のフックはModule#method_addedで可能だが、クラス定義のフックはどうする?)

Javaで書くとするとこんな感じ?書いたことないから適当だけど。


@Entity
@Table("entry")
public interface Entry {
@ManyToOne
public Blog getBlog();

@ManyToOne
@JoinColumn(name="entry_author_id")
public Author getAuthor();

@OneToOne("trackback")
@JoinColumn(name="trackback_entry_id")
public Trackback getTrackback();

@OneToMany("comment")
@JoinColumn(name="comment_entry_id")
public Set<Comment> getComments();

@ManyToMany("tbping")
@AssociationTable( ... )
public Set<TBPing> getTBPings();
}

投稿者 4bit : 19:18 | コメント (0) | トラックバック (0)

2005年06月07日 (火)

AmritaHandler(仮) 20050607版 [テクニカル]

前回はこちら

変更点は、定数アノテーションをスキャンする部分を独立にクラス化。まだ汎用的にはなってないけど。さらにそれを使ってNeedle登録を自動化。アノテーション INSTANCE, INIT_PARAM が使える。何も書かなければsingletonで登録される。

また、前回作ったTodoアプリケーションActiveRecordでも実際に実装してみた。一応動いたけどチェックボックスがまだちゃんと処理できてない。これはモデル上 true, false で扱っているものをデータベース上では整数値(1, 0)で扱っているミスマッチのため。でもこれってActiveRecordが面倒見てくれてるはずなんだけど…。

todo.rbのコメントにActiveRecordを使う場合のTodoクラスのコードが書いてある。実行するにはActiveRecordとSQLite3/Rubyが必要だが、RubyGemsをインストールしているなら、 gem install activerecord; gem install sqlite3-ruby でOK。もちろんMySQLなどの他のデータベースも使える(はず)。

ActiveRecordについてはRubyist Magazineの「RubyOnRails を使ってみる 第 3 回 ActiveRecord」の記事が詳しいです。

download amritahandler_20050607.zip

投稿者 4bit : 18:50 | コメント (0) | トラックバック (0)