このエントリは以下のエントリをベースにしています。
This entry is based on the following one written by Ralph Soika (Project lead of Imixs-Workflow).
https://microprofile.io/2019/06/18/microprofile-customconfigsource-with-ejbs/
https://ralph.blog.imixs.com/2019/06/11/microprofile-customconfigsource-with-database/
MicroProfile Config APIでは、アプリケーションの構成プロパティを簡単かつ新しい方法で扱うことができます。
Configuration for MicroProfile
https://microprofile.io/project/eclipse/microprofile-config
このAPIを使うと、以下のような異なるソースの構成やプロパティの値にアクセスできます。
- System.getProperties() (ordinal=400)
- System.getenv() (ordinal=300)
- すべての META-INF/microprofile-config.properties ファイル
MicroProfile Config APIの手始めには以下をどうぞ。
Eclipse MicroProfile Config – what is it?
https://www.eclipse.org/community/eclipse_newsletter/2017/september/article3.php
もちろん、ご自身のConfigSourceも実装できますが、大多数は以下の例のように既存ファイルからのカスタム構成値の読み取りをベースにしています。
Microprofile Config: Creating a Custom ConfigSource
https://rhuanrocha.net/2018/12/21/microprofile-config-creating-a-custom-configsource/
このエントリではデータベースから読み取った値に基づいたMicroProfile ConfigSourceの実装方法をご紹介します。
How To Access a Database?
以下の例は、JPAデータソースもしくはEJBサービスから値を読み取るカスタムのConfigSourceを実装方法を示したものです。一見すると、アプリケーションが提供するカスタム構成ファイルへアクセスするための外部ソースやサービスを注入するのは簡単そうに見えます。
public class MyConfigSource implements ConfigSource {
@PersistenceContext(unitName = ".....")
private EntityManager manager;
@Override
public String getValue(String key) {
.....
}
@Override
public Map<String, String> getProperties() {
// read data form JPA Entity manager
....
}
}
しかしながら、この直接的なやり方には問題があります。JPA Entity Managerや単なる別のEJBをCustomConfigSourceに注入しようとすると、このEntity ManagerがNullのため、値が想定通りに利用できないことに気づくでしょう。
この理由は、MicroProfile ConfigではすべてのConfigSourcesをPOJOとして取り扱うためです。そのため、注入された値は利用できません。例えば別のCDI beanが起動時に構成値が注入されることを想定していると考えてください。カスタムのConfigSource自体がCDIに依存していると、起動ループの問題が発生する可能性があります。その場合、どうすればよいでしょうか。
解決策は、Java Enterpriseではよくあることですが、非常にシンプルです。EntityManagerが注入済みであることを確実にするため、カスタムConfigSourceに@Startupを付け、@PostConstructをつけたメソッドを実装すればよいのです。
@Startup
@Singleton
public class MyConfigSource implements ConfigSource {
@PersistenceContext(unitName = ".....")
private EntityManager manager;
@PostConstruct
void init() {
// load your data from teh JPA source or EJB
....
}
...
}
こうすれば、init()メソッドで、自身で用意したEntity Manager(もしくは注入されたものすべて)にアクセスできます。MicroProfile Config APIは構成ソースをPOJOと引き続き見なしているため、作成したクラスを2度生成します(1回目はConfig APIから、2回目は@PostConstructのCDI実装からそれぞれコンストラクタが呼び出される)。では、両インスタンスに値を設定するにはどうすればよいのでしょうか。
こちらも解決策は極めてシンプルです。ConfigSourceはPOJOなので、静的メンバー値を使ってあたいを格納できます。この方法で、カスタムConfigSourceの各インスタンスで同一の値を確認できるのです。@PostConstructをつけると、ある種の遅延ロードで構成の値を設定します。以下の例をご覧ください。
@Startup
@Singleton
public class MyConfigSource implements ConfigSource {
public static final String NAME = "MyConfigSource";
public static Map<String, String> properties = null; // note to use static here!
@PersistenceContext(unitName = ".....")
private EntityManager manager;
@PostConstruct
void init() {
// load your data from teh JPA source or EJB
....
// override the static property map..
properties.put(....)
}
@Override
public int getOrdinal() {
return 890;
}
@Override
public String getValue(String key) {
if (properties != null) {
return properties.get(key);
} else {
return null;
}
}
@Override
public String getName() {
return NAME;
}
@Override
public Map<String, String> getProperties() {
return properties;
}
}
静的メンバー値であるpropertiesを使い、すでに生成済みのConfigSourceからの値をオーバーロードします。これにより、ConfigSourceの任意のインスタンスは同じ値を共有します。値は後でロードされるため、ConfigSourceで起動時に値は設定されません。これはつまり別のCDI beanがあれば、@PostConstructの愛大これらの値にアクセスできない、ということです。しかしながら、値は実行時には利用できるようになっています。
遅延ロードメカニズムの不利な点があるため、この解決策は実装が極めてシンプルかつ簡単です。もちろんJNDI Lookupを使って、遅延ロードのトリックを使わずにデータソースからデータを取得することもできます。ここで紹介した解決策を使うと、データソースだけでなく、任意のCDIにアクセスできます。オープンソースプロジェクトlmixs-Workflowの最新版はMicroProfile 2.2ベースですが、この中でこの解決策を使っています。
Imixs Workflow – Open Source Workflow Engine for Human-Centric BPM
https://www.imixs.org/