This document explains the configuration design.
The design goals are:
- Load and validate the configuration early, before loading the graph or creating any components/modules. This ensures fast feedback and early termination of the server if the configuration is not valid.
- Ignore unknown parameters, just print a warning message in the log. This will make the process of upgrading and downgrading OTP easier and less prune to service failure. Try to make reasonable default values instead of making a parameter required.
- Encapsulate config parsing, so parsing and error handling is consistent - and stay consistent over time(maintenance).
- Configuration should be injected into OTP components/modules in using inversion-of-control. Each module should define the needed config as an interface(or simple Java class). This ensures type-safety and provides a consistent way to document needed configuration for each module.
- For Sandbox modules the configuration loading should be put in the
org.opentripplanner.standalone.config.sandbox
package. This keeps all the configuration loading in one place, avoiding fragmentation and makes it easier to get an overview. The parameters (interface or POJO) injected into the Sandbox module itself, should be declared in the Sandbox module.
For historic reasons the configuration loader uses jackson and parses the config into a JSON node
tree. Java objects are mapped explicit by wrapping each JsonNode
in a NodeAdapter
. The
NodeAdapter
decorates the JsonNode
to provide type-safe getters for rich basic types like
Enum
, List
(type-safe), Map
(type-safe), LocalDateTime
, URI
and so on. It also helps with
validation and providing a list of unused parameters.
Each OTP module in need for configuration should declare needs and document each parameter using JavaDoc. It can be done using an interface or a simple plain-old-java-object(POJO):
/** <Summary doc goes here> */
interface <ModuleNnnnParameters> {
/**
* <Parameter doc goes here>
*
* This is an optional parameter, if not set the default is to ....
*/
@Nullable
LocalDateTime firstDayOfService() = null;
}
or
/** <Summary doc goes here> */
public class <ModuleNnnnParameters> {
<fields>
/**
* <Parameter doc goes here>
*
* This is an optional parameter, if not set the default is to ....
*/
@Nullable
LocalDateTime firstDayOfService() { ... }
}
In the org.opentripplanner.standalone.config
package there will be a class called
ModuleNnnnConfig
that either extends the interface above or instantiates the POJO. The
config class should map from JSON using the NodeAdapter
into itself(extending the interface)
or into the POJO.
The 2 approaches are almost identical, but the interface is a bit more "pure" with respect to the responsibilities, while the POJO approach saves a few lines of code.
Before you start implementing or injecting config into your new code, take a look at
TransitRoutingConfig
or RouterConfig#mapRoutingRequest(...)
for good examples.
The root configuration classes BuildConfig
, OtpConfig
and RouterConfig
are NOT good examples,
they are not defined in the modules they are used, and they do parse their own config. This creates
cyclic dependencies between the configuration loading and the modules using it. There is an issue
to (Merge otp-config, router-config and build-config)[opentripplanner#3020].
When doing this issue, we should move the above classes to the appropriate packages, possibly
splitting them by their usage. Than parsing and instantiating them should be done in the
org.opentripplanner.standalone.config
package.