Specify environment-specific configuration values for an application

OSF provides a mechanism that you can use to create separate application configuration for development, test, and production environments, and to automatically apply the correct configuration for whichever environment the application is currently running in.

Configuration files

A workspace includes an index.js configuration file in its node_modules/@oracle-cx-commerce/osf-config/app-config/ directory. The settings in this file apply by default to all of the applications in the workspace.

The settings in the configuration files are specified as JSON key/value pairs. The default configuration file contains the following settings:

module.exports = {
  cacheEnabled: true, // whether to enable cache or not
  cacheOptions: {
    validityPeriod: 2 * 60 * 1000, // validity period of the req to be in cache. If expired, the request goes to OCC for re-validation.
    ttl: 30 * 60, // if expired, the response against respective request stored in cache gets removed.
    max: 99999, // maximum objects to be stored in the cache.
    cacheableUrlPatterns: {
      // url patterns for which the urls should be cached. If the url doesn't fall under any of these, it is not cache-able.
      default: {
        urlPatterns: [
          '/ccstore/v1/collections',
          '/ccstore/v1/countries',
          '/ccstore/v1/files/',
          '/ccstore/v1/itemTypes',
          '/ccstore/v1/locations',
          '/ccstore/v1/merchant/cancelReasons',
          '/ccstore/v1/merchant/cloudConfiguration',
          '/ccstore/v1/merchant/paymentConfigurations',
          '/ccstore/v1/merchant/production-Experiments',
          '/ccstore/v1/merchant/production-Facebook',
          '/ccstore/v1/merchant/returnReasons',
          '/ccstore/v1/merchant/samlSettings',
          '/ccstore/v1/merchant/shopperSettings',
          '/ccstore/v1/payment/types',
          '/ccstore/v1/priceListGroups',
          '/ccstore/v1/prices/',
          '/ccstore/v1/products',
          '/ccstore/v1/productTypes',
          '/ccstore/v1/registry',
          '/ccstore/v1/shippingRegions',
          '/ccstore/v1/shippingMethods/',
          '/ccstore/v1/sites',
          '/ccstore/v1/xregistry'
        ]
      }
    }
  }
};

To override settings in this file or create additional configuration, you should not modify the default configuration file directly, because the values in the file will be restored if you perform certain operations in the workspace. Instead, create an application-specific index.js file in the packages/apps/<app-name>/config/app-config/ directory. When an application is started up, the system checks for this file, and if it exists, merges it in memory with the default configuration to produce the configuration for the application. Both files are packaged up with the application and uploaded to the Commerce environment and OSF controllers.

You can use predefined properties for application-specific settings in this file, as well as custom keys to specify additional configuration. The predefined properties available include:

  • configRepositoryState – This object appears at the top level of the merged configuration. It contains settings for the client state, which are loaded at runtime.
  • endpointOrigins – This object specifies mappings of keys to associated URL domains for calling endpoints.
  • liveConfigurations – This object contains settings that supplement or modify the merged configuration if the application is running in live mode.
  • appContextConfigurations – This object contains development, test, and production subobjects. Depending on the application context the application is running in, the settings in the associated subobject are merged with the configuration.

These properties are discussed below.

Local development mode

If the application is running in local development mode, the system also checks for a config.js file in the packages/apps/<app-name>/.occ/ directory. If the file exists, the configuration in it is merged as well. This file can include the same predefined properties listed above. The file is not included with the application when it is uploaded, since it applies only to local development environments.

The next section describes the logic used in merging the configuration in memory and how to create application-specific configuration files based on this logic.

How the configuration is merged

When configuration is merged, the properties from multiple sources are combined in memory to create the configuration for the application. Properties that do not conflict are simply added to the configuration, while properties that are specified in multiple sources must be resolved to determine which values to use.

You can set the same property to different values in multiple configuration files or even in multiple places in the same configuration file. When configuration is merged in memory, the system can override values with ones that apply in specific contexts. This section illustrates how the configuration is merged, using examples of application-specific index.js files.

Note that overriding values applies only to top-level properties in the merged configuration. So overriding a scalar property simply replaces the property value in the merged configuration, but overriding a top-level property whose value is an object replaces the object in its entirety, including any subobjects. Properties within objects are not added or overridden individually.

In the following example, the application-specific configuration file includes configRepositoryState and endpointOrigins settings as well as custom properties:

module.exports = {
  // Values to add to the client state
  "configRepositoryState": {
    "client-state-value-1": 1,
    "client-state-value-2": [1000, 2000, 3000],
    "client-state-value-3": {
      "pizza": "cheese",
      "topping": ["olives"]
    }
  },
  // Endpoint Origins for constructing endpoint calls
  "endpointOrigins": {
    "pizza-endpoint": {
      "origin": "https://development.pizza-endpoint.com"
    }
  },
  // Top-Level Custom Properties
  "example-top-level-property-string": "sample-value",
  "example-top-level-property-array": ["string-1", "string-2"],
  "example-top-level-property-object": {
    "object-key-a": "nested-object-string",
    "object-key-b": [ "value-1", "value-2"],
    "object-key-c": { "arg1": 1, "arg2": 2 }
  },
};

The default configuration file does not contain a configRepositoryState or endpointOrigins object, so these objects in the application-specific settings are added to the merged configuration without overriding anything. The same is true for the custom properties in the application-specific file. You can use application-specific values to override settings from the default configuration file, but typically the default settings can be left as is.

Merge a live object

In the next example, we add a liveConfigurations object to the application-specific file shown above:

  // Merged with above when --live flag is turned on
  "liveConfigurations": {
    "example-top-level-property-string": "override-sample-value",
    "configRepositoryState": {
      "client-state-value-1": 777,
      "client-state-live-value": "in client state when live flag is turned on"
    }
  }

The liveConfigurations object contains settings that are merged into the configuration only if the application is running in live mode. (They are omitted if the application is run in preview mode.) The live settings are merged if the application is running on a live controller, or if it is running locally and is started up with the --live flag.

In this example, the properties in the liveConfigurations object override the equivalent top-level properties in the merged configuration. The value of the example-top-level-property-string property is changed from sample-value to override-sample-value in the merged configuration, and the entire top-level configRepositoryState object is replaced by the one under liveConfigurations.

Context-specific configuration

In addition to the settings in the examples above, the application-specific configuration file can also have an appContextConfigurations object that includes settings that are merged conditionally, depending on the environment (development, test, or production) the application is running in. For example:

  "appContextConfigurations": {
    // will be merged with top-level properties when --appContext development or ENV_TYPE=DEV
    "development": {
      "example-top-level-property-string": "sample-value-development",
      "configRepositoryState": {
        "client-state-value-1": 333
      },
      // Merged with above when --live flag is turned on
      "liveConfigurations": {
        "example-top-level-property-string": "override-sample-value-development-live"
      }
    },
    // will be merged with top-level properties when --appContext test or ENV_TYPE=TST
    "test": {
      "example-top-level-property-string": "sample-value-test",
      "configRepositoryState": {
        "client-state-value-1": 444
      },
      // Merged with above when --live flag is turned on
      "liveConfigurations": {
        "example-top-level-property-string": "override-sample-value-test-live"
      }
    },
    // will be merged with top-level properties when --appContext test or ENV_TYPE=PRD
    "production": {
      "example-top-level-property-string": "sample-value-production",
      "configRepositoryState": {
        "client-state-value-1": 555
      },
      // Merged with above when --live flag is turned on
      "liveConfigurations": {
        "example-top-level-property-string": "override-sample-value-production-live"
      }
    }
  }

As this example shows, the appContextConfigurations object can have development, test, and production subobjects. When the application is started up, the settings associated with the environment the application is running in are merged. The environment is determined by an environment variable on the controller, or by the --appContext flag if the application is started up locally.

In this example, if the application is running in the test environment, the test object is merged. The value of example-top-level-property-string is changed to sample-value-test, and the configRepositoryState object in test overrides the top-level configRepositoryState.

Notice, though, that each environment-specific object can also have a liveConfigurations subobject. If the application is running in live mode, the liveConfigurations subobject is merged after the rest of the environment-specific object. In this example, merging the liveConfigurations subobject changes the value of example-top-level-property-string to override-sample-value-test-live.

Finally, if an application is running in local development mode, the packages/apps/<app-name>/.occ/config.js file is also merged. This file can also contain configRepositoryState, endpointOrigins, liveConfigurations, and appContextConfigurations objects, as well as custom properties, to override or supplement the properties set elsewhere.

View the merged configuration

You can debug the merged configuration to ensure that it is what you expect it to be. To do this, enter the following command to display the configuration without starting up the application:

yarn occ serve --listConfig

When you use the serve command with the --listConfig flag, you can also use other command-line flags to specify the application context. These settings override the values supplied in environment variables and in the workspace's .occ/config.js file. For example, the following command returns the merged configuration for running an application in the live context of the test environment:

yarn occ serve --listConfig --live --appContext test

Endpoint origins

The endpointOrigins object provides a way to store the base URLs for endpoints in application configuration files. This makes it possible to override these URLs on a context-specific basis.

When you create endpoints from a Swagger catalog using the create-endpoint command, an entry is automatically added to the top-level endpointOrigins object of the application's /config/app-config/index.js file. (If the file does not already exist, it is created automatically.) For example, you can create an endpoint like this:

yarn occ create-endpoint --directoryName my-endpoints --swagger http://www.example.com/catalogApi --endpoints getOrder

An entry for the specified directory name (in this case, my-endpoints) is added to the file:

"endpointOrigins": {
  "my-endpoints": {
    "origin": "http://www.example.com/catalogApi:443"
  }
}

The value of the origin property is set to value of the host property in the Swagger catalog. If the location specified with the --swagger flag does not exactly match the value from the catalog, the entry also includes a catalogOrigin property with the specified value. For example:

"endpointOrigins": {
  "my-endpoints": {
    "origin": "http://www.example.com/catalogApi:443",
    "catalogOrigin": "http://www.example.com/catalogApi"
  }
}

For more information about using the create-endpoint command, see Create components using command-line tools.

Context-specific endpoint origins

By default, the endpointOrigins settings apply to all environments, but you can override them for specific environments using the appContextConfigurations object. For example, if the production environment accesses the Swagger catalog in a different location, you might specify:

"appContextConfigurations": {
    "production": {
      "endpointOrigins": {
        "my-endpoints": {
          "origin": "http://www.example.com/PRODcatalogApi"
        }
      }
    }
}