Implementing External Configuration Store Pattern with Jakarta EE

In my another posts I explained ways to turn your Jakarta EE application decoupled. In this post I will cover how to decouple the application configuration from our application.

Software delivery have many steps that need be done and one of these step is make the configuration of our application, such as configuration about file system and directories, configuration about paths to other services or resource, configuration about database and others configuration about environment.

Generally, we works with development environment, testing environment, staging environment and production environment. The application need be configured in each of these environment that have its values of configuration — each environment have the same properties but the value of these properties can be different — that allow the application to work. Many developers make the configuration using a configuration file (.properties, .xml or another) inside application to configure them, but this way turn the package of the application coupled with the environment, and the developer need generate one package to each environment. Each package need know the details of the environment that it will running. It is a bad practices and increase the complexity of  the delivery of application both to application with monolithic architecture and to application with microservice architecture.

External configuration store pattern is an operational pattern (some literatures define as architecture pattern or cloud design pattern) that decouple the configuration details from application. With this, the application don’t know the value of configuration properties, but only know which properties it need to be read from configuration store. Below we have the figure showing the diference between application using external configuration store pattern and application don’t using external configuration store pattern.

Blank Diagram (1)

Benefits of using external configuration store pattern

The use of external configuration store pattern have several benefits, but I will talk about some. The main benefit is that we can update any configuration values without shall rebuild our application. Once the package was generated, then this package will running on any environment except if another problem that is not related with configuration occur or if the environment is with a wrong configuration. Furthermore, we allow another team (infrastructure team or middleware team) manage the configuration without need a help of developer, because the package of application don’t need be updated.

Another benefit of external configuration store pattern is that it can make all configuration centralized and many applications can read the configuration properties from the same location.

Implementing external configuration store pattern using Jakarta EE

The implementation of external configuration store pattern can be done with the following ways:

  • Using the application server as configuration server by system properties
  • Using an external file or a set of external files.
  • Using a datasource (relational database, NoSQL or other)
  • Using a custom configuration server

In this post I’ll show you how to implemente external configuration store pattern using the application server as configuration server by system properties and using an external file or a set of external files.

In our scenario, we’ll have three JAX-RS resources, one to return a welcome message, other to upload files and other to download files. On resource that return a welcome message we’ll have one method to get message from system properties of application server and another method to get message from an external file. To implement that we will use CDI producer to read the properties both from application server and from external file. Furthermore, we’ll create the qualifier @Property to be used by producer at moment of injection.

Creating the configurationStore.properties

This file is used when the application uses a external file. This is the only configuration that is configured inside application, to permit the application know where is the configuration store.


path=${path_to_configuration_store}

Implementing Qualifier

The code below has the implementation of qualifier used to configure the injection and permit the producer product the value injected.

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface Property {

    @Nonbinding String key() default "";

    @Nonbinding boolean container() default true;

    @Nonbinding String fileName() default "";

}

Note that the qualifier have three attributes, key, container and fileName. The attribute key is used to pass the key of property. The attribute container is used to define if the attribute will be read from Jakarta EE container. If the attribute container is true, the application will search the property on the application server.  The attribute fileName is used to pass the file path when we are using an external file. When the fileName is passed and the attribute container is true, then the application will search first on the Jakarta EE container and if the property is not found on the Jakarta EE container, then is find on external file.

Implementing Producer

The code below has implementation of PropertyProducer, the producer used to inject the properties.

public class PropertyProducer {

    @Property
    @Produces
    public String readProperty(InjectionPoint point){

        String key = point
                .getAnnotated()
                .getAnnotation(Property.class)
                .key();

        if( point
                .getAnnotated()
                .getAnnotation(Property.class)
                .container() ){

           String value = System.getProperty(key);

           if( Objects.nonNull(value) ){
               return value;
           }

        }

        return readFromPath(point
                .getAnnotated()
                .getAnnotation(Property.class)
                .fileName(), key);

    }

    private String readFromPath(String fileName, String key){

        try(InputStream in = new FileInputStream( readPathConfigurationStore() + fileName)){

            Properties properties = new Properties();
            properties.load( in );

            return properties.getProperty( key );

        } catch ( Exception e ) {
            e.printStackTrace();
            throw new PropertyException("Error to read property.");
        }

    }

    private String readPathConfigurationStore(){

        Properties configStore = new Properties();

        try( InputStream stream = PropertyProducer.class
                .getResourceAsStream("/configurationStore.properties") ) {

            configStore.load(stream);
        }
        catch ( Exception e ) {
            e.printStackTrace();
            throw new PropertyException("Error to read property.");
        }

        return configStore.getProperty("path");
    }

}

Implementing the Config

This class is the core of this post, because it class contain the configurations of application that was read from both application server and external file. This class is a singleton and all configuration properties injected are centralized in this class.

@Singleton
public class Config {

    @Inject
    @Property(key="message.welcome")
    public String WELCOME;

    @Inject
    @Property(key="message.welcome", container = false, fileName = "config.properties")
    public String WELCOME_EXTERNAL_FILE;

    @Inject
    @Property(key="path.download")
    public String PATH_DOWNLOAD;

    @Inject
    @Property(key="path.upload")
    public String PATH_UPLOAD;

}

Implementing the WelcomeResource

The code below has the implementation of a JAX-RS resource that has two methods, one method to return a welcome message defined into system properties of application server, and other method to return a welcome message defined into external file.

@Path("/welcome")
public class WelcomeResource {

    @Inject
    private Config config;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response message(){

        Map map = new HashMap();
        map.put("message", config.WELCOME);

        return Response
                .status( Response.Status.OK )
                .entity( map )
                .build();

    }

    @GET
    @Path("/external")
    @Produces(MediaType.APPLICATION_JSON)
    public Response messageExternalFile(){

        Map map = new HashMap();
        map.put("message", config.WELCOME_EXTERNAL_FILE);

        return Response
                .status( Response.Status.OK )
                .entity( map )
                .build();

    }
}

Implementing FileDao

The code below have the FileDao implementation, the class to read and write file. FileDao is used by UploadResource and DownloadResource.

@Stateless
public class FileDao {

    @Inject
    private Config config;

    public boolean save( File file ){

        File fileToSave = new File(config.PATH_UPLOAD + "/" + file.getName());

        try (InputStream input = new FileInputStream( file )) {

            Files.copy( input, fileToSave.toPath() );

        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    public File find( String fileName ){

        File file = new File(config.PATH_DOWNLOAD + "/" + fileName);

        if( file.exists() && file.isFile() ) {

            return file;
        }

        return null;
    }

}

Implementing UploadResource

The code below has the implementation of a JAX-RS resource process an upload of file. Note that we have the FileDao class used to read and write the file.

@Path("/upload")
public class UploadResource {

    @Inject
    private FileDao fileDao;

    @POST
    public Response upload(@NotNull File file){

        if( fileDao.save( file ) ){
            return Response
                    .created(URI.create("/download?fileName="+ file.getName()))
                    .build();
        }

        return Response.serverError().build();

    }
}

Implementing DownloadResource

The code below has the implementation of a JAX-RS resource process a download of file. Note that we have the FileDao class used to read and write the file.

@Path("/download")
public class DownloadResource {

    @Inject
    private FileDao fileDao;

    @GET
    public Response download(@NotNull @QueryParam("fileName") String fileName){

        File file = fileDao.find( fileName );

        if( Objects.isNull( file ) ){

            return Response.status(Response.Status.NOT_FOUND).build();

        }

        return Response.ok(file)
                .header("Content-Disposition",
                "attachment; filename=\"" + fileName + "\"")
                .build();
    }

}

 

Eclipse MicroProfile Config

The Jakarta EE is a new project and is based on Java EE 8, but many peoples talk about a possible merge between MicroProfile and Jakarta EE. For me, these projects will be increasingly closer. The MicroProfile project has a solution called Eclipse MicroProfile Config,  today on 1.3 version, that permit us implementing the external configuration store pattern. If you want to know more about Eclipse MicroProfile Config, access: https://microprofile.io/project/eclipse/microprofile-config

Conclusion

Using external configuration store pattern, we decouple configurations from application. Thus, we can update some configurations without rebuild our application. Furthermore, other teams may managing the configuration without a developer intervention and we can share the same set of configurations with any applications.

The use of this pattern is a good practice, mainly if the application was done with a microservice architecture, because it promote a better delivery and an easier maintenance.

If you wanna see the full code of this example, access the github in this link:  https://github.com/rhuan080/jakartaee-example-external-conf

 

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s