読者です 読者をやめる 読者になる 読者になる
無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

Rubyでクラスをホットデプロイする手段を考える

Rubyでクラスのホットデプロイをサポートするにはどういった方法がいいかな、と考えてみた。

案1:evalを使う

そもそもRubyでは、ロード済みクラスを後から書き換える、といったことが可能なので単純な手段としては新しいクラスが書かれた文字列を読み込んでevalしてやればとりあえずクラスの更新はできる。

# クラスローダー(こんな適当な実装だと使い物にならんけど)
class ClassLoader
  # 文字列からクラスをロードする
  def define_class( class_definition )
    eval class_definition
  end
  # クラス名のクラスを得る
  def load_class( name )
    eval name
  end
end

# クラス定義
kitten_definition =<<-CLASS
  class Kitten
    def meow; "meow!"; end
  end 
CLASS

# クラスローダーを生成
loader = ClassLoader.new
loader.define_class kitten_definition # クラスをロード
kitten = loader.load_class("Kitten").new # ロードしたクラスからインスタンスを生成
puts kitten.meow # -> "meow!"


# クラス定義を更新
kitten_definition =<<-CLASS
  class Kitten
    def meow; "meow!meow!"; end
  end 
CLASS
loader.define_class kitten_definition # 再ロード
kitten2 = loader.load_class("Kitten").new # 新規にインスタンスを生成
puts kitten2.meow # -> "meow!meow!" 

# クラスを更新するとすでに作成済みのインスタンスの動作も変わる
puts kitten.meow # -> "meow!meow!"

実行結果です。

meow!
meow!meow!
meow!meow!

文字列がファイルに保存されているのであれば、loadとかも使えそう。
ただ、これだとクラスの改変により既存の作成済みインスタンスの動作も変わってしまう。今作ってるプログラムではこれはちょっと困る。クラスの改変後、新規に作成したインスタンスから動作が変わるようにしたい。

案2:module_evalを使う

ということで、無名モジュールを作成してそこにクラスを定義する方法を考えてみた。同じクラスローダーを使う場合はeval版と同じだが、別のクラスローダーを作成して改変後のクラスをロードすると、既存インスタンスの動作を変更することなくクラスを差し替えられる。

# クラスローダー(こんな適当な実装だと使い物にならんよー。)
class ClassLoader
  def initialize
    @m = Module.new
  end
  # 文字列からクラスを生成する
  def define_class( class_definition )
    @m.module_eval class_definition
  end
  # クラス名のクラスを得る
  def load_class( name )
    @m.const_get name
  end
end


# クラス定義
kitten_definition =<<-CLASS
  class Kitten
    def meow; "meow!"; end
  end 
CLASS

# クラスローダーを生成
loader = ClassLoader.new
loader.define_class kitten_definition # クラスをロード
kitten = loader.load_class("Kitten").new # ロードしたクラスからインスタンスを生成
puts kitten.meow # -> "meow!"


# 新しいクラス定義
kitten_definition =<<-CLASS
  class Kitten
    def meow; "meow!meow!"; end
  end 
CLASS

# 別のクラスローダーでロードすれば既存インスタンスには影響しない
loader2 = ClassLoader.new
loader2.define_class kitten_definition # クラスをロード
kitten2 = loader2.load_class("Kitten").new # ロードしたクラスからインスタンスを生成
puts kitten2.meow # -> "meow!meow!"
puts kitten.meow # -> "meow!"

# クラスローダーのクラスを更新すればそこから作成したインスタンスの動作も変わる
loader.define_class kitten_definition # 再定義
puts kitten.meow #  -> "meow!meow!"

実行結果です。

meow!
meow!meow!
meow!
meow!meow!

読み込むクラス定義ごとにクラスローダーを用意すれば、意図しないクラスの上書きによる動作不整合も防げるメリットがあるかな。あと、トップレベル?にクラスを定義しないので、クラスの追加先となる無名モジュールを汚せばセーフレベルを上げた環境でもクラスを定義できる(はず)。