Every now and then it's useful if your application tweaks itself to the environment its running in; be it development, testing or production.

For example, during development its great for your web application to serve up 500 error pages with stack traces, session dumps and other detailed system information. But in production, so not to scare users, you're gonna want to turn that off and serve up a plain Internal Application Error page instead.

Other reasons may include environment specific database connections, email accounts and so on.

IocEnv is a small utility library for Fantom that scans system information to determine what environment your application is running in. It looks at environment variables, program arguments and even offers a manual override option.

IocEnv is built on top of IoC and makes the IocEnv class available as an IoC service. It also integrates with IocConfig to give you a handful of injectable config values. All of these may be freely used by your IoC application.

Usage - Service Injection

IocEnv uses IoC for Dependency Injection (DI) so we can inject the service in the usual manner:

select all
using afIoc::Inject
using afIocEnv::IocEnv

class MyService {
    @Inject IocEnv iocEnv

    ...

    Void wotever() {
        if (iocEnv.isDev) {
            ... // dev only stuff
       }
    }
}

The IocEnv class holds the environment setting and has handy utility methods for some common use cases.

Usage - Config Injection

IocEnv also makes available some IocConfig values, allowing you to inject Bool's straight into your service:

select all
using afIoc::Inject
using afIocConfig::Config

class MyService {
    @Config { id="afIocEnv.isDev" }
    @Inject Bool isDev

    ...

    Void wotever() {
       if (isDev) {
          ... // dev only stuff
       }
    }
}

Configuring the Environment

The environment may be obtained in a number of ways. In order of precedence, it is inferred by the following:

  1. Manual Override

    You may manually set the environment when you create an IocEnv instance.

  2. Program Arguments

    IocEnv checks the program arguments that Fantom was launched with. It looks for an option labelled -env or -environment and assumes the environment is the parameter following. Example:

    fan afBedSheet myWebApp 8080 -env test
  3. Environment Variables

    IocEnv looks for an environment variable called env or environment

The ordering means the environment variables are checked first but may be overridden by program arguments. Subsequently, program arguments may be overridden by setting the environment manually.

The convention for prefixing program arguments with a hyphen is taken from the @Opt facet and AbstractMain. If no environment setting is found, it defaults to PRODUCTION as this is often the lowest common denominator. It also means it's easy to deploy your application to production, which is good, as prod deployments can be tricky enough!

To ensure apps always run in development mode on your local machine, set a new environment variable called ENV to the value dev or development.

Creating an ENV environment variable

Manual Override

Sometimes you want to override the environment setting programmatically - which is common in testing. You can do this by manually creating the IocEnv service in a test specific AppModule, passing in whatever environment you want.

@Override
IocEnv overrideIocEnv() {
    return IocEnv.fromStr("Testing")
}

As we can't change or un-make the existing IocEnv service, we create a new service instance and contribute it as a service override. IoC then uses the override everywhere in place of the original.

Complete Example

This example has been successfully tested with:

  • Fantom 1.0.68
  • Ioc 3.0.2
  • IocEnv 1.1.0
  • IocConfig 1.1.0

1). Create a text file called Example.fan:

select all
using afIoc
using afIocEnv
using afIocConfig

const class Example {
    @Inject
    const IocEnv iocEnv               // --> Inject IocEnv service

    @Config { id="afIocEnv.isProd" }  // --> Inject Config values
    const Bool isProd

    new make(|This| in) { in(this) }

    Void printEnv() {
        echo("The environment is '${iocEnv.env}'")

        if (isProd) {
            echo("I'm in Production!")
        } else {
            echo("I'm in Development!!")
        }
    }
}

// ---- Standard afIoc Support Classes ----

class Main {
    Void main() {
        registry := RegistryBuilder() {
            addModulesFromPod("afIocEnv")
            addService(Example#)
        }.build()

        example  := (Example) registry.rootScope.serviceByType(Example#)
        example.printEnv()
    }
}

2). Run Example.fan as a Fantom script from the command line:

C:\> fan Example.fan -env PRODUCTION
[info] [afIocEnv] Setting from environment variable 'env' : development
[info] [afIocEnv] Overriding from cmd line argument '-env' : PRODUCTION
The environment is 'PRODUCTION'
I'm in Production!

Epilogue

We have seen it is useful and important for an application to distinguish between the environments it runs in. We've looked at how the IocEnv library is environmentally aware and how your application can use it to make decisions dependent on the environment it runs in.

Have fun!

Edits

  • 31 Jul 2016 - Updated examples to use IoC 3.0 and IocEnv 1.1.
  • 3 Dec 2013 - Original article.

Discuss