This is the third post in a series describing a business component architecture using Spring 3.0/3.1 features like Java based configuration, the environment abstraction, bean definition profiles and property sources.
After the general structure and resources I’m gonna talk about properties this time. The subject seems to be an easy one, developers with Spring background will probably point to the PropertyPlaceholderConfigurer and mark it off, but anyway – in the environment described (>100 developers, many departments, applications may use any business component) we have some aspects complicating things.
What are properties?
But let’s step back and take a look at what properties are. Properties are used to source out configuration data from the application, being set later by somebody / something standing outside the application. We have two groups of properties:
- Properties determining application behaviour, like different modes (category A).
- Properties configuring resources, database URLs, queue names or the like (category B).
Properties are static and don’t change during runtime. For values changing dynamically we have other concepts (databases, JMX).
Reading properties is infrastructure code and shouldn’t be mixed with business logic. In the context of the business component architecture it means that properties are read in the configuration project and injected into the business component via dependency injection. Let’s take a look at an example.
Let’s say the PartnerService had a flag read-only, a property determining application behaviour.
1public class PartnerServiceImpl implements PartnerService {
2
3 private boolean readOnly;
4
5 private JdbcTemplate jdbcTemplate;
6
7 public PartnerServiceImpl(JdbcTemplate jdbcTemplate, boolean readOnly) {
8 this.jdbcTemplate = jdbcTemplate;
9 this.readOnly = readOnly;
10 }
11
12 public void savePartner(Partner partner) {
13 if (readOnly) {
14 throw new IllegalStateException(
15 "Persisting partner not allowed in read-only mode!");
16 }
17 // save Partner
18 }
19
20 public Partner getPartner(long id) {
21 return this.jdbcTemplate.queryForObject("SELECT ....",
22 new PartnerRowMapper, id);
23 }
24
25}
Properties and Spring 3.1’s environment abstraction
The flag isn’t read directly in the PartnerServiceImpl, the PartnerConfig is taking over this part. With Spring 3.1’s environment abstraction (check here and here ) it looks like this:
1@Import(HighLevelDataAccessConfig.class)
2@PropertySource("classpath:partner.properties")
3@Configuration
4public class PartnerConfig {
5
6 @Autowired
7 private Environment environment;
8
9 @Autowired
10 private HighLevelDataAccessConfig dataAccessConfig;
11
12 @Bean
13 public PartnerService partnerService() throws Exception {
14 return new PartnerServiceImpl(dataAccessConfig.jdbcTemplate(),
15 environment.getProperty("partner.readonly", boolean.class));
16 }
17
18}
Here the new elements in detail:
1@PropertySource("classpath:partner.properties")
If you use the annotation PropertySource, all properties in partner.properties are added to the Environment.
1@Autowired 2 private Environment environment;
The Environment exists in every ApplicationContext (from Spring 3.1) and can be injected into @Configuration objects (of course in any other Spring bean as well, but you shouldn’t do that, because it means mixing business code with configuration code!).
1environment.getProperty("partner.readonly", boolean.class)
Properties can be accessed via the Environment object. Per default the Environment contains all JVM system properties and environment variables. In our case at least the properties in partner.properties are added as well.
Properties and enterprise
So far, so good. Let’s take a look at an application using business components: for example a web application offering services from CashingService as restful services. The obvious approach regarding properties would be to build a deployment unit (a war or ear) NOT containing the properties files, and deploy those files later to a lib directory of the application server, so that they are added to the classpath on runtime. In our case that’s out of question and in addition it’s nonpractical:
- Changes in properties have to result in a new revision: If somebody changes a property, we have to know who did it when. If the properties are just located somewhere in the file system, it’s not controllable.
- Build and deployment process is complicated: Where do the properties come from? Who looks after them? Who cares for deploying the right properties into the right directory? Who cleans up the directories?
- Developers have to know all properties: The developer of the CashingService – rest – application has to know all integrated business and infrastructure components for adding the correct properties files. We lose the advantage of just importing the CashingConfig without knowing which other components get imported automatically. Properties of category B normally just depend on stage and runtime environment, and not on the specific application. Properties can be defined application independent, so that application developers don’t need to care about setting them. Properties of category B just depend on the application, but are much rarer. Often it’s possible to define reasonable defaults for them.
In most companies we have individual replacement procedures maintained by the operations team that, on deployment, replace properties that are located at a certain location inside the deployment unit. The needed values are managed securely by operations. Of course the build and deployment process is complicated as well, and the third point of the list above holds: the developer needs to know all properties expected by one of the components used.
Properties and the business component architecture
The solution I propose now doesn’t need a special build or deployment process. A change to a property leads to a new revision for that property. Developers of an application doesn’t need to care about properties of category B (resources) of imported business components.
Every business component adds its properties files into its jar. These files contain default values for the properties. If it’s not possible to define a reasonable default for a property, it’s left out.
Every business component describes in its API which properties exist and what the default is.
Properties depend on three dimensions:
- stage (dev, integration, production)
- runtime environment (WebSphere, Tomcat, standalone (for example JUnit integration tests))
- application (Cashing-Rest, Partner-Batch etc.)
We set up a database for properties with the following columns (for making revisions there may be more):
- stage
- runtime
- application
- key
- value
Data in this database may only be manipulated with special rights. Applications may only read this database.
When developing a business or an infrastructure component we set default values for properties of category B (resources) for each stage and runtime. The column application is filled with default in this case.
For accessing the properties in the database we develop our own PropertySource (have a look here for more on PropertySources):
1public class DatabaseReaderDelegate {
2
3 private JdbcTemplate jdbcTemplate;
4 private String stage;
5 private String runtime;
6 private String application;
7
8 private static final String SQL = "SELECT p.value FROM PROPERTYTABLE p WHERE stage = ? AND runtime = ? AND application = ? AND key = ?";
9
10 public DatabaseReaderDelegate(DataSource dataSource, String stage,
11 String runtime, String application) {
12 jdbcTemplate = new JdbcTemplate(dataSource);
13 this.stage = stage;
14 this.runtime = runtime;
15 this.application = application;
16 }
17
18 public String getProperty(String property) {
19 String value = null;
20 try {
21 value = jdbcTemplate.queryForObject(SQL, String.class, stage,
22 runtime, application, property);
23 } catch (EmptyResultDataAccessException e) {
24 try {
25 value = jdbcTemplate.queryForObject(SQL, String.class, stage,
26 runtime, "default", property);
27 } catch (EmptyResultDataAccessException e2) {
28 // nothing to do
29 }
30 }
31 return value;
32 }
33
34}
35
36public class DatabasePropertySource extends
37 PropertySource<DatabaseReaderDelegate> {
38
39 public DatabasePropertySource(DataSource dataSource, String stage,
40 String runtime, String application) {
41 super("database_propertysource", new DatabaseReaderDelegate(dataSource,
42 stage, runtime, application));
43 }
44
45 @Override
46 public Object getProperty(String key) {
47 return this.source.getProperty(key);
48 }
49
50}
This PropertySource needs a DataSource and knowledge about stage, runtime and application. When a property is asked for, it first looks for an entry for application, stage and runtime. If there is one, it is given back. If there is none, it checks if there is an default entry for stage and runtime. If there is none as well, it returns null, which indicates that this PropertySource doesn’t have a value for this property.
The DatabasePropertySource is set on the ApplicationContext with an ApplicationContextInitializer.
1public class CustomApplicationContextInitializer implements
2 ApplicationContextInitializer<ConfigurableApplicationContext> {
3 public void initialize(ConfigurableApplicationContext ctx) {
4 String stage = System.getProperty("de.codecentric.stage");
5 String runtime = System.getProperty("de.codecentric.runtime");
6 String application = System.getProperty("de.codecentric.application");
7 String dbURL = System.getProperty("de.codecentric.db.url");
8 String dbUser = System.getProperty("de.codecentric.db.user");
9 String dbPassword = System.getProperty("de.codecentric.db.password");
10 ctx.getEnvironment().setActiveProfiles(runtime);
11 BasicDataSource dataSource = new BasicDataSource();
12 dataSource.setUrl(dbURL);
13 dataSource.setUsername(dbUser);
14 dataSource.setPassword(dbPassword);
15 DatabasePropertySource databasePropertySource = new DatabasePropertySource(
16 dataSource, stage, runtime, application);
17 ctx.getEnvironment().getPropertySources()
18 .addFirst(databasePropertySource);
19 }
20}
Beside reading the JVM properties and initializing the DataSource two important things are happening here:
1ctx.getEnvironment().setActiveProfiles(runtime);
Here we set the value read in for runtime as the active profile. Doing that the correct resource definitions are used (take a look at the second blog post for more on that).
1ctx.getEnvironment().getPropertySources() 2 .addFirst(databasePropertySource);
Here we set our own DatabasePropertySource as first PropertySource to be checked by Spring. Only if the DatabasePropertySource doesn’t have a value for a key other PropertySources are being asked. The default properties files added to the jar of the component are belonging to those PropertySources.
In a web application an ApplicationContextInitializer can be used with a ServletContext parameter:
1<context-param> 2 <param-name>contextInitializerClasses</param-name> 3 <param-value>de.codecentric.CustomApplicationContextInitializer</param-value> 4</context-param>
Of course there is a lot of optimization potential in these sources, a caching is missing, the value for runtime can be determined somehow cleverly without JVM property, the application is impractical as a JVM property, because you might wanna run more than one application in a JVM, the DataSource could be retrieved via JNDI with a fallback to JVM properties and so on. It’s most important, that the concept is clear.
Conclusion
Reading properties is infrastructure code and therefore separated from business logic by using Spring’s Environment to read properties in @Configuration classes and setting them via Dependency Injection on business components.
By using our own DatabasePropertySource we get a simple build and deployment process without complex replacements. It’s easy to include a process that makes revisions of properties whenever they are changed. A developer of an application normally does not have to set properties because there are reasonable defaults. Anyway he may overwrite whatever property he wants to change.
If we create the web.xml including the definition of the ApplicationContextInitializer with a Maven archetype, the concept works out-of-the-box.
Completion of the example
In the preceding blog post I presented the low level data access configurations omitting the properties. That’s how they look like with properties:
1@Profile("websphere")
2@Configuration
3public class JndiDataAccessConfig implements LowLevelDataAccessConfig {
4
5 @Autowired
6 private Environment env;
7
8 @Bean
9 public DataSource dataSource() throws Exception {
10 InitialContext initialContext = new InitialContext();
11 return (DataSource) initialContext.lookup(env
12 .getProperty("infrastructure.db.jndi"));
13 }
14
15 @Bean
16 public PlatformTransactionManager transactionManager() {
17 return new WebSphereUowTransactionManager();
18 }
19
20}
21
22@Profile("standalone")
23@Configuration
24public class StandaloneDataAccessConfig implements LowLevelDataAccessConfig {
25
26 @Autowired
27 private Environment env;
28
29 @Bean
30 public DataSource dataSource() {
31 BasicDataSource dataSource = new BasicDataSource();
32 dataSource.setUrl(env.getProperty("infrastructure.db.url"));
33 dataSource.setUsername(env.getProperty("infrastructure.db.user"));
34 dataSource.setPassword(env.getProperty("infrastructure.db.password"));
35 return dataSource;
36 }
37
38 @Bean
39 public PlatformTransactionManager transactionManager() {
40 return new DataSourceTransactionManager(dataSource());
41 }
42
43}
Since it’s impossible to have reasonable defaults for these properties that we could add to a properties file inside the jar, we don’t specify such a file. The properties have to be in the database or added by another PropertySource.
What do we need to do for configuring a web application offering services from CashingService?
The web application is created with a Maven archetype that creates a web.xml already containing the ApplicationContextInitializer for the DatabasePropertySource.
There are five properties relevant for the application:
- partner.readonly -> partner.properties contains the default false, sufficient in this case.
- infrastructure.db.jndi -> the database contains defaults for all stages and relevant runtimes, sufficient in this case.
- infrastructure.db.user -> the database contains defaults for all stages and relevant runtimes, sufficient in this case.
- infrastructure.db.url -> the database contains defaults for all stages and relevant runtimes, sufficient in this case.
- infrastructure.db.password -> the database contains defaults for all stages and relevant runtimes, sufficient in this case.
The developer can include the CashingService via CashingConfig without caring about the properties.
And if he wants, he can overwrite every property by adding a database entry.
More articles
fromTobias Flohre
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Tobias Flohre
Senior Software Developer
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.