-
Notifications
You must be signed in to change notification settings - Fork 2
English__Theory__The Environment Object
wj-config has been designed to collect all the information about environments in a single object, and this object
serves as the single source of truth for environment information. This object is used internally by the package to
provide certain features, and it is also exposed to the consumer so logic that is environment-specific or
environment-aware can be easily coded.
This, like all other features of the package, is an opt-in feature and will only be present if the consumer of the
package makes use of the includeEnvironment() function call while building the configuration.
There are two ways to include this handy environment object in the final configuration object:
- The consumer creates the environment object and then provides it.
- The consumer just provides the information necessary to create the object directly.
This (the first choice) is probably the more common one because often times, building the configuration requires knowledge of the current environment.
import wjConfig, { buildEnvironment } from "wj-config";
const env = buildEnvironment('myCurrentEnvironment', [
'My',
'List',
'Of',
'Possible',
'Environments'
]);
const config = await wjConfig()
.includeEnvironment(env)
...
.build();
export default config;Use this method of creating the environment object yourself if you have the need to use it during the configuration building process.
As seen in the code sample, creating the object is very simple: Pass the current environment name and a list of all possible environment names.
The latter might be confusing: Why do we want to specify all possible environment names? This is why:
- It allows the package to create handy testing functions for each environment (see Environment Testing).
- It enables the use of the shortcut builder function
addPerEnvironment(). - It enables environment coverage checking (see Using forEnvironment()).
- It assists you, the developer, in catching any mistyped environment names.
The list of environments is actually optional. If not provided, then the default list of environments is used. This
default list contains: Development, PreProduction and Production.
IMPORTANT: Even though providing the list of environment is optional, you are required to provide as current environment name a name that is in the list of possible environments, be it the default list or the list you provide. Long story short, as best practice, enumerate all your environments.
The typical use case looks something like this:
import wjConfig, { buildEnvironment } from "wj-config";
import mainConfig from './config.json';
const env = buildEnvironment('myCurrentEnvironment', [
'My',
'List',
'Of',
'Possible',
'Environments'
]);
const config = await wjConfig()
// Forward the created Environment object.
.includeEnvironment(env)
.addObject(mainConfig).name('Main')
// Use the environment object to build your environment-specific data source.
// This is referred to as "classic" per-environment configuration.
.addFetched(`./config.${env.current.name}.json`, false)
.build();
export default config;If there is no need to make use of the functionality that the environment object provides during configuration
building, then use option 2: Just call includeEnvironment() with the necessary data to build the environment object:
import wjConfig from "wj-config";
const config = await wjConfig()
.includeEnvironment('myCurrentEnvironment', [
'My',
'List',
'Of',
'Possible',
'Environments'
])
...
.build();
export default config;This is the preferred way to go when you opt for the "conditional" per-environment configuration (see here).
Ok, so we've seen one piece of information coming out of the environment object: The current environment's name.
Let's see about all the available functionality.
The environment object exposes the current property of type IEnvironmentDefinition. This means that the value of
current is an object with 2 properties: name and traits.
The name property returns the current environment's name and this is what is seen in the example of use in the
Creating an Environment Object section. Yes, its data type is string.
The traits property, on the other hand, is far more interesting: It can be a number or it can be an array of
strings. It is used in Per-Trait Configuration
and basically contains the list of all traits assigned to the current environment.
While traits is a concept developed to facilitate configuration building, it is not limited to configuration
building. The traits property can be tested at any time for the presence of traits anywhere in your project, if you
so need to or desire. The environment object provides 2 trait-testing functions, which are the subject of the next
section.
As stated in the previous section, the current environment can be assigned traits. Said traits, in return, can be
tested for whatever reason. If you find the use of traits helpful for your project development, you'll find 2
trait-testing functions in the environment object: hasTraits() and hasAnyTrait().
The hasTraits() function returns true if the current environment's traits contain all of the specified traits.
The hasAnyTrait() function returns true if the current environment's traits contain any of the specified
traits.
Let's pose an example of use.
Imagine you are creating a project that you customize and sell to multiple clients by hosting instances of the customized application. There are basic (or core) features and there are premium features. Not all customers pay for the premium features. Your job as a developer is to create code that only shows the premium pieces of the user interface if the client has paid for them.
Individual traits can be bitmasked numbers or can be strings, making the value of the traits property a number
or an array of strings because it is meant to hold all traits (plural form). Let's define a traits enumeration. Why
if we only need the Premium trait? Because you might want to define more traits in the future. Preparing an
enumeration from day 1 is just good practice.
export default Object.freeze({
None: 0x0,
Premium: 0x1 // Least significant bit: 0000 0001
});Now in your UI project (React, Vue, Svelte, Preact, etc.) you can import the traits enumeration and test:
import myTraits from './myTraits.js';
import { environment } from './config.js';
// For just 1 trait, there is no difference between hasTraits() and hasAnyTrait().
if (environment.hasTraits(myTraits.Premium)) {
// Ok to show the premium pieces of the UI.
}That's it. This is how traits essentially work.
To define traits for the current environment, pass an object to buildEnvironment() instead of the current environment
name. The shape of this object must satisfy the IEnvironmentDefinition interface.
To do this, you may create a new EnvironmentDefinition object, or just a POJO object with the name and traits
properties:
import wjConfig, { buildEnvironment, EnvironmentDefinition } from "wj-config";
import mainConfig from './config.json';
// Bitmasked value of all traits assigned to the current environment:
const myCurTraits = parseInt(someEnvironmentVariable);
const curEnv = new EnvironmentDefinition('myCurrentEnvironment', myCurTraits);
// Pass the current environment definition as first argument.
const env = buildEnvironment(curEnv, [
'My',
'List',
'Of',
'Possible',
'Environments'
]);
const config = await wjConfig()
.includeEnvironment(env)
...
.build();
export default config;The variable someEnvironmentVariable represents any mechanism that your project uses to communicate the traits you
want assigned to this deployed instance of the application. It typically would be a defined environment variable, but
remember that in browser applications there is no environment, so it would be, for example, an artificial environment
in, say, window.env.
The environment object will carry environment-testing functions for all of the defined environment names passed to
buildEnvironment(). The names of these functions follow the pattern isXXX(), where XXX is the environment's name.
Note: The environment name is capitalized in these environment-testing functions. Example: Environment name
devproduces a function namedisDev().
If you don't define your environment names, the default environment names will produce the isDevelopment(),
isPreProduction() and isProduction() functions. Each function will return true if the current environment's
name equals the name associated to the function.
Looking at the list of environments being used in the examples in this page, the environment object will contain the following:
import { environment } from './config.js';
if (environment.isMy()) {
}
else if (environment.isList()) {
}
else if (environment.isOf()) {
}
else if (environment.isPossible()) {
}
else if (environment.isEnvironments()) {
}
else {
console.log('This application achieved the impossible!');
}Contents
- English
- Theory
- JavaScript Concepts
- Data Sources
- Español
- Teoría