Reputation: 4681
I am trying to setup a Spring Boot monolithic app which should behave like a microservice and I have some issues to manage configurations (.properties).
This is how I organized the project (resources
folder) :
As you can see there are common properties files : application.properties
,application-dev.properties
and application-prod.properties
these properties should be shared by all sub-properties and they can eventually be overridden.
Each service
has its own data source url and it also depending on the active profile : H2
for dev
and MySQL
for prod
.
Here an exemple of how I manage configurations :
Contents of common application.properties
:
spring.profiles.active=dev
spring.config.additional-location=classpath:productservice/, classpath:userservice/,classpath:financeservice/, classpath:securityservice/ #I am not sure this works...
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.database=default
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.validation-mode=none
Contents of common application-dev.properties
:
spring.datasource.driver-class-name=org.h2.Driver
spring.h2.console.enabled=true
spring.h2.console.path=/db
spring.h2.console.settings.trace=false
spring.h2.console.settings.web-allow-others=false
spring.datasource.username=sa # We override data source username and paswword
spring.datasource.password=
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
Contents of common application-prod.properties
:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
Now for specific services :
productservice :
Content of application.properties
:
spring.liquibase.change-log=classpath:productservice/db/changelog/db.changelog-master.xml
spring.liquibase.default-schema=productservicedb
spring.liquibase.check-change-log-location=true
Content of application-dev.properties
:
spring.datasource.url=jdbc:h2:mem:productservicedb;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS productservicedb;MV_STORE=FALSE;MVCC=FALSE
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
Content of application-prod.properties
:
spring.datasource.url=jdbc:mysql://localhost:3306/productservicedb?useSSL=false
And for userservice I have :
Content of application.properties
:
spring.liquibase.change-log=classpath:userservice/db/changelog/db.changelog-master.xml
spring.liquibase.default-schema=userservice
spring.liquibase.check-change-log-location=true
Content of application-dev.properties
:
spring.datasource.url=jdbc:h2:mem:userservicedb;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS userservicedb;MV_STORE=FALSE;MVCC=FALSE
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
Content of application-prod.properties
:
spring.datasource.url=jdbc:mysql://localhost:3306/userservicedb
And so on ... for other services.
To configure each data source I proceed like this :
productservice :
1- Retrieve properties from environment :
@Configuration( "productServiceProperties" )
@Getter
@Setter
@PropertySource( value = { "classpath:productservice/application.properties",
"classpath:productservice/application-${spring.profiles.active}.properties" } )
public class ProductServiceProperties {
@Autowired//I want `Environment` to contain properties from common properties + files in @PropertySource above
private Environment environment;
// ========DATASOURCE PROPERTIES=========
private String datasourceDriverClass;
private String datasourceUsername;
private String dataSourcePassword;
private String dataSourceUrl;
// ========LIQUIBASE PROPERTIES==========
private String liquibaseChangeLog;
private String liquibaseDefaultSchema;
@PostConstruct
public void init() {
this.datasourceDriverClass = environment.getProperty( "spring.datasource.driver-class-name" );
datasourceUsername = environment.getProperty( "spring.datasource.username" );
dataSourcePassword = environment.getProperty( "spring.datasource.password" );
dataSourceUrl = environment.getProperty( "spring.datasource.url" );
liquibaseChangeLog = environment.getProperty( "spring.liquibase.change-log" );
liquibaseDefaultSchema = environment.getProperty( "spring.liquibase.default-schema" );
log.debug( "Initialisation {} ", datasourceDriverClass );
}
}
2- Inject them in the DB configuration :
@Configuration
@EnableJpaRepositories( basePackages = "com.company.product.repository", entityManagerFactoryRef = "productServiceEntityManager", transactionManagerRef = "productServiceTransactionManager", considerNestedRepositories = true )
@EnableTransactionManagement( proxyTargetClass = false )
public class ProductServiceDBConfig {
private static final String packageToScan = "com.company.product.models";
@Autowired
private ProductServiceProperties productServiceProperties;
@Bean( name = "productServiceDataSource" )
public DataSource productServiceDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName( productServiceProperties.getDatasourceDriverClass() );
dataSource.setUrl( productServiceProperties.getDataSourceUrl() );
dataSource.setUsername( productServiceProperties.getDatasourceUsername() );
dataSource.setPassword( productServiceProperties.getDataSourcePassword() );
return dataSource;
}
@Bean
public JpaVendorAdapter productServiceVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setGenerateDdl( false );
hibernateJpaVendorAdapter.setShowSql( true );
return hibernateJpaVendorAdapter;
}
@Bean( name = "productServiceEntityManager" )
public LocalContainerEntityManagerFactoryBean productServiceEntityManager() {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource( productServiceDataSource() );
emf.setPackagesToScan( packageToScan );
emf.setJpaVendorAdapter( productServiceVendorAdapter() );
return emf;
}
@Bean( name = "productServiceTransactionManager" )
public PlatformTransactionManager productServiceTransactionManager() {
JpaTransactionManager productTransactionManager = new JpaTransactionManager();
productTransactionManager.setEntityManagerFactory( productServiceEntityManager().getObject() );
return productTransactionManager;
}
@Bean
public SpringLiquibase productServiceLiquibase() {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource( productServiceDataSource() );
liquibase.setChangeLog( productServiceProperties.getLiquibaseChangeLog() );
liquibase.setDefaultSchema( productServiceProperties.getLiquibaseDefaultSchema() );
return liquibase;
}
}
userservice :
1-
@Configuration( "userServiceProperties" )
@Getter
@Setter
@PropertySource( value = { "classpath:userservice/application.properties",
"classpath:userservice/application-${spring.profiles.active}.properties" } ) //I want properties from properties file source above
public class UserServiceProperties {
@Autowired //I want `Environment` to contain properties from common properties + files in @PropertySource above
private Environment environment;
// ========DATASOURCE PROPERTIES=========
private String datasourceDriverClass;
private String datasourceUsername;
private String dataSourcePassword;
private String dataSourceUrl;
// ========LIQUIBASE PROPERTIES==========
private String liquibaseChangeLog;
private String liquibaseDefaultSchema;
@PostConstruct
public void init() {
datasourceDriverClass = environment.getProperty( "spring.datasource.driver-class-name" );
datasourceUsername = environment.getProperty( "spring.datasource.username" );
dataSourcePassword = environment.getProperty( "spring.datasource.password" );
dataSourceUrl = environment.getProperty( "spring.datasource.url" );
liquibaseChangeLog = environment.getProperty( "spring.liquibase.change-log" );
liquibaseDefaultSchema = environment.getProperty( "spring.liquibase.default-schema" );
}
}
2-
@Configuration
@EnableJpaRepositories( basePackages = "com.company.user.repository", entityManagerFactoryRef = "userServiceEntityManager", transactionManagerRef = "userServiceTransactionManager", considerNestedRepositories = true )
@EnableTransactionManagement( proxyTargetClass = false )
public class UserServiceDBConfig {
private static final String packageToScan = "com.company.user.models";
@Autowired
private UserServiceProperties userServiceProperties;
@Bean( name = "userServiceDataSource" )
public DataSource userServiceDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName( userServiceProperties.getDatasourceDriverClass() );
dataSource.setUrl( userServiceProperties.getDataSourceUrl() );
dataSource.setUsername( userServiceProperties.getDatasourceUsername() );
dataSource.setPassword( userServiceProperties.getDataSourcePassword() );
return dataSource;
}
@Bean
public JpaVendorAdapter userServiceVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setGenerateDdl( false );
hibernateJpaVendorAdapter.setShowSql( true );
return hibernateJpaVendorAdapter;
}
@Bean( name = "userServiceEntityManager" )
public LocalContainerEntityManagerFactoryBean userServiceEntityManager() {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource( userServiceDataSource() );
emf.setPackagesToScan( packageToScan );
emf.setJpaVendorAdapter( userServiceVendorAdapter() );
return emf;
}
@Bean( name = "userServiceTransactionManager" )
public PlatformTransactionManager userServiceTransactionManager() {
JpaTransactionManager userTransactionManager = new JpaTransactionManager();
userTransactionManager.setEntityManagerFactory( userServiceEntityManager().getObject() );
return userTransactionManager;
}
@Bean
public SpringLiquibase userServiceLiquibase() {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource( userServiceDataSource() );
liquibase.setChangeLog( userServiceProperties.getLiquibaseChangeLog() );
liquibase.setDefaultSchema( userServiceProperties.getLiquibaseDefaultSchema() );
return liquibase;
}
}
The problem is, as I @Autowired Environment
to get my properties and all properties have same names, some properties always take precedence over other properties. E.g. the property spring.datasource.url
contains the same value in both services.
I use Environment
and not @Value
neither @ConfigurationProperties
because it gets properties basing on the active profile and that exactly what I want.
My questions are :
Is it possible to configure Environment
to get properties from the configuration class where it is injected?
If not, is there a simple way to achieve what I want to do?
Should I configure multiple application contexts (one per service) to isolate each Environment
variable from other? (I am not sure this would work)
Thanks a lot (and sorry for the length of this post)
Upvotes: 3
Views: 5040
Reputation: 4681
I solve the problem myself with the suggestion of Andy Wilkinson (@ankinson) on Twitter.
We can't have many properties files with same name. So I had to rename my sub configurations files like this :
product.properties
product-dev.properties
product-prod.properties
And so on...
Upvotes: 1