Easily manage microservice configurations with microconfig.io

One of the main problems in the development and subsequent operation of microservices is the competent and accurate tuning of their instances. In my opinion, the new microconfig.io framework can help. It allows you to quite elegantly solve some of the routine tasks of setting up applications.



If you have a lot of microservices, and each of them comes with its own file / settings files, then it is likely to make an error in one of them, which without proper dexterity and a logging system can be very difficult to catch. The main task that the framework sets itself is to minimize duplicate instance settings, thereby reducing the likelihood of adding an error.



Consider an example. Suppose there is a simple application with a yaml configuration file . It can be any microservice in any language. Let's see how the framework can be applied to this service.



But first, for greater convenience, we will create an empty project in the Idea IDE by first installing the microconfig.io plugin in it:



image



We configure the plugin launch configuration, you can use the default configuration, as in the screenshot above.



Our service is called order, then in a new project we will create a similar structure:







In the folder with the service name we put the configuration file - application.yaml . All microservices are launched in some kind of environment, so, in addition to creating the config of the service itself, it is necessary to describe the environment itself: for this, create the envs folder and add a file with the name of our working environment to it. Thus, the framework will create configuration files for services in the dev environment, since this parameter is set in the settings in the plugin.



The structure of the dev.yaml file will be quite simple:



mainorder: components: - order
      
      





The framework works with configurations that are grouped together. For our service, select a name for the mainorder group. The framework finds each such application group in the environment file and creates configurations for all of them that it finds in the corresponding folders.



In the order service settings file itself, we will indicate so far only one parameter:



 spring.application.name: order
      
      





Now run the plugin and it will generate the desired configuration of our service for us according to the path specified in the properties:







You can do without installing the plugin by simply downloading the framework distribution kit and launching it from the command line.

This solution is suitable for use on the build server.



It is worth noting that the framework perfectly understands the property syntax, that is, ordinary property files that can be used together in yaml configurations.



Add another payment service and complicate the existing one at the same time.

In order :



 eureka: instance.preferIpAddress: true client: serviceUrl: defaultZone: http://192.89.89.111:6782/eureka/ server.port: 9999 spring.application.name: order db.url: 192.168.0.100
      
      





In payment :



 eureka: instance.preferIpAddress: true client: serviceUrl: defaultZone: http://192.89.89.111:6782/eureka/ server.port: 9998 spring.application.name: payments db.url: 192.168.0.100
      
      





The main problem with these configurations is the presence of a large amount of copy paste in the service settings. Let's see how the framework will help get rid of it. Let's start with the most obvious - the presence of eureka configuration in the description of each microservice. Create a new directory with the settings file and add a new configuration to it:







And in each of our projects we will now add the line #include eureka .



The framework will automatically find the eureka configuration and copy it to the service configuration files, while a separate eureka configuration will not be created, since we will not specify it in the dev.yaml environment file . Service order :



 #include eureka server.port: 9999 spring.application.name: order db.url: 192.168.0.100
      
      





We can also make the database settings in a separate configuration by changing the import line to #include eureka, oracle .



It is worth noting that each change during the regeneration of configuration files the framework monitors and places in a special file next to the main configuration file. The entry in his log looks like this: โ€œStored 1 property changes to order / diff-application.yaml โ€. This allows you to quickly detect changes in large configuration files.



Removing the common parts of the configuration allows you to get rid of many unnecessary copy-paste, but does not allow you to flexibly create a configuration for various environments - the endpoints of our services are unique and hardcoded, this is bad. Let's try to remove it.



A good solution would be to keep all the endpoints in one configuration that others could reference. To do this, support for placeholders has been introduced into the framework. Here's how the eureka configuration file changes:



  client: serviceUrl: defaultZone: http://${endpoints@eurekaip}:6782/eureka/
      
      





Now let's see how this placeholder works. The system finds a component called endpoints and looks for eurekaip in it, and then substitutes it in our configuration. But what about different environments? To do this, create the settings file in endpoints of the following type application.dev.yaml . The framework independently, by file extension, decides which environment this configuration belongs to and loads it:







The contents of the dev file:



 eurekaip: 192.89.89.111 dbip: 192.168.0.100
      
      





We can create the same configuration for the ports of our services:



 server.port: ${ports@order}.
      
      





All important settings are in one place, thereby reducing the likelihood of errors due to the scattered parameters in the configuration files.



The framework provides many ready-made placeholders, for example, you can get the name of the directory in which the configuration file is located and assign it:



 #include eureka, oracle server.port: ${ports@order} spring.application.name: ${this@name}
      
      





Due to this, there is no need to additionally specify the name of the application in the configuration and it can also be moved to a common module, for example, to the same eureka:



 client: serviceUrl: defaultZone: http://${endpoints@eurekaip}:6782/eureka/ spring.application.name: ${this@name}
      
      





The order configuration file will be reduced to one line:



 #include eureka, oracle server.port: ${ports@order}
      
      





In the event that we do not need any setting from the parent configuration, we can specify it in our configuration and it will be used during generation. That is, if for some reason we need a unique name for the order service, just leave the spring.application.name parameter.



Let's say you need to add custom logging settings to the service, which are stored in a separate file, for example, logback.xml . Create a separate group of settings for it:







In the basic configuration, we will indicate the framework where to place the logging settings file we need using the @ConfigDir placeholder :



 microconfig.template.logback.fromFile: ${logback@configDir}/logback.xml
      
      





In the logback.xml file, we configure standard appenders, which in turn can also contain placeholders, which the framework will change during the generation of configs, for example:



 <file>logs/${this@name}.log</file>
      
      





Adding logback import in the service configuration, we automatically get the configured logging for each service:



 #include eureka, oracle, logback server.port: ${ports@order}
      
      





It is time to familiarize yourself with all available framework placeholders in more detail:



$ {this @ env} - returns the name of the current environment.

$ {... @ name} - returns the name of the component.

$ {... @ configDir} - returns the full path to the component config directory.

$ {... @ resultDir} - returns the full path to the destination directory of the component (received files will be placed in this directory).

$ {this @ configRoot} - returns the full path to the root directory of the configuration store.



The system also allows you to get environment variables, for example, the path to java:

$ {env @ JAVA_HOME}

Or, since the framework is written in JAVA , we can get system variables similar to calling System :: getProperty using a construct like this:

${system@os.name}

It is worth mentioning support for the Spring EL extension language. In the configuration, similar expressions are applicable:



 connection.timeoutInMs: #{5 * 60 * 1000} datasource.maximum-pool-size: #{${this@datasource.minimum-pool-size} + 10}
      
      





and you can use local variables in configuration files using the #var expression:



 #var feedRoot: ${system@user.home}/feed folder: root: ${this@feedRoot} success: ${this@feedRoot}/archive error: ${this@feedRoot}/error
      
      





Thus, the framework is a fairly powerful tool for fine and flexible configuration of microservices. The framework perfectly performs its main task - eliminating copy-paste in settings, consolidating settings and, as a result, minimizing possible errors, while making it easy to combine configurations and change for different environments.



If you are interested in this framework, I recommend that you visit its official page and read the full documentation , or delve into the source here .



All Articles