夢とガラクタの集積場

落ちこぼれ三流エンジニアである管理人の夢想=『夢』と、潰えた夢=『ガラクタ』の集積場です。

並列バッチデータ転送OSSのEmbulkをソースコードリーディングしてみる(その2:初期化

こんにちは。

前回は起動時のさわりだけでしたので、今回からまともに中身を読む形になりますね^^;

1.クラス概要構成

JRubyによる起動処理部分が終わり、
Javaに戻ったのでまずはJavaクラスの概要構成から確認してみます。

概要の構成はこれだけです。
EmbulkはGuiceによるインジェクションで必要なオブジェクトを取得して
使用する形になるので、固定的に起点となるRunnerクラスから参照が行われているのは
上記の図の要素だけになります。

各クラスの解説は下記の通りです。

Runner

JRubyから起動されるEmbulkの本来のメインクラス。
各種オブジェクトの初期化後、run/cleanup/guess/previewといった個別コマンドの処理を行うクラス。

DataSourceImpl

システムプロパティに設定されたembulk関連の定義(embulk.で始まるもの)を
JSON形式で読み込んで保持し、Embulkプロセス内で自由に使用するために用いられるクラス。

EmbulkService

下記のモジュール類を用いて
インジェクション用のオブジェクト(Guice:Injector、SpringだったらContextみたいなもの)を
生成するクラス。

  • SystemConfigModule
  • ExecModule
  • ExtensionServiceLoaderModule
  • BuiltinPluginSourceModule
  • JRubyScriptingModule

・・という動作のため、上記の〜Moduleクラス群が
実際にインジェクションされるクラスを設定している形になりますね。

2.Moduleクラス群の設定内容

〜Moduleクラスの処理を見てみると下記のようになります。

SystemConfigModule
  • bind定義:DataSourceImpl > ConfigSource(@ForSystemConfig)
ExecModule
  • bind定義:LoggerProvider > ILoggerFactory(Provider)
  • bind定義:ModelManager(Singleton)
  • bind定義:BufferAllocator > BufferAllocator(Singleton)
  • bind定義:GuessExecutor.GuessParserPlugin > ParserPlugin(@Named("ParserPlugin.system_guess"))
  • bind定義:SamplingParserPlugin > ParserPlugin(@Named("ParserPlugin.system_sampling"))

Plugin系のクラスについてはアノテーションでPluginと名前を指定してインジェクションする形になりますが、
FileInputPlugin実装クラスとFileOutputPlugin実装クラスについては
Pluginの実行制御を行うFileInputRunner/FileOutputRunnerという形で登録されるようです。
理由は今後読み進めた際に確認してみます。

  • bind定義:ObjectMapper > ObjectMapperProvider

上記のObjectMapperProviderにおいて、
TimeZone、Timestamp、Charset、GuavaDataType、JodaDataTypeのオブジェクト変換に対応する
ObjectMapperが生成されています。
文字列が時刻として上手く解釈されるのはここで生成されるObjectMapperのおかげですかね。

ExtensionServiceLoaderModule

ServiceLoaderを用いてExtension定義(META-INF/services/org.embulk.exec.Extensionから読み込んだ結果)を
読み込み、Extensionを介してModuleのBindを行う。

現状、「org.embulk.standards.StandardPluginExtension」が定義されており、
embulk-standardプロジェクトで作成されたPluginがBindされています。
これと同じ方式でPluginをロードする流れを作れれば、JVM系言語で開発したPluginもロードが出来そうです。

JVM系言語で追加のPluginを記述したければ、下記の4手順を踏む必要がありそうです。

  1. 追加Pluginを開発する。
  2. 「追加Plugin」をロードするModuleを開発する。
  3. 「追加PluginをロードするModule」を返すExtensionを開発する。
  4. Extension定義に「追加PluginをロードするModuleを返すExtension」のクラスを記述する。

ただ、これは自前でEmbulkをビルドするならいくらでも可能でしょうけど、
Pluginとして提供は・・可能なんですかね。
ServiceLoaderがクラスパス上に存在する「META-INF/services/org.embulk.exec.Extension」を全て読み込む、
という動作であれば、Pluginとして提供するJarに「META-INF/services/org.embulk.exec.Extension」を含めておき、
Pluginとして提供するJarファイルがEmbulk起動時のクラスパスに配置されていれば可能にはなります。

実際どうなるかはやってみるしかなさそうではあります。

BuiltinPluginSourceModule
  • bind定義:InjectedPluginSource > PluginSource(Multi)

Multibinderを使用していますが、現状InjectedPluginSourceのみがBindされています。

JRubyScriptingModule
  • bind定義:ScriptingContainerProvider > ScriptingContainer(Provider、Singleton)
  • bind定義:JRubyPluginSource > PluginSource(Multi)

PluginSourceに対するMultibinderにJRubyPluginSourceも設定されていますが、
この場合PluginManagerで両方のPluginSourceがロードされるんでしょうか・・?


・・と、こんな感じの初期化処理でした。
JVM系言語で作成されたPluginをロードするための流れが見えたのは非常に収穫がありました。

尚、今回Guiceを使ったコードを始めてきちんと読んでみましたが、
インジェクション用の定義をコードに書く分、Springよりも直観的に読みやすくわかりやすいですね。
ただ、その代わりインジェクションするものを変える場合はコードを修正する=再ビルドする、
が絡んでくるわけですが。

今回のEmbulkのようにインジェクションするものは固定で良くて、
プラグインのロードによって機能を設定する、という用途では非常に使いやすそうです。