11 分のあれに 5 分くらいでウェブサービス API を追加するシナリオ

http://itoshi.tv/d/?date=20060506#p01ActionWebService (AWS) をつかってウェブサービス API を追加してみた.タイプするだけなら 5 分くらいでできるシナリオである.

仕様

今回のウェブサービス API の仕様は,データベースのエントリを ID で取得したり,ID のリストを取得する基本的で簡単なものを考える.

  • API 名:Expression
  • affyid の発現量を取得するメソッド:find_expressions_by_id(affyid)
    • 引数 affyid は String
    • 返り値は Float の Array
  • affyid の任意の day の発現量を取得するメソッド:find_expression_by_id(affyid, day)
  • 引数 affyid は String
    • 引数 day は Int
    • 返り値は Float
  • affyid のリストを取得するメソッド:find_all_affyid
    • 引数はなし
    • 返り値は String の Array

次に,ウェブサービスのためのコードを生成する.なお,以降の作業は実働する11 分のあれディレクトリ expview の中でおこなう.

ruby script/generate web_service Expression find_expression_by_id find_expressions_by_id find_all_affyids

仕様にもとづいて API 定義 app/api/expression_api.rb を埋める.

生成された状態.

class ExpressionApi < ActionWebService::API::Base
  api_method :find_expression_by_id
  api_method :find_expressions_by_id
  api_method :find_all_affyids
end

こうする.

class ExpressionApi < ActionWebService::API::Base
  api_method :find_expression_by_id,
             :expects => [{:affyid => :string}, {:day => :int}],
             :returns => [:float]

  api_method :find_expressions_by_id,
             :expects => [{:affyid => :string}],
             :returns => [[:float]]

  api_method :find_all_affyids,
             :expects => [],
             :returns => [[:string]] 
end

expects に引数の名前と型情報,returns に返り値の型情報を Hash の形で記述する.DSL 風味.

つぎに,仕様にもとづいて API のコントローラ app/controllers/expression_controller.rb を書く.
生成された状態.

class ExpressionController < ApplicationController
  wsdl_service_name 'Expression'

  def find_expression_by_id
  end

  def find_expressions_by_id
  end

  def find_all_affyids
  end
end

こうする.

class ExpressionController < ApplicationController
  wsdl_service_name 'Expression'
  web_service_scaffold :invoke

  def find_expression_by_id(affyid, day)
    @expression = Expression.find_by_affyid(affyid) 
    if [0, 2, 4, 10].include?(day)
      @expression["d#{day}"]
    else
      -1.0
    end
  end

  def find_expressions_by_id(affyid)
    @expression = Expression.find_by_affyid(affyid) 
    ['d0', 'd2', 'd4', 'd10'].map {|m| @expression[m] }
  end

  def find_all_affyids
    Expression.find(:all).map {|x| x.affyid }
  end
end

サーバの起動と web service scaffold で動作確認

ruby script/server
open http://localhost:3000/expression/invoke

app/controllers/expression_controller.rb の web_service_scaffold :invoke によって,ウェブサービスのクライアントが提供されている.なお,open コマンドは Mac OS X 付属のものである.
ここで注意する必要があるのは find_all_affyids() は返り値が非常に大きいため,レンダリングに非常に多くのリソースを必要とするのでブラウザが固まりがち.invoke ボタンをクリックしてはいけない.
これくらいの API だったら 5 分くらいでタイプできるでしょう.

SOAP::WSDLDriver をつかったクライアント

SOAP::WSDLDriver をつかったクライアントの例.ウェブサービスで利用できるメソッドは methods(false) で調べることができる.

require 'soap/wsdlDriver'

wsdl_uri = "http://localhost:3000/expression/service.wsdl"
factory = SOAP::WSDLDriverFactory.new(wsdl_uri)
driver = factory.create_driver

method_list = driver.methods(false)
p method_list #=> ["findExpressionsById", "FindAllAffyids", "findAllAffyids", "FindExpressionById", "findExpressionById", "FindExpressionsById"]

expression = driver.findExpressionById("100015_at", 0) 
p expression #=> 5.55660043997955

expression = driver.send("FindExpressionById", "100015_at", 0)
p expression #=> 5.55660043997955

expressions = driver.findExpressionsById("100015_at") 
p expressions #=> [5.55660043997955, 5.6629197160292, 5.60480888509297, 5.72528846920502]

affyid_list = driver.findAllAffyids()
p affyid_list #=> ["100001_at", "100002_at", "100003_at", ...

まとめ

Rails すごい.Action Web Service をつかうと本当に簡単に API の公開ができる.WSDL を手書きしたくない.
API 名を考えるのはしんどい.
ウェブサービス API とくに SOAP を使ったサービスを提供すると,Taverna といったワークフローエディタから利用することができる.(じつはこの例では問題があり,そのままでは利用できない.)