decadence

個人のメモ帳

Java + Bot = Jabot

Javaで動的にプラグインを読むシステムを実現する方法について考えようとした結果Jabotなるものが出来た

github.com

r7kamura/ruboty · GitHubJava移植版

Jabot/Rubotyについて

凡そRubotyについての説明になるが、プラガブルなChat専用Botである

+------+      +---------+      +---------+      +-------+
| User | <==> | Adapter | <==> | Handler | <==> | Brain |
+------+      +---------+      +---------+      +-------+

Userのチャットに対しAdapterが会話を受信したり送信したりする機能を持つ。Adapterにはシェル上で対話機能を実現するShellAdapterや、Slackでのメンションを受信するSlackAdapterなどが存在する。受信したメッセージは複数のHandlerを経由し、Handler毎に定義されたアクションを行う。例えば、PingHandlerには、PINGというメッセージがあればPONGを返すのようなものが定義されている。また永続化層としてBrainが用意されており、これを利用すると特定の語を違う語に置き換えるルールを保存すると、適宜後続のHandlerに変換されたメッセージを送ったり出来るReplaceHandlerなどが作れる。

まさにこのRubotyが持つプラグインシステムが今回試したかったものだった。

プラグインシステム

Adapter, Handler, BrainをまとめてPluginと呼ぶと、利用したいPluginだけを用意・宣言しておいて使い分けたくなる。Rubotyでは必要なHandlerなどをGemfileに記述するだけで、クラスが定義される際にRuboty本体へ情報が保存されるようになっている。Jabotでは、以下のようなplugins.ymlを利用する事でプラグインシステムを成立させるようにした。プラグイン毎に必要なoptionなどもまとめて記述出来るのでわりと理に適っているように思う。

name: jabot
adapter: # require one adapter
  plugin: com.krrrr38.jabot.plugin.adapter.ShellAdapter
  namespace: shell-adapter
  options:
    prompt: "> "
handlers:
  - plugin: com.krrrr38.jabot.plugin.handler.HelpHandler
    namespace: help-handler
  - plugin: com.krrrr38.jabot.plugin.handler.PingHandler
    namespace: ping-handler
brain:
  plugin: com.krrrr38.jabot.plugin.brain.InmemoryBrain
  namespace: inmemory-brain

Java側で動的にプラグインを読む仕組みとして、今回はgitbucketを参考にした。gitbucketではデフォルトパッケージにPluginというクラスを予め用意する事で様々なPluginを読み込んでいる。今回は設定ファイルがあるので、特に気にする事なくパッケージ名から情報を記述するようにし、起動時に必要なPluginを読み込みようにした。

新しいPluginを作成するには、予め抽象クラスでAdapterHandlerBrainが用意されているので、それらに合うような実装をし、classpathの通る所にjarを入れた上でplugins.ymlに必要な情報を追記すれば良い。例えば新しいAdapterを作る際には、どのようにしてデータを受信するのか、どのようにして送信するのか、接続時のアクションなどを定義すれば完成である。

appassembler-maven-plugin / Jabotの使い方

Javaの制約として、コマンドラインツールの作りにくさが挙げられる。Jabotを試したいのならGitHubのReleaseページにある最新のものからjabot-app-*-executable.zipをダウンロードし、解凍した後そのディレクトリでbin/jabotと打てば実行出来る。

これが出来たのはappassembler-maven-pluginが以下のような構造でzipやtar.gzを作ってくれるためである。bin以下のファイルは定義しておいたMainClassを実行するのに十分なクラスパスなどを記述したシェルスクリプトになっている。

├── plugins.yml
├── bin
│   ├── jabot
│   └── jabot.bat
└── lib
    ├── jabot-echo-handler.jar
    ├── jabot-shell-adapter.jar
    └── ... (more custom plugin jar)

まぁ

Ruby普通に使える環境ならRuboty使おう

jabot-handler-pluginだけを使って独自のHandlerを作って欲しかったりと、そんな利用もありーのpluginをmavenのモジュールとかで作ったせいで、リリース作業でmavenに10数個完成物投げたりしたのは面白かった

基本的にはRubotyを移植しただけなんで2日程しか手つけてないし、

だそうだ。