Java Reference

Configuration System

Introduction

KOS simplifies the management of configuration settings in your application by providing a centralized solution. It consists of several components that work together to load configuration values from various sources, update them dynamically, and notify your application when they change. This article explains how these components interact and how you can use them to configure your application effectively.

Overview

This section gives a brief overview of the configuration system.

Background

In any system as complex as beverage dispensers, there’s a need for a hundreds if not thousands of configuration values. Pump parameters, cup sizes, available ingredients, recipes, mix ratios, special modes — just to name a few. KOS is built with configuration in mind.

Reasons for config values include:

  • Most services have config beans to make the service configurable

  • Some settings are related to things that can be changed from the NCUI

  • Many settings exist to facilitate debugging and testing

  • Config changes are commonly used to mitigate field issues

Highlights

The KOS system handles all configurations for a node.

  • An object becomes configurable by implementing the ConfigAware interface

    • ConfigAware extends HandleAware, which means that all configurable objects are identified by their handle path

    • A ConfigBean is associated with every ConfigAware object

      • Must derive your configuration class from ConfigBean

      • It stores all configuration data for a ConfigAware object

      • May be an object graph accessed via dot notation

      • The ConfigBean instance must be created by the user unless the ConfigAware object is in a BeanContext

      • Implements the Ready interface

        • ConfigAware won’t be ready until ConfigBean is configured

      • Allows listeners to receive callbacks when the config bean is changed

    • ConfigAware callbacks

      • onConfigSet() called on first set, defaults to call onConfigChanged() but can be overridden

      • onConfigChanged() called when configuration changes

    • BeanChanges

      • Records all changes made to a ConfigBean

      • Contains dotted path to the changed property, the new value, and the previous value

      • Can be used to detect when particular properties are modified

      • Setting a property to its existing value doesn’t generate a change event

  • ConfigSource

    • Source of configuration data that can be added to ConfigService

    • Highest priority source is override source backed by database, holds all changes

    • Default source contains a sorted list of child sources

      • Sources can be loaded from XML files

      • Allows different defaults to be injected at startup to change overall system application

      • Example: different models of dispenser have different default values (such as max pour volume)

    • Lowest priority source is ConfigBean source

      • Every ConfigBean is examined before it is configured, and constructor settings are recorded as the default values

      • Ensures every ConfigBean has default values that reflect values used in the constructor

  • Configuring beans

  • Endpoints

  • Broker events

  • ConfigService uses reflection to process beans

    • Use @ConfigIgnore to hide properties/getters that are not part of the configuration

    • Be careful of creating aliases via getters:

      • example: getCups() returns a list of cups, getFirstCup() returns the first, so there are now two ways to reach the first cup (two distinct dotted notation paths to the same property)

      • Use @ConfigIgnore to hide getFirstCup()

      • Aliases allow multiple values for the same property so final value is ambiguous

  • Use @ConfigDesc to document config properties

    • Description only vs. description plus format

    • Description used on config editor

    • Format used by UI SDK to auto-format data

Objects

Many objects in KOS are configurable. That is, you can read and set their operational characteristics at runtime.

All configurable objects implement the ConfigAware interface. These objects have a getConfig() method that returns the object’s configuration data. All configuration objects extend the ConfigBean base class.

The configuration data is a different object from the actual object being configured. For example, a PumpService class might have a PumpServiceConfig configuration object.

Defaults vs. Overrides

ConfigService has one stack of ConfigSources for defaults and another for overrides.

  • Overrides only contain one ConfigSource, which is the database

  • Endpoints know the difference between overrides and defaults

  • Allows users to know what a value was before it was overridden

  • Not all configurations have default values, as this would require putting them all into XML files

API Components

ConfigAware (interface)

This is the ConfigAware definition:

API: ConfigAware interface
public interface ConfigAware<T extends ConfigBean> extends HandleAware {
    T getConfig();
    void setConfig(T config);

    default void onConfigSet(BeanChanges changes) {
        onConfigChanged(changes);
    }

    default void onConfigChanged(BeanChanges changes) {
    }
}

ConfigBean (class)

This is the ConfigBean definition:

API: ConfigBean class
public class ConfigBean implements Ready {

    // "Is configured" getter and setter:
    public boolean isConfigured();
    public void setConfigured(final boolean configured);

    // Control of listeners:
    public void addListener(ConfigBeanListener listener);
    public void removeListener(ConfigBeanListener listener);

    // From the Ready interface:
    public ReadyIndicator getReady();
}

ConfigBeanListener (interface)

This is the ConfigBeanListener definition:

API: ConfigBeanListener interface
public interface ConfigBeanListener {
    void onConfigChanged(ConfigBean bean, BeanChanges changes);
}

BeanChanges (class)

This is the BeanChanges definition:

API: BeanChanges class
public class BeanChanges extends HashMap<String, ValPair> {
    public Object getBean();
    public String getPath();

    public boolean isConfigured();
    public void setConfigured(boolean configured);

    public void recordChange(String attr, String value, String prevVal);
    public boolean containsAnyKey(String... keys);
}

Configuration data

Here’s an example of configuration data:

PumpServiceConfig class example
/**
 * Set of parameters that control the pump's operation.
 */
public class PumpServiceConfig extends ConfigBean {

    private int    masterLockHoldTimeMS;  // how long to enforce ownership of the master lock
    private int    volumeFlusherDelayMS;  // delay between volume cache flushes
    private int    dilutionRate;          // water flow rate for dilution
    private double dilutionVolume;        // volume to use for dilution
    private int    ambientDurationMS;     // duration of ambient pours
    private int    ambientRate;           // rate of ambient pours

    public PumpServiceConfig() {
        masterLockHoldTimeMS = 35000;
        volumeFlusherDelayMS = 300;
        dilutionRate = 80;
        dilutionVolume = 2000;
        ambientDurationMS = 30000;
        ambientRate = 10;
    }

    // . . .
}

Viewing config beans

Use the /api/handles/{handle}/details endpoint to see the actual config bean in memory.

For example: http://localhost:8080/api/paths/system.service:pump/details

{
    "code": 0,
    "message": "success",
    "data": {
        "config": {
            "options": null,
            "masterLockHoldTimeMS": 35000,
            "volumeFlusherDelayMS": 300,
            "dilutionRate": 80,
            "dilutionVolume": 2000,
            "ambientDurationMS": 30000,
            "ambientRate": 10
        },
    "name": "pump",
    "path": "system.service:pump"
    }
}

ConfigService

The ConfigService:

  • Configures ConfigAware beans at startup

  • Provides endpoints for updating config beans (updates are persisted to a database, so they survive reboots)

  • Any UI that changes settings uses config service endpoints

ConfigSource

The ConfigSource object:

  • provides configuration data about a collection of beans

  • ConfigSources are stacked and merged from bottom to top (bottom contains default values, top contains overrides)

  • Other services can inject a ConfigSource into the stack (LockDownService adds a ConfigSource when unlocked which points most services to test environments)

Config service endpoints

The ConfigService endpoints:

  • Returns configuration data for a handle

  • Only returns data from ConfigSources

  • Does NOT return data from the actual config bean, only ConfigSource objects (use /api/paths to see the actual config bean settings)

  • Allows new settings to be applied to a config bean named by a path

Dotted notation

Config beans are described using dotted notation Provides a way to describe a single property within an object graph

Consider cup sizes:

public class SelfServeAppConfig extends Config {

    /* the number of cups available */
    private static final int MAX_CUPS = 6;

    private Map<String,Cup> cups;

    // . . .
}

Dotted notation example

Example: How do you change the volume of portion number 1?

{
    "config": {
        "portions": {
            "1": {
                "id": 1,
                "enabled": true,
                "name": "K",
                "volume": 177, (1)
                "pauseSec": 2,
                "totalVolume": 100,
                "cupGrams": 4,
                "fullIceGrams": 100,
                "iceDensity": 0.9168,
                "fillPercent": 80,
                "calibratedOnDispenser": true
            }

    // . . .
}
1 Access this value by looking for the "portions.1.volume" node.

Dotted notation from /api/configs:

For configuration endpoint, use dotted notation:

  • Payload is wrapped in an object with values property: {"values": {"portions.1.volume": 400}}

  • Example of setting two volumes: {"values": {"portions.1.volume": 400, "portions.2.volume": 500}}

Configuration recap

Paths name things and are human-readable

  • /api/paths returns actual config bean in memory

  • /api/configs returns data from ConfigSource objects

  • Config beans can be changed on the fly using ConfigService endpoints

  • ConfigService endpoints use dotted notation

{
    "code": 0,
    "message": "success",
    "data": {
        "path": "system.service:crewServe",
        "overrides": {
            "portions.1.volume": "177",
            "portions.2.volume": "207.901705",
            "portions.4.calibratedOnDispenser": "true",
            "portions.4.volume": "872.1225149999999",
            "portions.2.pauseSec": "4",
            "portions.3.volume": "251.079015",
            "portions.5.volume": "1153.9579700000002",
            "portion.fullIceGrams": "300",
            "portions.2.cupGrams": "4",
            "enableProfiles": "true",
            "portions.1.totalVolume": "100"

            // . . .
        }
    }
}

Summary

Previous
Next
On this page
Java Development
Seamlessly transition from Legacy+ systems to Freestyle microdosing and advanced distributed dispense systems.
UI Development
Using KOS SDKs, integrating Consumer and Non-consumer facing UIs becomes seamless, giving you less hassle and more time to create.
Video Library
Meet some of our development team, as they lead you through the tools, features, and tips and tricks of various KOS tools.
Resources
Familiarize yourself with KOS terminology, our reference materials, and explore additional resources that complement your KOS journey.
Copyright © 2024 TCCC. All rights reserved.