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