Codementor Events

Implementing External Configuration Store Pattern with Jakarta EE

Published Nov 19, 2018

Learn how to decouple a configuration from your application in this article by Rhuan Rocha, senior middleware consultant with experience in development using Java and Java EE.

Software delivery consists of many steps that need to be followed. One of the primary steps in software delivery involves the configuration of your application. Configuration can be of several types:

• The configuration of the file system and directories
• The configuration of paths to other services or resource
• The configuration of the database
• Others configurations in the environment

An environment consists of development, testing, staging, and production. An application needs to be configured in each of these environments, which have the same properties but different values. For configuration, a developer usually uses a configuration file (.properties, .xml or another) inside the application.
However, this method couples the package of the application with the environment, and the developer needs to generate a separate package for each environment. This package will only know the details of the environment that it is running. This is not the best practice and increases the complexity in delivering the article to both, application with monolithic architecture and application with microservice architecture.

A solution to this method is an external configuration store pattern. External configuration store pattern is an operational pattern (some literatures define it as an architecture pattern or a cloud design pattern) that decouple the configuration details from the application. External configuration is unaware of the value of the configuration properties and only knows the properties to be read from the configuration store.
The below figure shows the difference between an application using external configuration store pattern and an application that does not use external configuration store pattern:

1.png

Advantages of external configuration store pattern

External configuration store pattern comes with a lot of benefits but this article details on only a few. One of the primary benefits includes updating the configuration values without rebuilding the application. Once the package is generated, it can run on any environment until a problem (unrelated to the configuration) arises or the environment is configured wrongly.

External configuration store pattern also allows more freedom. Any team (infrastructure or middleware) can manage the configuration without the help of a developer as the application package doesn’t need to be updated. Furthermore, it centralizes all configurations, and the various applications can read the configuration properties from the same location.

Implementing an external configuration store pattern using Jakarta EE

External configuration store pattern can be implemented in the following ways:
• Using the application server as a configuration server by system properties
• Using an external file or a set of external files
• Using a data source (relational database, NoSQL, or others)
• Using a custom configuration server

This section will delve into the implementation of external configuration store pattern using the application server as a configuration server by system properties and using an external file or a set of external files.

In this scenario, you’ll have three JAX-RS resources:
• To return a welcome message
• To upload files
• To download files

On a resource that returns a welcome message, you’ll have one method to get a message from system properties of the application server and another method to get a message from an external file. To implement this, you can use a CDI producer to read the properties from both, application server and external file. Furthermore, create the qualifier @Property to be used by the producer at the moment of injection.

Creating the configurationStore.properties

Use this file when the application uses an external file. This is the only file configured inside an application to permit the application to know where the configuration store is.

path=${path_to_configuration_store}

Implementing Qualifier

The below code shows the implementation of a qualifier used to configure the injection and permit the producer product of 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 has three attributes:

• Key – This attribute is used to pass the key of the property.
• Container – This attribute is used to define the attribute that will be read from Jakarta EE container. If the attribute container is true, the application will search the property on the application server.
• FileName – This attribute is used to pass the file path when you are using an external file. When the fileName is passed and the attribute container is true, the application will search first on the Jakarta EE container. If the property is not found there, then it can be found on an external file.

Implementing Producer

The below code shows the 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 forms the core of this article as it contains the configurations of the application that was read from both the 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

This code includes the JAX-RS resource implementation that has two methods:

• To return a welcome message defined into the system properties of the application server
• To return a welcome message defined into an 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

This code has the FileDao implementation, the class to read and write a 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

This code includes the implementation of a JAX-RS resource process of uploading a file. Note that the FileDao class is 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

This code includes the implementation of a JAX-RS resource process of downloading the file. Note that the FileDao class is 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 based on Java EE 8, but many people talk about a possible merger between MicroProfile and Jakarta EE. The MicroProfile project includes a solution called Eclipse MicroProfile Config on version 1.3, which permits the implementation of the external configuration store pattern. If you want to know more about Eclipse MicroProfile Config, go to https://microprofile.io/project/eclipse/microprofile-config

Conclusion

Using external configuration store pattern, you can decouple configurations from an application. Thus, you can update some configurations without rebuilding your application. Furthermore, other teams can manage the configuration without a developer intervention and you can share the same set of configurations with any applications.

This a good practice, primarily if the application was created using microservice architecture, as it promotes better delivery and easier maintenance. To see the full code of this example, visit https://github.com/rhuan080/jakartaee-example-external-conf

If you found this article interesting, you can explore Java EE 8 Design Patterns and Best Practices to efficiently address common problems faced when developing applications and will be comfortable working on scalable and maintainable projects of any size. Java EE 8 Design Patterns and Best Practices helps developers attain better code quality and progress to higher levels of architectural creativity by examining the purpose of each available pattern and demonstrating its implementation with various code examples.

Discover and read more posts from PACKT
get started
post commentsBe the first to share your opinion
edwickhd
8 months ago

Why would it be a good idea to go with lousy home improvement devices when we get quality tools and appliances at home depot at a lower cost? Share your feedback about items and store service through the home depot customer survey blog at Home Depot Survey Org and get a qualification to win a $5000 gift voucher.

Show more replies