Creating Micronaut Configurations
Iván Lopez - @ilopmar
About me...
➢ Iván Lopez Martín - @ilopmar
➢ Java/Groovy developer
Grails & Micronaut teams
at OCI
➢ @MadridGUG coordinator
https://madridgug.com
➢ Greach ex-organizer
https://greachconf.com
➢ Speaker: Devoxx, GeeCon, Codemotion, ConFoo,
JavaCro, RigaDevDays, SpringOne 2GX,...
What is a
configuration?
Micronaut Features (configurations)
annotation-api
aws-api-gateway
aws-api-gateway-graal
cassandra
config-consul
discovery-consul
discovery-eureka
file-watch
flyway
graal-native-image
groovy
grpc
hibernate-gorm
hibernate-jpa
http-client
http-server
java
jdbc-dbcp
jdbc-hikari
jdbc-tomcat
jib
jrebel
junit
kafka
kafka-streams
kotlin
liquibase
management
micrometer
micrometer-atlas
micrometer-graphite
micrometer-prometheus
micrometer-statsd
mongo-gorm
mongo-reactive
neo4j-bolt
neo4j-gorm
netflix-archaius
netflix-hystrix
netflix-ribbon
picocli
postgres-reactive
rabbitmq
redis-lettuce
security-jwt
security-session
spek
spock
springloaded
swagger-groovy
swagger-java
swagger-kotlin
tracing-jaeger
tracing-zipkin
Creating app with CLI
$ mn create-app greach
--features=discovery-consul,
management,
graal-native-image,
tracing-zipkin
It is “just”
creating beans...
...although it’s
not
Creating a configuration
- Bean Factories
- Conditional Beans
- Bean Replacement
- Bean Configuration
- Configuration Properties
- Configuration Builder
- Each Property
- Each Bean
- More options
Bean Factories
public class FooLibrary {
// Do stuff
}
@Singleton
public class FooLibrary {
// Do stuff
}
@Factory
public class FooFactory {
@Bean
public FooLibrary createFoo() {
return new FooLibrary();
}
}
Conditional Beans
@Requires(beans = DataSource.class)
@Requires(property = "datasource.url")
@Singleton
public class JdbcBookService implements BookService {
DataSource dataSource;
public JdbcBookService(DataSource dataSource) {
this.dataSource = dataSource;
}
}
Conditional Beans (II)
@Requires(classes = DataSource.class)
@Requires(missing = JdbcRowSet.class)
@Requires(beans = DataSource.class)
@Requires(missingBeans = DataSource.class)
@Requires(env = Environment.TEST)
@Requires(notEnv = Environment.TEST)
@Requires(configuration = "some.config")
@Requires(missingConfigurations = "other.config")
@Requires(sdk = Requires.Sdk.JAVA, value = "1.8")
@Requires(entities = javax.persistence.Entity.class)
@Requires(property = "data-source.url")
@Requires(missingProperty = "data-source.url")
@Requires(property="foo", value="John", defaultValue="John")
@Requires(condition = FlywayCondition.class)
public class Foo {
}
Bean Replacement
@Singleton
@Requires(beans = DataSource.class)
@Requires(property = "datasource.url")
public class JdbcBookService implements BookService {
DataSource dataSource;
public JdbcBookService(DataSource dataSource) {
this.dataSource = dataSource;
}
}
Bean Replacement
@Singleton
@Requires(beans = DataSource.class)
@Requires(property = "datasource.url")
public class JdbcBookService implements BookService {
DataSource dataSource;
public JdbcBookService(DataSource dataSource) {
this.dataSource = dataSource;
}
}
@Singleton
@Replaces(JdbcBookService.class)
@Requires(property = "spec.name", value = "foo")
public class MockBookService implements BookService {
Map<String, Book> bookMap = new LinkedHashMap<>();
@Override
public Book findBook(String title) {
return bookMap.get(title);
}
}
Bean Configuration
// package-info.java
@Configuration
@Requires(property = "flyway.enabled", notEquals = "false")
package io.micronaut.configuration.dbmigration.flyway;
@Configuration
@Requires(classes = {Channel.class, Connection.class,
ConnectionFactory.class})
package io.micronaut.configuration.rabbitmq;
Configuration injection
@Singleton
public class ApiService {
@Value("${api.key}")
protected String apiKey;
@Value("${api.secret}")
protected String apiSecret;
@Value("${api.results:10}")
protected int results;
@Value("${api.url:`https://api.foo.com`}")
protected URL url;
}
Configuration injection (II)
@Controller("/api/${api.version:v1}")
public class ApiController {
// ...
}
@Client("${api.server-url:`http://localhost:8080`}")
public interface ApiClient {
// ...
}
Configuration injection (III)
# application.yml
datasources:
default:
name: 'mydb'
jpa:
default:
properties:
hibernate:
hbm2ddl:
auto: 'update'
show_sql: true
@Property(name = "jpa.default.properties")
Map<String, Object> jpaProperties;
Configuration Properties
@ConfigurationProperties('my.engine')
class EngineConfig {
@NotBlank
String manufacturer = "Ford"
@Min(1L)
int cylinders
CrankShaft crankShaft = new CrankShaft()
@ConfigurationProperties('crank-shaft')
static class CrankShaft {
Optional<Double> rodLength = Optional.empty()
}
}
# application.yml
my:
engine:
manufacturer: Toyota
cylinders: 4
crank-shaft:
rod-length: 4
Configuration Builder
@ConfigurationProperties('my.engine')
class EngineConfig {
@ConfigurationBuilder(prefixes = "with")
EngineImpl.Builder builder = EngineImpl.builder()
@ConfigurationBuilder(prefixes = "with", configurationPrefix = "crank-shaft")
CrankShaft.Builder crankShaft = CrankShaft.builder()
SparkPlug.Builder sparkPlug = SparkPlug.builder()
@ConfigurationBuilder(prefixes = "with", configurationPrefix = "spark-plug")
void setSparkPlug(SparkPlug.Builder sparkPlug) {
this.sparkPlug = sparkPlug
}
}
Configuration Builder
@ConfigurationProperties('my.engine')
class EngineConfig {
@ConfigurationBuilder(prefixes = "with")
EngineImpl.Builder builder = EngineImpl.builder()
@ConfigurationBuilder(prefixes = "with", configurationPrefix = "crank-shaft")
CrankShaft.Builder crankShaft = CrankShaft.builder()
SparkPlug.Builder sparkPlug = SparkPlug.builder()
@ConfigurationBuilder(prefixes = "with", configurationPrefix = "spark-plug")
void setSparkPlug(SparkPlug.Builder sparkPlug) {
this.sparkPlug = sparkPlug
}
}
class EngineImpl implements Engine {
//...
static Builder builder() {
return new Builder()
}
static final class Builder {
private String manufacturer = "Ford"
private int cylinders
Builder withManufacturer(String manufacturer) {
this.manufacturer = manufacturer
this
}
Builder withCylinders(int cylinders) {
this.cylinders = cylinders
this
}
EngineImpl build(CrankShaft.Builder crankShaft, SparkPlug.Builder sparkPlug) {
new EngineImpl(manufacturer, cylinders, crankShaft.build(), sparkPlug.build())
}
}
}
Configuration Builder
@ConfigurationProperties('my.engine')
class EngineConfig {
@ConfigurationBuilder(prefixes = "with")
EngineImpl.Builder builder = EngineImpl.builder()
@ConfigurationBuilder(prefixes = "with", configurationPrefix = "crank-shaft")
CrankShaft.Builder crankShaft = CrankShaft.builder()
SparkPlug.Builder sparkPlug = SparkPlug.builder()
@ConfigurationBuilder(prefixes = "with", configurationPrefix = "spark-plug")
void setSparkPlug(SparkPlug.Builder sparkPlug) {
this.sparkPlug = sparkPlug
}
}
Configuration Builder
# application.yml
my:
engine:
cylinders: 4
manufacturer: 'Subaru'
crank-shaft:
rod-length': 4
spark-plug:
name: '6619 LFR6AIX'
type: 'Iridium'
companyName: 'NGK'
@ConfigurationProperties('my.engine')
class EngineConfig {
@ConfigurationBuilder(prefixes = "with")
EngineImpl.Builder builder = EngineImpl.builder()
@ConfigurationBuilder(prefixes = "with", configurationPrefix = "crank-shaft")
CrankShaft.Builder crankShaft = CrankShaft.builder()
SparkPlug.Builder sparkPlug = SparkPlug.builder()
@ConfigurationBuilder(prefixes = "with", configurationPrefix = "spark-plug")
void setSparkPlug(SparkPlug.Builder sparkPlug) {
this.sparkPlug = sparkPlug
}
}
@EachProperty
@EachProperty("flyway.datasources")
public class FlywayConfigurationProperties {
private final DataSource dataSource;
private final String nameQualifier;
public FlywayConfigurationProperties(@Nullable @Parameter DataSource dataSource,
@Parameter String name) {
this.dataSource = dataSource;
this.nameQualifier = name;
}
...
}
# application.yml
flyway:
datasources:
default:
async: true
locations: classpath:databasemigrations
books:
locations: classpath:bookmigrations
enabled: false
@EachProperty
@EachProperty("flyway.datasources")
public class FlywayConfigurationProperties {
private final DataSource dataSource;
private final String nameQualifier;
public FlywayConfigurationProperties(@Nullable @Parameter DataSource dataSource,
@Parameter String name) {
this.dataSource = dataSource;
this.nameQualifier = name;
}
...
}
# application.yml
flyway:
datasources:
default:
async: true
locations: classpath:databasemigrations
books:
locations: classpath:bookmigrations
enabled: false
@EachProperty (II)
public class FlywayEndpoint {
private final ApplicationContext applicationContext;
private final Collection<FlywayConfigurationProperties> flywayConfigurationProperties;
public FlywayEndpoint(ApplicationContext applicationContext,
Collection<FlywayConfigurationProperties> flywayConfigurationProperties) {
this.applicationContext = applicationContext;
this.flywayConfigurationProperties = flywayConfigurationProperties;
}
...
}
Optional<FlywayConfigurationProperties> optionConfig =
applicationContext
.findBean(FlywayConfigurationProperties.class,
Qualifiers.byName(“books”));
@EachBean
@Factory
public class FlywayFactory {
@Requires(condition = FlywayCondition.class)
@EachBean(FlywayConfigurationProperties.class)
public Flyway flyway(FlywayConfigurationProperties config) {
FluentConfiguration fluentConfiguration = config.fluentConfiguration;
fluentConfiguration.dataSource(config.getDataSource());
return fluentConfiguration.load();
}
}
Flyway flyway = applicationContext
.getBean(Flyway.class,
Qualifiers.byName(“books”));
More options
- Filters
- Endpoints
- Aspect Oriented Programming (AOP)
- Annotation Mapper
- Controllers
- Fire events
- Execute something on startup
Show me the
code!
Summary
Configurations (features)
out-of-the-box
Easy to create Extend the
Framework
Lots of different options The sky is the limit Increase productivity
SLIDE TITLE 27 PT ALL CAPS
Thank you!
@ilopmar
lopez.ivan@gmail.com
https://github.com/ilopmar
Iván López
http://bit.ly/greach-micronaut
Questions?

Greach 2019 - Creating Micronaut Configurations