{#==========================================
Docs : "configuration"
==========================================#}
Spincast doesn't force you to configure your application in a specific way, but does
suggest a strategy. The only requirement is that
in order to modify the configurations used by the internals of Spincast itself
(for example the
If you don't bind a custom implementation for that
The strategy we suggest to configure your application allows you to both modify the default configurations and add
specific configurations to your application in a single location. This strategy involves creating a
standard Java class with getter methods for each configuration that is needed.
Compared to a simple
Configuration
port the server is going to be started with), you need to bind
a custom implementation for the
SpincastConfig
interface. Spincast retrieves the values to use for its configurations through this interface.
SpincastConfig interface, a
default one
will be used and will provide default values.
Configuration strategy - introduction
.properties based configuration strategy, a class based one
requires more work (since you do have to define a getter method for each configuration),
but comes with three big advantages :
// Doesn't compile! The "getServerHost()" getter returns a String.
int port = configs.getServerHost();
.properties based configuration
that will fail at runtime :
// Compiles... But boom at runtime!
int port = (int)properties.get("server.host");
// Some configuration getter...
public int getHttpServerPort() {
// We use another configuration to create the value
// of this one! You can use any logic you need...
if("local".equals(getEnvironmentName())) {
return 12345;
} else {
return 80;
}
}
// A configuration getter that returns a File object
public File getSpincastWritableDir() {
// ...
}
The first step is to create a custom AppConfig interface that extends
SpincastConfig :
public interface AppConfig extends SpincastConfig {
/**
* An app specific configuration
*/
public String getSomeAppConfiguration();
/**
* Another app specific configuration
*/
public int getAnotherAppConfiguration();
}
public class AppConfigDefault extends SpincastConfigDefault implements AppConfig {
/**
* An app specific configuration
*/
public String getSomeAppConfiguration() { ... }
/**
* Another app specific configuration
*/
public int getAnotherAppConfiguration() { ... }
/**
* Overrides a default Spincast configuration too!
*/
@Override
public int getHttpServerPort() {
return 12345;
}
}
Finally, you add the associated bindings to your Guice module :
public class AppModule extends SpincastGuiceModuleBase {
@Override
protected void configure() {
bind(AppConfig.class).to(AppConfigDefault.class).in(Scopes.SINGLETON);
//...
}
}
SpincastConfig
interface has been bound, and will automatically adjust the binding for this interface. You can bind SpincastConfig
to AppConfigDefault by yourself if you want, but it is not required.
Have a look at the configuration
of this very website for an example of how this strategy looks like!
Be careful with the dependencies you inject in your implementation class : the configurations are
used by a lot of other components and it is therefore easy to create circular dependencies. One dependency
that you can inject without any problem, and that is often useful in a configuration
class, is the application arguments.
By using the strategy above, so by extending the SpincastConfigDefault base class, you also extend the ConfigFinder base class and get access to a lot of useful features to help you build your configuration. In particular, you gain access to an easy way to externalize the values of your configurations (ie : have different configurations depending on the environment the application runs on).
We'll see in the next section, Configuring the config plugin, that the way Spincast searches for external configurations is fully configurable.
To make configurations externalizable, the first thing to do in your implementation class is to remove any hardcoded values and, instead, use the provided getters. Those special getters are provided by the ConfigFinder class, from which SpincastConfigDefault extends. There are multiple getters, depending on the type of the configuration.
For example, in your implementation class, instead of this hardcoded value :
public class AppConfigDefault extends SpincastConfigDefault implements AppConfig {
@Inject
public AppConfigDefault(SpincastConfigPluginConfig spincastConfigPluginConfig) {
super(spincastConfigPluginConfig);
}
/**
* The HTTP server port to use
*/
@Override
public int getHttpServerPort() {
// Hardcoded value!
return 12345;
}
// ...
}
getInteger(...) method, so the "port" configuration is
externalized :
public class AppConfigDefault extends SpincastConfigDefault implements AppConfig {
@Inject
public AppConfigDefault(SpincastConfigPluginConfig spincastConfigPluginConfig) {
super(spincastConfigPluginConfig);
}
/**
* The HTTP server port to use
*/
@Override
public int getHttpServerPort() {
// Makes this configuration externalizable
// and provides a default value in case no
// external value is found.
return getInteger("server.port", 12345);
}
// ...
}
By using the special getters provided by the ConfigFinder
base class, your configuration is now externalized. A getter
is provided for all common types : String, Boolean, Integer,
Long, BigDecimal and Date.
Note that date configuration values must be using a valid ISO-8601 format.
Spincast will load externalized configurations from various sources, each source overriding the previous one, if a same configuration is found in both :
app-config.yaml file is looked for on the classpath. This is where you generally will place
the default values of your externalizable configurations.
.jar, Spincast will check if a app-config.yaml
file exists next to it. If your application is not running from an executable .jar (for example
it is launched in an IDE), Spincast will check if a app-config.yaml
file exists at the root of the project.
Environnement variables will be checked to see if some configurations are defined there.
An environment variable must start with "app." to be considered as a configuration
for a Spincast application. This prefix is configurable.
System properties will be checked to see if some configurations have been passed to the application
when launched.
An system property must start with "app." to be considered as a configuration
for a Spincast application. This prefix is configurable.
System properties have the highest priority and overrides any existing configurations (except
of course for hardcoded/non-externalized configurations).
Both environment variables and system properties can have multiple prefixes.
In association with the feature that can strip those prefixes when getting the configurations (see next section),
this allows you to define variables for more than one application on the same server. For example, you could
have those environment variables :
app1.admin.email.address = user1@example.com
app2.admin.email.address = user2@example.com
common.admin.email.format = html
Here's an example of a app-config.yaml file that could be used as a source of externalized configurations :
app: name: My Super app api: base: https://www.myApp.com databases: bd1: host: dbHost.com port: 12345 username: myDbUser password: # Empty! Must be provided at runtime...
Then in your AppConfigDefault class, you could access the port for the
"db1" database using :
@Override
public int getDb1Port() {
return getInteger("app.databases.bd1.port");
}
In this example, the password for the "db1" database will have to be defined as an
environment variable, or using any other mechanism that doesn't require the password to be defined as
plain text and be committed to your version control system (which would be a really bad idea)! Since the configuration
values are retrieved using standard Java methods, you can implement any mechanism you want in order to
retrieve such "secret" configurations.
The steps described in the sources of configuration values section are configurable. You configure the way the Spincast Config plugin works by binding a custom implementation of the SpincastConfigPluginConfig interface. If you don't bind a custom implementation for this interface, a default implementation, SpincastConfigPluginConfigDefault, will be used.
Those are the methods you can tweak :
String getClasspathFilePath()
Defaults to "app-config.yaml". This means you can simply create that file
in your project's /src/main/resources/ folder and it
will be used.
String getExternalFilePath()
The path can be relative or absolute. Spincast will check this using :
File configFile = new File(thePath);
if(configFile.isAbsolute()) {
// ...
}
If the path is relative, it is from the executable .jar or, if not run from a .jar, from the root of the project.
Defaults to "app-config.yaml".
List<String> getEnvironmentVariablesPrefixes()
Defaults to "app." only.
boolean isEnvironmentVariablesStripPrefix()
environmentVariablesPrefixes() indicates
that "app." is an environment variable prefix, then "app.admin.email"
will result in a "admin.email" key.
Note that each environment variable key must be unique once the prefixes are stripped, otherwise an exception will be thrown when the application starts!
Defaults to false.
List<String> getSystemPropertiesPrefixes()
Defaults to "app." only.
boolean isSystemPropertiesStripPrefix()
systemPropertiesPrefixes() indicates
that "app." is an system property prefix, then "app.admin.email"
will result in a "admin.email" key.
Note that each system properties key must be unique once the prefixes are stripped, otherwise an exception will be thrown when the application starts!
Defaults to false.
boolean isExternalFileConfigsOverrideEnvironmentVariables()
The default is false : environment
variables have priority.
boolean isThrowExceptionIfSpecifiedClasspathConfigFileIsNotFound()
null) but is not found.
If set to false, a message will be logged but no
exception will be thrown.
Defaults to false.
boolean isThrowExceptionIfSpecifiedExternalConfigFileIsNotFound()
null) but is not found.
If set to false, a message will be logged but no
exception will be thrown.
Defaults to false.
To know all the core configurations required by Spincast, have a look at the SpincastConfig javadoc. Here, we're simply going to introduce the most important ones, and their default value :
getPublicUrlBase() : This configuration is
very important and you should override it in your application and adjust
it from environment to environment! It tells Spincast what is the
base public URL used to reach your application.
For example, your application may be accessed using a URL such as
http://www.example.com but can in fact be behind a reverse-router
and actually started on the "localhost" host and on
port "12345".
The problem is that the public base URL ("http://www.example.com") can't be
automatically found, but Spincast still requires it to :
By default, the getPublicUrlBase() configuration
will be "http://localhost:44419". This default can be used for development
purposes, but very should be changed when releasing to another environment.
It is so important to override this configuration that Spincast has a validation in place : when an application starts, an exception will be thrown if those conditions are all meet :
environment name is not "local".
isDebugEnabled() configuration returns false.
public host is still "localhost".
Note that you can disable this startup validation using the
isValidateLocalhostHost() configuration.
getServerHost() : The host/IP the HTTP Server
will listen on. The default is 0.0.0.0, which
means the Server will listen on any IP.
getHttpServerPort() : The port the Server
will listen to for HTTP (unsecure) requests.
If <= 0, the Server won't listen on HTTP requests.
getHttpsServerPort() : The port the Server
will listen to for HTTPS (secure) requests.
If <= 0, the Server won't listen on HTTPS requests.
If you use HTTPS, you also have to provide some extra
configurations related to the SSL
certificate to use.
isDebugEnabled() : If true,
a development environment is taken for granted, and
internal error messages may be displayed publicly, no cache will be
used for the templates, etc. The default is true, so make
sure you change this to false before deploying to
production!