2011年11月20日日曜日

OSGi で GoF の Strategy

久しぶりに OSGi でもいじってみるかと思い立つ。

Wikipedia に載ってる Strategy パターンの サンプル・コードでも OSGi で動かしてみよっかなと。今回は、Equinox でやってみる。

OSGi 環境を作る

  • 適当な場所に osgi-test フォルダを作る
  • osgi-test フォルダ 下に plugins フォルダ を作る
  • Eclipse の plugins下から以下のファイルを osgi-test/plugins フォルダ 下にコピーする。
    • org.eclipse.osgi_*.jar
    • org.eclipse.equinox.ds_*.jar
    • org.eclipse.equinox.util_*.jar
    • org.eclipse.osgi.services_*.jar
うちの環境だとこんな感じになる
$ cd /tmp/osgi-test
$ tree
.
└── plugins
    ├── org.eclipse.equinox.ds_1.3.1.R37x_v20110701.jar
    ├── org.eclipse.equinox.util_1.0.300.v20110502.jar
    ├── org.eclipse.osgi.services_3.3.0.v20110513.jar
    └── org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar

起動して、他のプラグインも追加しておく

$ java -jar plugins/org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar -console

osgi> ss
Framework is launched.
id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106

osgi> install file:///tmp/osgi-test/plugins/org.eclipse.osgi.services_3.3.0.v20110513.jar
Bundle id is 1
osgi> install file:///tmp/osgi-test/plugins/org.eclipse.equinox.util_1.0.300.v20110502.jar
Bundle id is 2
osgi> install file:///tmp/osgi-test/plugins/org.eclipse.equinox.ds_1.3.1.R37x_v20110701.jar
Bundle id is 3

osgi> start 1
osgi> start 2
osgi> start 3
osgi> ss
Framework is launched.
id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE      org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE      org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE      org.eclipse.equinox.ds_1.3.1.R37x_v20110701

環境が出来たので、以降ではプラグインを作って動かしてみる。先に書いたとおり、Wikipedia にある Strategy のサンプルコードをネタにして、Strategy、ConcreteStrategyAdd、ConcreteStrategyMultiply、Context を連動させてみる。

Strategy の作成〜インストール

まずは、Strategy プラグインプロジェクトの作成

  • [New]->[Plugin-Project]
  • Project Name には "ex1.osgi.strategy.strategy"を指定、Activetor 無し、tempate 不使用で[Finish]
  • MANIFEST.MF の [Runtime]タブを開いて、[Exported Packages]に"ex1.osgi.strategy.strategy" を追加する。
  • 以下の Wikipedia から持ってきた、以下のコードを追加する
    package ex1.osgi.strategy.strategy;
    public interface Strategy {
       int execute(int a, int b);
    }
できたら以下のように Export しておく。
  • プロジェクトのコンテキストメニューから、[Export]->[Deployable plug-ins and fragments] を選択
  • [Destination] / [Directory]に、osgi-test フォルダを指定する
  • [Options] で、"Package plug-ins as individual JAR archives"のみチェックされている事を確認して[Finish]
エクスポートしたら、OSGi に install しておく
osgi> install file:///tmp/osgi-test/plugins/ex1.osgi.strategy.strategy_1.0.0.201111202011.jar
Bundle id is 4
osgi> refresh
osgi> ss
Framework is launched.
id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE      org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE      org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE      org.eclipse.equinox.ds_1.3.1.R37x_v20110701
4 RESOLVED    ex1.osgi.strategy.strategy_1.0.0.201111202011
ss や refresh といったコマンドの意味は、OSGi コンソールから help と打てば、説明が出てくる。

ConcreteStrategyAdd の作成〜インストール

  • [New]->[Plugin-Project]
  • Project Name には "ex1.osgi.strategy.add"を指定、Activetor 無し、tempate 不使用で[Finish]
  • MANIFEST.MF の [Dependencies]タブを開いて、[Required Plugins]に"ex1.osgi.strategy.strategy" を追加する。
  • 以下のようにコード追加する。
    package ex1.osgi.strategy.add;
    import ex1.osgi.strategy.strategy.Strategy;
    
    public class ConcreteStrategyAdd implements Strategy {
        public int execute(int a, int b) {
            System.out.println("Called ConcreteStrategyAdd's execute()");
            return a + b;  // Do an addition with a and b
        }
    }
  • プロジェクト直下に OSGI-INF フォルダを追加する
  • OSGI-INF フォルダのコンテキストメニューから、[New]->[Component Definition]を選択
  • "Component Definition Information" / Class に ex1.osgi.strategy.add.ConcreteStrategyAdd を指定して[Finish]
  • component.xml の Service タブで、[Provided Services]に ex1.osgi.strategy.strategy.Strategy を追加する
これも Export して、OSGi にインストールしておく。

Context の作成〜インストール

次は ConcreteStrategyMultiply をやる前に、Context に行ってみる

  • [New]->[Plugin-Project]
  • Project Name には "ex1.osgi.strategy.context"を指定、Activetor 無し、tempate 不使用で[Finish]
  • MANIFEST.MF の [Dependencies]タブを開いて、[Imported Packages]に"ex1.osgi.strategy.strategy" を追加する。
  • 以下のコードを追加する。Wikipedia のサンプルコードでは、Context#executeStrategy() を実行するのは StrategyExample#main() だけど、面倒だから strategy が関連付けられたときに Context 自身が executeStrategy() のテストコードを実行するようにした。
    package ex1.osgi.strategy.context;
    import ex1.osgi.strategy.strategy.Strategy;
    
    public class Context {
       private Strategy strategy;
     
       public synchronized void setStrategy(final Strategy strategy) {
          this.strategy = strategy;
          testExecuteStrategy();
       }
       public synchronized void unsetStrategy(final Strategy strategy) {
          if (strategy == this.strategy) this.strategy = null;
       }
       public int executeStrategy(int a, int b) {
          return strategy.execute(a, b);
       }
       public void testExecuteStrategy() {
          final long l = System.currentTimeMillis();
          final int a = (int) (l % 10);
          final int b = (int) (l / 10 % 10);
          final int result = executeStrategy(a, b);
          System.out.printf("execute(%d, %d) = %d", a, b, result);
       }
    }
  • プロジェクト直下に OSGI-INF フォルダを追加する
  • OSGI-INF フォルダのコンテキストメニューから、[New]->[Component Definition]を選択
  • "Component Definition Information"/Class にex1.osgi.strategy.context.Context を指定して[Finish]
  • component.xml の [Service]タブで、[Referenced Services] に Strategy を追加する
  • 追加した Strategy を選択して[Edit]、"Bind"にsetStrategy、"Unbind"にunsetStrategy を指定する
できたら、これもエクスポートして、OSGiにインストールしておく。


動作確認

とりあえず、ここまでで動かしてみる。

osgi> start 4
osgi> start 5
osgi> start 6
Called ConcreteStrategyAdd's execute()
execute(3, 1) = 4
osgi> 
期待通りに動作する。(実は、start 4 は無くても良いし、start 5 と start 6 の順番は逆でも良い。)

ConcreteStrategyMultiply の追加と動作確認

最後に、ConcreteStrategyMultiply を付け加えて、ConcreteStrategyMultiply と差し替えてみる。

  • [New]->[Plugin-Project]
  • Project Name には "ex1.osgi.strategy.multiply"を指定、Activetor 無し、tempate 不使用で[Finish]
  • MANIFEST.MF の [Dependencies]タブを開いて、[Required Plugins]に"ex1.osgi.strategy.strategy" を追加する。
  • 下のようにコード追加
    package ex1.osgi.strategy.multiply;
    import ex1.osgi.strategy.strategy.Strategy;
    
    public class ConcreteStrategyMultiply implements Strategy {
        public int execute(int a, int b) {
            System.out.println("Called ConcreteStrategyMultiply's execute()");
            return a * b;   // Do a multiplication with a and b
        }
    }
    
  • プロジェクト直下に OSGI-INF フォルダを追加する
  • OSGI-INF フォルダのコンテキストメニューから、[New]->[Component Definition]を選択
  • "Component Definition Information"/Class に net.yasuabe.osgi.ex1.strategy.multiply.ConcreteStrategyMultiplyを指定して[Finish]
  • component.xml の Service タブで、[Provided Services]に ex1.osgi.strategy.strategy.Strategy を追加する
これもエクスポートして、OSGiにインストールしておく。

エクスポートしたら、Context から実行される Strategy を、Add から Multiply に変えてみる。

osgi> ss
Framework is launched.

id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE      org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE      org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE      org.eclipse.equinox.ds_1.3.1.R37x_v20110701
4 ACTIVE      ex1.osgi.strategy.strategy_1.0.0.201111202011
5 ACTIVE      ex1.osgi.strategy.add_1.0.0.201111202028
6 ACTIVE      ex1.osgi.strategy.context_1.0.0.201111202042
7 RESOLVED    ex1.osgi.strategy.multiply_1.0.0.201111202123

osgi> stop 5
osgi> start 7
Called ConcreteStrategyMultiply's execute()
execute(3, 1) = 3
osgi> ss
Framework is launched.
id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE      org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE      org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE      org.eclipse.equinox.ds_1.3.1.R37x_v20110701
4 ACTIVE      ex1.osgi.strategy.strategy_1.0.0.201111202011
5 RESOLVED    ex1.osgi.strategy.add_1.0.0.201111202028
6 ACTIVE      ex1.osgi.strategy.context_1.0.0.201111202042
7 ACTIVE      ex1.osgi.strategy.multiply_1.0.0.201111202123
osgi> stop 7
osgi> start 5
Called ConcreteStrategyAdd's execute()
execute(4, 3) = 7
osgi> 
Context を起動したまま、Strategy を差し替えられるのが確認できる。

雑感

今まで、何回か、いわゆるパッケージ開発案件に関わってきたけど、納品先個別の要件の扱い方について、何パターンかの手法がある。

一番泥臭いのだと、トランクのソースコードに個別要件対応のコードを入れてしまって、条件文で切り分けるというベタベタのローテク手法がある。「バカじゃね?」って思う人も多いかもしれないけど、実際の現場では結構多い。

それよりは、少しはマシな手法だと、納品先別にブランチを切って個別要件はそこに入れていくという事になる。まあ、これも知的な方法とは言いにくいけど、割と普通に行われている。

もっと良い方法としては、パッケージ共通部分と個別にカスタマイズ可能な部分について最初に見通しを立てた上で、カスタマイズ可能な部分がプラガブルになるようにアーキテクチャを設計する手法がある。

この手法をとると、機能の差し替えや追加、また管理面においてもリスクが少なくて、柔軟なソフトウェアになるんだけど、この場合にもさらに、プラグインの方式を自分で作るか、既存の仕様や製品を使うかで道が分かれる。

必ずしも自作にこだわる必要がないとしたら、OSGi がかなりの有力候補なのではないかと思う。(個人的には、はっきり言って、かなり高い割合で自作の道を取るのは不正解だと思うけど。)

0 件のコメント:

コメントを投稿