MicroProfile CustomConfigSource With Database
Let's see how to implement a MicroProfile ConfigSource based on values read from a Database.
Join the DZone community and get the full member experience.
Join For FreeWith the MicroProfile-Config API, there is a new and easy way to deal with configuration properties in an application. The MicroProfile-Config API allows you to access config and property values from different sources, like:
- System.getProperties() (ordinal=400)
- System.getenv() (ordinal=300)
- all META-INF/microprofile-config.properties files
Developers can find a good introduction into the MicroProfile Config API here. Of course, developers can also implement your own config source. However, most of the examples are based on reading custom config values from an existing file, like in the example here.
In this article, I will show how you can implement a MicroProfile ConfigSource based on values read from a Database.
You might also enjoy: MicroProfile: What You Need to Know
How to Access a Database
The following example shows how developers can implement a custom ConfigSource reading values from a JPA Datasource or an EJB Service. At first glance, it looks quite easy to inject an external resource or a service to access custom config values provided by your application:
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
....
}
}
However, there is a problem with this direct approach. If you try to inject a JPA Entity Manager or just another EJB into your CustomConfigSource, you will note that your values are not available as expected because the entity manager will be null.
The reason is that in MicroProfile-Config, all ConfigSources are treated as POJOs: the injected values will not be available. For example, imagine another CDI bean may expect a config value to be injected during its startup phase. If your custom ConfigSource itself had dependencies on CDI's, you could get into a startup looping issue. So how can we solve this problem?
The solution is — as often in Java Enterprise — quite simple. To make sure your EntityManager is already injected, you can annotate your custom ConfigSource with @Startup and implement a method annotated with @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
....
}
...
}
Now developers can access your entity manager (or whatever you have injected) in the init() method. Because MicroProfile Config API still treats your config source as a POJO, your class will be constructed twice — first at the beginning of the Config API and second from the CDI implementation on @PostConstruct. So how can we provide the values for both instances?
The solution is quite simple. Because your configSource is a POJO, you can use static member variables to store your values. In this way, each instance of your custom source will see the same values. With the @PostConstruct annotation, we will provide the values in a kind of lazy loading. Take a look at the full example:
@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;
}
}
With the static member variable 'properties', you overload the values from the already constructed ConfigSource. So, any instance of our ConfigSource shares the same values. The values are loaded at a later point in time.
For that reason, our configSource will not show values during the startup phase. This means if you have another CDI bean, you cannot access those values during @PostConstruct. But the values will be available at runtime in any case.
With the disadvantage of the lazy loading mechanism, the solution is quite simple and easy to implement. Of course, you can also use a JNDI Lookup to get the data from a data source without the trick of lazy loading.
The solution shown here allows you to access not only data sources but also any kind of CDI. We use this solution in our open source project 'Imixs-Workflow', which is based in its new version on MicroProfile 2.2.
I hope you will get started with your own MicroProfile ConfigSource soon!
Further Reading
Published at DZone with permission of Ralph Soika. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments