What is a good way to set up a project in Scala which uses different configuration depending on environments.
I need to specifically have different databases for development, test and production environment (similar to what is done in Rails)
Another strategy I'm using consists of using includes.
I usually store my DEV settings in the default application.conf file then I create a new conf file for other environments and include the default one.
Let's say my DEV conf application.conf looks like this:
myapp {
server-address = "localhost"
server-port = 9000
some-other-setting = "cool !"
}
Then for the PROD, I could have another file called prod.conf:
include "application"
# override default (DEV) settings
myapp {
server-address = ${PROD_SERVER_HOSTNAME}
server-port = ${PROD_SERVER_PORT}
}
Note that I override only the settings that change in the PROD environment (some-other-setting is thus the same as in DEV).
The config bootstrap code doesn't test anything
...
val conf = ConfigFactory.load()
...
To switch from the DEV to the PROD conf, simply pass a system property with the name of the config file to load:
java -Dconfig.resource=prod.conf ...
In DEV, no need to pass it since application.conf will be loaded by default.
So here we're using Typesafe Config's default loading mechanism to achieve this.
I've created a simple project to demonstrate this technique. Feel free to clone and experiment.
Use typesafe Config. Create a Config object like this:
import com.typesafe.config._
object Config {
val env = if (System.getenv("SCALA_ENV") == null) "development" else System.getenv("SCALA_ENV")
val conf = ConfigFactory.load()
def apply() = conf.getConfig(env)
}
Then create the application.conf file in src/main/resources folder:
development {
your_app {
databaseUrl = "jdbc:mysql://localhost:3306/dev_db"
databaseUser = "xxxx"
databasePassword = "xxxx"
}
}
test {
your_app {
databaseUrl = "jdbc:mysql://localhost:3306/test_db"
databaseUser = "xxxxx"
databasePassword = "xxxx"
}
}
Now from anywhere in your application, you can access configuration:
Config().getString("your_app.databaseUrl")
If you have your environment set up (e.g. export SCALA_ENV=test) when you run your application, it will consider the right configuration section. The default is development
I wasn't happy with how Daniel Cukiers solution did not allow defaults and overrides, so I changed it around to make full use of those.
The only configuration you have to do is set a ENVIRONMENT variable on the system (defaults to 'dev' if none is set)
(Java solution, compatible with Scala):
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
public class MyCompanyConfig {
public final static Config base = ConfigFactory.load().getConfig("mycompany");
public final static String environment = System.getenv("ENVIRONMENT") == null ? "dev" : System.getenv("ENVIRONMENT");
/**
* Returns a subtree of the base configuration with environment settings applied.
*
* #param setting The subtree to return config for.
* #return A config with base in given setting, with environment modifications applied.
*/
public static Config load(String setting) {
Config config = base.getConfig(setting);
if (config.hasPath(environment)) {
return config.getConfig(environment).withFallback(config);
}
return config;
}
}
This allows a single reference.conf in a library looking like this:
mycompany.module1 {
setting1 : "adefaultvalue"
url : "localhost"
test {
// will be used where ENVIRONMENT="test"
url : "test.mycompany.com"
}
prod {
// will be used where ENVIRONMENT="prod"
setting1 : "changethedefault"
url : "www.mycompany.com"
}
}
Usage:
Config conf = MyCompanyConfig.load("module1")
Related
In my Codeigniter 3, I have a simple settings.php file that looks something like this:
<?php
$config["lang1_HTML"] = "sr-Latn-RS";
$config["lang1_HTML_2"] = "sr-Latn";
$config["lang1_HTML_3"] = "";
$config["lang1_code"] = "SRP";
$config["lang1_flag"] = "/images/flags/rs.png";
$config["sr"] = "lang1";
$config["lang3"] = "en";
$config["lang3_HTML"] = "en-RS";
$config["lang3_HTML_2"] = "en";
$config["lang3_HTML_3"] = "";
$config["lang3_code"] = "ENG";
...
Now I want to upgrade this to CI4. Is there any chance to put this file in app\Config without changing it and still be able to access this array?
Or better is it possible to autoload Settings.php and use it like this?
Upgrading from 3.x to 4.x » Upgrade Configuration
Upgrade Guide
You have to change the values in the default CI4 config files according to the changes in the CI3 files. The config names are pretty
much the same as in CI3.
If you are using custom config files in your CI3 project you have to create those files as new PHP classes in your CI4 project in
app/Config. These classes should be in the Config namespace and should extend CodeIgniter\Config\BaseConfig.
Once you have created all custom config classes, you have to copy the variables from the CI3 config into the new CI4 config class as
public class properties.
Now, you have to change the config fetching syntax everywhere you fetch config values. The CI3 syntax is something like
$this->config->item('item_name');. You have to change this into
config('MyConfigFile')->item_name;.
Step A:
Create a new class app/Config/Settings.php in the Config namespace that extends CodeIgniter\Config\BaseConfig.
Step B:
Copy the variables from the CI3 config into the new CI4 config class as public class properties. I.e:
<?php
namespace Config;
class Settings extends \CodeIgniter\Config\BaseConfig
{
public string $lang1_HTML = "sr-Latn-RS";
public string $lang1_HTML_2 = "sr-Latn";
public string $lang1_HTML_3 = "";
public string $lang1_code = "SRP";
public string $lang1_flag = "/images/flags/rs.png";
public string $sr = "lang1";
// ...
}
Step C:
Change the config fetching syntax everywhere you fetch config values.
I.e: from $this->config->item('lang1_HTML'); to config(\Config\Settings::class)->lang1_HTML;.
Summary:
CodeIgniter 3.x
CodeIgniter 4.x
1. Loading custom config files.
Manual Loading $this->config->load('config_filename');
CodeIgniter 4.x will automatically look for the files in all defined namespaces as well as /app/Config/.
2. Dealing with name collisions.
If you need to load multiple config files, normally they will be merged into one master $config array. To avoid collisions you can set the second parameter to TRUE and each config file will be stored in an array index corresponding to the name of the config file. Load config file: $this->config->load('settings', TRUE); Access item: $this->config->item('settings')['lang1_HTML']
You don't have to worry about this since all config files reside in their own individual classes. Access item: config(\Config\Settings::class)->lang1_HTML
3. Fetching Config items.
$this->config->item('lang1_HTML');
config(\Config\Settings::class)->lang1_HTML
4. Dynamically setting a config item or changing an existing one.
Set item: $this->config->set_item('lang1_HTML', 'sr-Cyrl-ME'); Access set item: $this->config->item('lang1_HTML');
Set item: config(\Config\Settings::class)->lang1_HTML = 'sr-Cyrl-ME' Access set item: config(\Config\Settings::class)->lang1_HTML
5.Auto-loading.
Global Auto-loading: Open the autoload.php file, located at application/config/autoload.php, and add your config file as indicated in the file. $autoload['config'] = array('settings');
Global Auto-loading: There is no need to configure this since the config(...) helper function returns a shared instance of the particular config class by default. Hence, you can always access your configs using: config('config_class_here')->item_name_here;
first go to app config
public $defaultLocale = 'en';
/**
* --------------------------------------------------------------------------
* Negotiate Locale
* --------------------------------------------------------------------------
*
* If true, the current Request object will automatically determine the
* language to use based on the value of the Accept-Language header.
*
* If false, no automatic detection will be performed.
*
* #var bool
*/
public $negotiateLocale = true;
/**
* --------------------------------------------------------------------------
* Supported Locales
* --------------------------------------------------------------------------
*
* If $negotiateLocale is true, this array lists the locales supported
* by the application in descending order of priority. If no match is
* found, the first locale will be used.
*
* #var string[]
*/
public $supportedLocales = ['en','fa'];
go to app create folder language
folder en and folder fa
<?php
en/trans.php
return [
'auth' => [
'validation' => 'information is not valid',
'loggedIn' => 'you are already login',
'notLogIn' => 'you are not login',]];?>
fa/trans.php
<?php
return [
'auth' => [
'validation' => 'خطای اطلاعات وارد شده',
'loggedIn' => 'شما قبل وارد شده بودید',
'notLogIn' => 'شما وارد نشدید',]];?>
read header it send to ci4 --
Accept-Language en
or Accept-Language fa
echo lang('trans.auth.validation');
I'm trying to do some integration testing for my cloud streaming application. One of the main issues I'm observing so far is that the TestChannelBinderConfiguration keeps picking up the configuration specified in src/main/java/resources/application.yml instead of keeping it as blank (since there is no config file in /src/test/resources/).
If I delete the application.yml file or remove all spring-cloud-stream related configuration, the test passes. How can I ensure that the TestChannelBinderConfiguration does not pick up application.yml file.
#Test
public void echoTransformTest() {
try (ConfigurableApplicationContext context =
new SpringApplicationBuilder(
TestChannelBinderConfiguration.getCompleteConfiguration(DataflowApplication.class))
.properties(new Properties())
.run("--spring.cloud.function.definition=echo")) {
InputDestination source = context.getBean(InputDestination.class);
OutputDestination target = context.getBean(OutputDestination.class);
GenericMessage<byte[]> inputMessage = new GenericMessage<>("hello".getBytes());
source.send(inputMessage);
assertThat(target.receive().getPayload()).isEqualTo("hello".getBytes());
}
}
I resolved this by doing the following:
SpringBootTest(properties = {"spring.cloud.stream.function.definition=reverse"})
#Import(TestChannelBinderConfiguration.class)
public class EchoTransformerTest {
#Autowired private InputDestination input;
#Autowired private OutputDestination output;
#Test
public void testTransformer() {
this.input.send(new GenericMessage<byte[]>("hello".getBytes()));
assertThat(output.receive().getPayload()).isEqualTo("olleh".getBytes());
}
}
and adding an application.yml to the test/resources this ensures that we don't read the src/resources application properties.
Another way was to explicitly define
#TestPropertySource(locations= "test.yml")
The default integration spec of the scala play framework looks as follows:
class IntegrationSpec extends PlaySpec with OneServerPerTest with OneBrowserPerTest with HtmlUnitFactory {
"Application" should {
"work from within a browser" in {
go to ("http://localhost:" + port)
pageSource must include ("Your new application is ready.")
}
}
}
Clicking the port variable opens the OneServerPerTest.scala file:
/**
* The port used by the `TestServer`. By default this will be set to the result returned from
* `Helpers.testServerPort`. You can override this to provide a different port number.
*/
lazy val port: Int = Helpers.testServerPort
Clicking Helpers.testServerPort results in:
/**
* The port to use for a test server. Defaults to 19001. May be configured using the system property
* testserver.port
*/
lazy val testServerPort = Option(System.getProperty("testserver.port")).map(_.toInt).getOrElse(19001)
Searching for testserver.port does not return the variable. How is it possible that the test succeeds while the default port is 9000 and where is testserver.port defined?
The TestServer is started using this port, as you can see here. The port 9000 is the default for development mode, not all the test mode. If you are not setting testserver.port property in the command used to start the tests, them the default one (19001) will be used.
I want to do something like that
class AssetsController #Inject()(path: String) extends Controller {
// ...
}
The path should be taken from application.conf where the key is path.to.something. In the future, I may add some other properties (source is the same *.conf file) to my controller.
Is it possible to that in Finatra?
PS
When using Spring Framework you can inject a value in this way
#Value("#{configuration.key}")
private String key;
Maybe in Finatra there is something similar to String approach?
Finatra doesn't read configuration from files. You have to pass the config via command line options and can get the options via #Flag annotation. For example,
$ java -jar app.jar -path=/foo/bar
// MyController.scala
class MyController #Inject(#Flag("path") path: String) {
def index(request: Request) = {
path // "/foo/bar"
}
}
You can read the doc for more info.
I try to build an importer with a scheduler task.
The task creates an object manager which creates my import service.
This import service has dependencies to the repository.
I simply create instances and add them to the repository.
It works well until i tried to specify on which pid my records are supposed to be saved. I tried to configure it in setup.txt.
plugin.tx_extkey {
view {
templateRootPath = {$plugin.tx_extkey.view.templateRootPath}
partialRootPath = {$plugin.tx_extkey.view.partialRootPath}
layoutRootPath = {$plugin.tx_extkey.view.layoutRootPath}
}
persistence {
storagePid = {$plugin.tx_extkey.persistence.storagePid}
classes {
EXTNAME\EXTNAME\Domain\Model\MODELNAME {
newRecordStoragePid = {$plugin.tx_extkey.persistence.storagePid}
}
}
}
features {
# uncomment the following line to enable the new Property Mapper.
# rewrittenPropertyMapper = 1
}
}
module.tx_extkey {
persistence < plugin.tx_extkey.persistence
}
But that didn't work. Everything is still saved to pid 1.
Are there any pitfalls that I might have overlooked?
I found an ugly way. The BackendConfigurationManager does not get the extensionName when the service is executed though the scheduler. Manually setting it in the task resolves this.
$objectManager = GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Object\ObjectManager');
/** #var BackendConfigurationManager $configurationManager */
$configurationManager = $objectManager->get('TYPO3\CMS\Extbase\Configuration\BackendConfigurationManager');
$configurationManager->setConfiguration(array(
'extensionName' => 'hnsenvionjob'
));
I use an ImportCommandController as recommended by lorenz in In an extbase extension, how to access the persistence layer from a scheduler task? (my actual code has changed in the meantime, let me know if you want it)
and I have to set the pid manually:
$item->setPid($storagePid);
There is an easy trick for that
add in your ts something like that
plugin.tx_extkey.settings.storagePid = {$plugin.tx_extkey.persistence.storagePid}
That will allow you to have access to your storage pid everywhere in your code where you have access to your ts. For example in controller
$this->settings['storagePid']