Asynchronous Messaging with Rails

ActiveMessaging を利用したシステム間のデータ連携の話がありました。
ActiveMessaging は、 ActiveMQ + Stomp によるメッセージングソリューションの Rails プラグインです。

http://beechbonanza.blogspot.com/2007/06/asynchronous-messaging-with-rails.html

以下、適当訳です。 (^^;

ほかのアプリケーションといかに統合するかと聞かれたら、多くの Rails 開発者は「REST」と答えるだろう。そして REST と答えたときには、常に「同期」を意味している。それ自体異論はないが、わたしは同期よりも非同期通信の方が好きだ。JMS のような非同期メッセージングソリューションは、次のような利点で定評がある。

  • メッセージプロデューサは「発射して終了」、つまりメッセージを送信して、その後は別の大事な作業に移れる
  • 信頼性を要求されるメカニズムは、コンシューマとプロデューサにおいては比較的単純で、より難しい部分(たとえば永続化とか)はメッセージブローカーで扱われる
  • メッセージ基盤のなかで冗長性を追加するのが容易

より詳しいことは、Grefor Hohpe の Enterprise Integration Patterns という良い本があるのでそちらを。

Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions (Addison-Wesley Signature Series (Fowler))

Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions (Addison-Wesley Signature Series (Fowler))


多くの機会にわたしは同期メッセージングを使い、「これを信頼性のあるものにしたいな」と心のなかで考えていた。HTTP リクエストを投げ、200番が返ってくるのを待つ。ある程度待っても 200番が返ってこないと、もう一度リクエストしてみる。これを何度か繰り返した後、あきらめる。そしてどこかに失敗のログがでている。


「プロセスの外でこの繰り返しができたらいいな」と思う。そこでリクエストを受け取る別のプロセスをたて、メッセージを送り、必要な繰り返しをまかせる。そして突然、わたしは非同期メッセージングを手に入れたことに気づく。非同期メッセージングを回避する REST については聞いたことがないが、Rails コミュニティの誰かが同期メッセージング以外について話題にすることはめったにない。そんなわけで、わたしはプロジェクトの抱えていた問題をどのように解決したかをここで話そうと思う。われわれは ActiveMessaging プラグインを利用した。Thoughtworks のひとによって書かれたものである。ここで ActiveMessaging の利用例を示し、次週ないし数週間にわたって、ちょっと遊んでみた JMS JRuby によるソリューションと対比してみよう。


2つの Rails アプリケーションがある。顧客管理アプリと注文管理アプリだ。顧客管理アプリは顧客に関するあらゆること、個人情報や顧客とのやりとりの記録、顧客の嗜好などを管理する責務をもつ。顧客管理アプリは以下のフィールドをもつリッチな顧客画面がある。


注文管理アプリは注文と顧客を関連づける必要があるが、顧客管理アプリが定期メンテナンスでダウンしている場合でも、注文は依然としてやってくる。注文管理アプリには、名前と id をもつシンプルな顧客画面がある。


顧客管理アプリで顧客の作成、更新、削除をおこなっている。これらのアクションが実行されるたびに、顧客管理アプリは注文管理アプリにメッセージを飛ばす。メッセージは実行されたアクションのタイプ(作成、更新、削除)と、顧客id を含み、(2つの異なるアプリケーション内の)顧客オブジェクトが同期化される。注文管理アプリは、送られたメッセージにしたがって顧客情報を生成、更新、削除する。


適当訳終了。
記事の後半では、生成アクションの連携を例に a13g のセッティング方法、サンプルコードなどが記載されています。
長くなるので端的にまとめると、

  • ActiveMQ サーバのセットアップ
  • 2つのアプリにそれぞれ ActiveMessaging plugin をインストール
  • 顧客管理アプリの説明
    • メッセージ用のオブジェクトを用意
    • Rails observer で 顧客オブジェクトの create イベントをひっかけて、メッセージを作成、シリアライズして、Activemessaging 経由で投げる
    • ActiveMQ サーバと接続するための設定 (注文管理アプリでも同様)
  • 注文管理アプリの説明
  • これで顧客管理アプリの内容がちょっと遅れて注文管理アプリにも反映される



で、似たようなことは AP4R でも可能です。(ようやくここに辿り着いた...)
RubyKaigi2007 でも事例のひとつとして紹介しましたが、いまちょうどそんなことをやってたりします。

  • RubyKaigi2007 発表資料

http://rubyforge.org/docman/view.php/1765/1256/AP4R_on_RubyKaigi2007.pdf


HTTP POST で連携できるので、連携先は Ruby/Rails じゃなくても大丈夫ですが、Rails であればシンプルに以下のようなコードで実現できます。

  • 顧客管理アプリ

1. AP4R を gem インストール
2. AP4RRails plugin をインストール
3. migration で SAF(Stored And Forward) テーブルを作成

4.メッセージのプロデューサを作成

class CustomerController < ApplicationController

  def create
    ap4r.transaction do # 確実な配送保証のためのおまじない
		  ...
      c = Customer.new(params[:name])
      c.save!   # create 処理
			...

      ap4r.async_to({:controller => 'order', :action => 'create_customer'},
                    {:customer_name = c.name}) # form_for ライクにメッセージング
    end

    render ...
  end

  ...
										
end


5. AP4R サーバを起動

  • 注文管理アプリ

1. メッセージのコンシューマを作成

class OrderController < ApplicationController

  def create_customer
    c = Customer.new(params[:customer_name) # メッセージから取り出し
    c.save!   # create 処理
    
    render :text => 'true' # 非同期ロジックの成功判定用のおまじない
  end

  ...
	
end

ざっとこんな感じです。
流れが分かりやすいように order コントローラに create_customer アクションとしましたが、注文管理アプリ内にも customer コントローラがあってその create アクションをたたくほうが自然かもしれません。
ひとまずこれで、顧客管理アプリでの処理の後、非同期で注文管理アプリの処理が実行されることになります。


ただし、デフォルトの AP4R の設定で上記のようなコードだと、同一の Rails サーバ上での非同期メッセージングとなりますので、異なる Rails サーバ間で連携させたいときには設定ファイルを弄ります。

  • $AP4R_HOME/config/queues_*.cfg
...
  acl: allow 127.0.0.1 allow ::1 allow 10.0.0.0/8
dispatchers:
  -
    targets: queue.*
    threads: 4
    modify_rules:
      url: "Proc.new {|url| url.host = 'hoge'; url.port = 4000; url}"
...


ポイントは url: の部分です。
ここで適当に url を書き換えることができます。上記の設定だと、非同期処理として http://hoge:4000/order/create_customer をたたくことになります。
#async_to の第一引数で、async_to({:url => 'http://hoge:4000/order/create_customer'}, ...) としても同様の効果が得られます。


設定ファイルの方が柔軟性が高いので、たとえば以下のように書くことでポート 4001〜4003 番の Rails サーバに処理を散らすなど、分散ロジックなどを比較的簡単に記述することもできます

      url: "Proc.new {|url| url.port = 4001 + rand(3); url}"


以上、システム間のデータ連携のお話でした。
このほか、システム内で重めの処理を切り離したりするときにも非同期メッセージングを活用することができるでしょう。


  • 参考

http://ap4r.rubyforge.org/wiki/wiki.pl?Ja/HomePage

  • dara日記 - AP4R on Rails はじめの一歩

http://d.hatena.ne.jp/darashi/20070615/1181894461

  • kiwamu日記 - URL rewrite フィルタ

http://d.hatena.ne.jp/kiwamu/20070530/1181175145

  • kiwamu日記 - ブロック渡し

http://d.hatena.ne.jp/kiwamu/20070529/1181175126

  • kiwamu日記 - reliable-msg を IPv6 環境で動かすときの注意

http://d.hatena.ne.jp/kiwamu/20070305/1173094913

  • kiwamu日記 - SAF(Store and forward)機能について

http://d.hatena.ne.jp/kiwamu/20061013/1161017209