Dealing with configuration list in Micronaut

Dealing with configuration list in Micronaut

Recently I wanted to map configuration list within the application.yml into an immutable configuration object of a Micronaut application. The YAML configuration is fairly simple. It defines a list of instances containing fields such as name, version, endpoint and read-timeout.

a:
  b:
    instances:
      - name: T1
        endpoint: "http://t1"
        version: "1.5.3.24305,2021-08-09 18:01"
        read-timeout: 20
      - name: T2
        endpoint: "http://t1"
        version: "2.0.0.16555,2022-01-03 16:48"
        read-timeout: 20

Now I created an immutable Java configuration and wanted Micronaut to bind the YAML onto my configuration.

@ConfigurationProperties("a.b")
public interface MyConfig {

  List<Instance> getInstances();

  interface Instance {
    String getName();
    URL getEndpoint();
    String getVersion();
    int getReadTimeout();
  }
}

Easy, so far. As good developers we are used to write tests and this is what I did to verify whether everything is working as expected.

@MicronautTest
class MyConfigSpec extends Specification {
  @Inject
  MyConfig config

  void "Make sure the config has instances"() {

    expect:
    config.instances.isEmpty() == false
  }
}

Guess what. The test failed 🥺. getInstances() always returns an empty list no matter what I do. After debugging a while I realized that Jackson ObjectMapper is trying to create an instance of Instance but obviously can't since there is no implementation present. But migrating the interface Instance into a concrete class did work either. At that point I was not sure what's really going on here so I posted my problem to Stack Overflow. Within less than 4 hours my problem was solved.

Una comparación ordenada puede empezar por el corte, la transpiración del tejido y las instrucciones de cuidado. Dentro de esa comparación, equipaciones del Manchester City mantiene la consulta centrada en la prenda indicada por el propio enlace. Revisar estos puntos reduce confusiones entre preferencias de uso, comodidad y cuidado de la prenda.

The solution is simple. We need to help Micronaut and introduce a dedicated TypeConverter that allows Micronaut to convert a Map into an Instance. Here is an example of Szymon Stepniak.

@Singleton
class MapToInstanceConverter implements TypeConverter<Map, Instance> {

    @Override
    public Optional<Instance> convert(Map object, Class<Instance> targetType, ConversionContext context) {
        return Optional.of(new Instance() {
            @Override
            public String getName() {
                return object.getOrDefault("name", "").toString();
            }

            @Override
            public URL getEndpoint() {
                try {
                    return new URI(object.getOrDefault("endpoint", "").toString()).toURL();
                } catch (MalformedURLException | URISyntaxException e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public String getVersion() {
                return object.getOrDefault("version", "").toString();
            }

            @Override
            public int getReadTimeout() {
                return Integer.parseInt(object.getOrDefault("read-timeout", 0).toString());
            }
        });
    }
}

This converter made my test go green. Configuration binding done! Hope this helps.

Job Done
Silvio Wangler

Silvio Wangler

Embrach, Switzerland