はじめに
drogon はC++によるフルスタックのWebフレームワークです。
サンプルコードはわかりやすさ優先と思われますが,コントローラ内で色々やっています。
この記事は,Java の Spring と同じように,DIコンテナで疎結合にしたい... と思ってそのやり方を調べたメモです。
DIコンテナとしてこの記事では hypodermic を使いますが,他のライブラリでも同じようにできるはずです。
方針
drogon のプラグインとして,hypodermic を保持するものを作ります。
これだけだと,コントローラ(HttpControllerなど)にインジェクションができないので,コントローラ生成をテンプレート特殊化でフックし,hypodermic でインスタンスを作るようにします。
hypodermic を使うプラグイン
こんなコードを書きます。
class HypodermicPlugin : public drogon::Plugin<HypodermicPlugin>
{
std::shared_ptr<Hypodermic::Container> m_container;
public:
// drogon から呼び出されるプラグイン初期化関数
void initAndStart(const Json::Value &config) override
{
// DIコンテナのbuilderに対象のクラスとインスタンスを登録
Hypodermic::ContainerBuilder builder;
// TODO: ここでbuilderにクラスを登録
// DIコンテナ構築
m_container = builder.build();
}
// drogon から呼び出されるプラグイン終了関数
void shutdown() override
{
m_container = nullptr;
}
// コンテナへのアクセッサ
std::shared_ptr<Hypodermic::Container> getContainer()
{
return m_container;
}
};
main関数など初期化時にこのプラグインを登録します。
void main()
{
auto &instance = drogon::app();
instance.addPlugin("HypodermicPlugin", Json::Value::Members{}, Json::Value{});
// 以後,待ち受けポートの設定や instance.run() 呼び出し
// ...
}
これで,HttpController継承クラスでこんな処理が書けます。
SomeClassはhypodermicに登録したタイプです。
auto plugin = drogon::app().getSharedPlugin<HypodermicPlugin>();
auto container = plugin->getContainer();
auto instance = container->resolve<SomeType>();
コントローラ生成処理のフック
上記コードで,コントローラ内で色々なクラス(例えばdrogon::orm::DbClient)をhypodermic から取得できます。
ただ,コントローラそのものはdrogonが生成しているので,コントローラに依存性を注入することができません。
コントローラ生成は,drogonのクラスマップにコントローラが登録される時,ファクトリ関数オブジェクトを同時に登録することで行われています。
具体的な処理は, drogon::DrObject::DrAllocator::registerClass() メソッドです。
以下のようなコードを書くと,テンプレート特殊化でこれを上書きし, hypodermic を使ってコントローラを生成することができます。
SomeClassはhypodermicに登録したコントローラクラスです。
namespace drogon {
// DIコンテナを使ってUserControllerを構築するように drogon を設定(テンプレート特殊化)
template <>
template <>
inline void DrObject<SomeClass>::DrAllocator::registerClass<SomeClass>()
{
DrClassMap::registerClass(
className(),
[]() -> DrObjectBase * { return nullptr; }, // 生成処理(生ポインタ返却)
[]() -> std::shared_ptr<DrObjectBase> { // 生成処理(スマートポインタ返却)
auto plugin = drogon::app().getSharedPlugin<HypodermicPlugin>();
auto container = plugin->getContainer();
return container->resolve<SomeClass>();
});
}
}
生ポインタを取得する関数オブジェクトは(コントローラ生成では)使われてないようなので,手抜きしています。
おわりに
これで,hypodermic のコンストラクタインジェクションをコントローラにも使えるようになります。
Javaのようにアノテーションを書けばDIできるとはならないですが,C++でもDI導入によりクラス間を疎結合にできるのが分かってよかったです。