Skip to content
Muhammad Hamed edited this page May 3, 2024 · 2 revisions

Integrating baigan-config

Step 1: Install baigan-config dependencies

Add the following dependency to your project:

For maven build

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>baigan-config</artifactId>
    <version>${baigan.version}</version>
</dependency>

For gradle build

implementation 'org.zalando:baigan-config:${baigan.version}'

Step 2: Creating configurations

Baigan configurations follow a specific schema and can be stored on any of the supported repositories.

Configuration schema

Configurations are stored in its simplest form as key values. A configuration is a pair of a dot(.) separated key and a value objects in JSON format.

A configuration object should conform to the following JSON Schema:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "Configuration",
    "description": "A baigan configuration object value.",
    "type": "object",
    "properties": {
        "alias": {
            "description": "The identifier for the configuration, same as its key.",
            "type": "string"
        },
         "description": {
            "description": "Summary of the configuration.",
            "type": "string"
        },
         "defaultValue": {
            "description": "Default configuration if none of the condition is satisfied.",
            "type": {}
        },
         "conditions": {
            "description": "List of conditions to check",
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "value": {
                        "description": "Configuration value if this condition evaluates to true.",
                        "type": {}
                    },
                    "conditionType": {
                        "description": "Type of condition to evaluate. This can be custom defined, with custom defined properties.",
                        "type": "object"
                    }
                }
            }
        }
    },
    "required": ["defaultValue"]
}

Example configurations

This sample JSON defines a configuration for the key express.feature.enabled with the value true when the country_code is 3, and a default value of false.

[
  {
    "alias": "express.feature.enabled",
    "description": "Feature toggle",
    "defaultValue": false,
    "conditions": [
      {
        "value": true,
        "conditionType": {
          "onValue": "3",
          "type": "Equals"
        },
        "paramName": "country_code"
      }
    ]
  }
]

Step 3: Configure application to read baigan configurations (created in Step 2)

Setup Baigan Spring configurations

Application.java

@ComponentScan(basePackageClasses = { BaiganSpringContext.class })
@ConfigurationServiceScan(basePackages = { "com.foo.configurations" })
public class Application {

    @Bean
    public ConfigurationRepository configurationRepository(RepositoryFactory factory) {
        return factory.fileSystemConfigurationRepository()
                      .fileName("configs.json")
                      .build();
    }

}
  • @ComponentScan(basePackageClasses = { BaiganSpringContext.class }) loads BaiganSpringContext, which, in turn, loads all the necessary Spring beans required by baigan.

  • @ConfigurationServiceScan(basePackages = { "com.foo.configurations" }) scans the provided packages to check for baigan-config interfaces (explained below). Baigan uses these interfaces to parse simple JSON configurations into concrete types. In this example, baigan will scan the com.foo.configurations package to identify baigan-config interfaces.

  • You may consider enriching the repository builder with the Object mapper, In some cases you may face deserialization issues (e.g., Cannot construct instance of clazz (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)).

@ComponentScan(basePackageClasses = { BaiganSpringContext.class })
@ConfigurationServiceScan(basePackages = { "com.foo.configurations" })
public class Application {

    @Bean
    public ConfigurationRepository configurationRepository(RepositoryFactory factory, ObjectMapper objectMapper) {
        return factory.fileSystemConfigurationRepository()
                      .fileName("configs.json")
                      .objectMapper(objectMapper)
                      .build();
    }

}

Define baigan-config interface

ExpressFeature.java

@BaiganConfig
public interface ExpressFeature {
    Boolean enabled();
}

Baigan follows structured configurations, where JSON configurations are automatically parsed into a specific type. @BaiganConfigannotation serves as a hint to Baigan to map simple JSON configurations to a specific type in the client's application. Configuration interfaces should adhere to specific conventions.

  • The interface should be annotated with @BaiganConfig.
  • The interface name and the interface method should match the key name of the JSON configuration. For example, to map a express.feature.enabled key, Baigan takes the interface name ExpressFeature and the interface method enabled to map the value.

Note: Primitives are not supported as return types, as they cannot be null and therefore cannot express a missing configuration value.

Caution

Primitives are not supported as return types as they cannot be null and therefore cannot express a missing configuration value. If you use Baigan with Kotlin, it means you need to use nullable primitive types, e.g. Int? instead of Int.

Step 4: Use the configurations

@Component
public class ExpressServiceImpl implements ExpressService {

    @Inject
    private ExpressFeature expressFeature;

    @Override
    public void sendShipment(final Shipment shipment) {
        if (expressFeature.enabled()) {
            final String serviceUrl = expressFeature.serviceUrl();
            // Use the configuration
        }
    }
}

Caution

Due to the way bean access is managed, concurrent use of Baigan's proxies from multiple threads during the early stages of Spring context initialization can result in concurrency issues, including the potential for deadlock. To mitigate this risk, it is advisable to refrain from accessing Baigan's proxies until the Spring context has been initialized.