Runtime environments for applications

Hi,

using Rails for quite a while, there is a notion of an runtime environment which basically let’s you define different configurations for different names like “staging”, “production”, “testing” as well as custom ones like “UAT”, “testing-load”, “staging-config-x” etc. This configuration influence the behavior of the running software (as well as how it might start). Here are some information about http://guides.rubyonrails.org/configuring.html#rails-environment-settings. Grails basically supports the same thing. Spring boot calls it Spring profiles and if i get it correctly it is basically a very similar thing.

So before starting my own thing here and just create a config interface in my app that allows me to switch environments / profiles / contexts, i thought it would be interesting about how you generally see this topic, if you use something like this in your own applications, if this is something that should be considered for the platform etc.

Perhaps you have even other approaches to do some kind of switching (like building different wars through gradle buildWar with different web.xml files that include different app.properties files etc. Or you just don’t do it at all and just use the external local.app.properties feature outside the war to customize the behavior of the running application.

I’d be interested in your thoughts on that.

Thanks & Bye
Mario

Hi Mario,

You are right, we should introduce a more or less holistic way to manage configurations, because currently we just prepare different property files, web.xml, Gradle tasks, etc.

As a first thing to do, we need to gather some requirements. Could you share some details about switches that you had to implement in your project?

Hi,

from the top of my head, i would think of something like this:

  • We have UI buttons that are only present in a test environment for the ease of use of our testers (for generating test data, see CRUD screens for entities that are not accessible in the real app, e.g. because these screens are done via a android app)
  • configuration of URLs to external systems that change from dev to test to production
  • load test data on startup depending on the environment that the application starts (like described here: https://www.road-to-cuba-and-beyond.com/test-and-seed-data/)

It might be that we want to switch some bean implementations based on the environment, but currently i don’t see a use case for this (at least for me).

Bye
Mario

:ticket: See the following issue in our bug tracker:

https://youtrack.cuba-platform.com/issue/PL-8684

I see the following comment in PL-8684:

“Since 6.8 we can use @ConditionalOnAppProperty annotation to emulate desired behavior.”

Couldn’t find any reference in Documentation.
Can we have some more details, please?

If it can help. To manage our different environments we have a rather classic approach by managing different set of .properties file and building a war by environment.

More importantly we use that to produce a war tailored for each of our clients (1 client = 1 war = 1 database).

I have maybe an old-fashioned approach on that, but this problematic is linked to configuration and should not interfere with code IMHO. With .properties file anyway you can tell your app that you are in this or that environment.

If someone interested I can share part of our build.gradle.

@michael.renaud Please share your approach.

Hi, a bit late to the party but here you are.

We defined a gradle task ‘distrimake’ that takes the war generated by buildWar and modify it depending on specific configuration by tenant. For us a tenant is an environment that can be dev, homologation, or production for one client (our app is multitenant, 1 app per client).

Specific configurations are in /distrimake/configs with specific .properties file and context.xml files per tenant.

Hereunder the gradle task.

Regards
Michael

task distrimake(dependsOn: buildWar) {
    doLast {
        def tenantList = "tenant1, eurodif, theappdev"
        def arcName = "theapp-" + cuba.artifact.version + ".war"
        def zipFile = file("${buildDir}/distributions/war/" + arcName)
        copy {
            from "${buildDir}/distributions/war/theapp.war"
            into file("${buildDir}/distributions/war/")
            rename "theapp.war", arcName
        }
        delete "${buildDir}/distributions/war/theapp.war"
        def unpacked = file("${buildDir}/distributions/war/unpacked")
        copy {
            from zipTree(zipFile)
            into unpacked
        }
        def tenants = tenantList.tokenize(", ")
        File target = new File("$unpacked/WEB-INF/local.app.properties")
        String localProps = target.text
        tenants.each {
            // local app : change home directory, override connectionUrlList & append tenant specific props
            String localPropsTenant = localProps.replaceAll("theapp-home/", it + "_home/")
                    .replaceAll("theapp-core", "theapp-core-" + it)
            localPropsTenant = localPropsTenant + "cuba.logFile = " + it;
            File tenantPropFile = new File("$project.rootDir/distrimake/configs/busy-tenant." + it + ".properties")
            String tenantProps = tenantPropFile.text
            localPropsTenant = localPropsTenant + System.getProperty("line.separator") + tenantProps
            target.text = localPropsTenant
            // replace context.xml with tenant one
            def tenantCtxFile = new File("$project.rootDir/distrimake/configs/core-war-context." + it + ".xml")
            String tenantCtx = tenantCtxFile.text
            new File("$unpacked/META-INF/context.xml").text = tenantCtx
            // override web contexts in core & web blocks
            new File("$unpacked/WEB-INF/local.core-app.properties").text = "cuba.webContextName = theapp-core-" + it
            new File("$unpacked/WEB-INF/local.web-app.properties").text = "cuba.webContextName = theapp-" + it
            // set logging.properties helping audting tomcat deploy issues
            new File("$unpacked/WEB-INF/classes/logging.properties").text = "org.apache.catalina.core.ContainerBase.[Catalina].level=INFO\n" +
                    "org.apache.catalina.core.ContainerBase.[Catalina].handlers=java.util.logging.ConsoleHandler"
            // copy local app properties where logback can find it
            ant.copy(
                    file: "$unpacked/WEB-INF/local.app.properties",
                    toFile: "$unpacked/WEB-INF/classes/local.app.properties",
                    overwrite: true
            )
            // rezip war for tenant
            def arcTenantName = "$project.buildDir/distributions/war/theapp-$cuba.artifact.version-" + it + ".war"
            ant.zip(destfile: arcTenantName) {
                fileset(dir: unpacked) {
                    include(name: "**/*.*")
                }
            }
        }
        ant.delete(dir: unpacked)
    }
}
2 Likes