Статьи

Ratpacked: внешняя конфигурация приложения

Ratpack имеет очень полезные методы для применения конфигурации приложения в нашем приложении. Мы можем читать свойства конфигурации из файлов в разных форматах, таких как свойства JSON, YAML и Java, а файлы можно читать из разных мест, таких как путь к классам или файловая система. Мы также можем установить свойства конфигурации через системные свойства Java в командной строке или использовать переменные среды.

Мы используем ratpack.config.ConfigDataкласс со статическим ofметодом для добавления свойств конфигурации в наше приложение. Мы предоставляем лямбда-выражение или Groovy-закрытие для ofметода для построения нашей конфигурации. Здесь мы указываем внешние файлы, местоположения и другие параметры конфигурации, которые мы хотим включить для нашего приложения. Если одно и то же свойство конфигурации определено в нескольких источниках конфигурации, Ratpack применит последнее обнаруженное значение. Таким образом, мы можем, например, предоставить значения по умолчанию и разрешить их переопределять переменными среды, если мы применяем переменные среды в последнюю очередь.

Чтобы использовать собранные значения, мы используем getметод ConfigDataэкземпляра. Мы можем применить свойства конфигурации к свойствам класса конфигурации, который затем автоматически создается. Мы добавляем это в реестр, чтобы мы могли использовать свойства конфигурации ниже в нашем приложении.

В следующем примере приложения Ratpack мы используем различные способы применения свойств конфигурации. Он вдохновлен тем, как Spring Boot читает и применяет внешние параметры конфигурации. Сначала мы используем простое Mapсо значениями по умолчанию, затем путь к классу сканируется на наличие файлов с именами application.yml, application.jsonа application.propertiesтакже в корне пути к классам или configпакета. Затем в файловой системе ищутся те же имена файлов относительно того места, где запущено приложение. Следующие свойства системы Java, начиная с sample., применяются к конфигурации. И, наконец, переменные среды, начинающиеся с SAMPLE_, интерпретируются как свойства конфигурации.

Давайте начнем с простого класса конфигурации и свойств, которые мы хотим установить с помощью возможности конфигурации Ratpack:

// File: src/main/groovy/com/mrhaki/SampleConfig.groovy
package com.mrhaki

/**
 * Configuration properties for our application.
 */
class SampleConfig {

    /**
     * URL for external service to invoke with HTTP client.
     */
    String externalServiceUrl

    /**
     * URI to access the Mongo database.
     */
    String mongoUri

    /**
     * Indicate if we need to use a HTTP proxy.
     */
    boolean useProxy

    /**
     * Simple message
     */
    String message

}

Далее у нас есть очень простое приложение Ratpack. Здесь мы используем ConfigData.ofмножество вспомогательных методов для чтения свойств конфигурации из разных источников:

// File: src/ratpack/Ratpack.groovy
import com.google.common.io.Resources
import com.mrhaki.SampleConfig
import ratpack.config.ConfigData
import ratpack.config.ConfigDataBuilder

import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson
import static ratpack.groovy.Groovy.ratpack

ratpack {

    bindings {
        final ConfigData configData = ConfigData.of { builder ->
            // Set default value, can be overridden by
            // configuration further down the chain.
            // The map must have String values.
            builder.props(['app.useProxy': Boolean.TRUE.toString()])

            loadExternalConfiguration(builder)

            // Look for system properties starting with
            // sample. to set or override configuration properties.
            builder.sysProps('sample.')

            // Look for environment variables starting
            // with SAMPLE_ to set or override configuration properties.
            builder.env('SAMPLE_')

            builder.build()
        }

        // Assign all configuration properties from the /app node
        // to the properties in the SampleConfig class.
        bindInstance(SampleConfig, configData.get('/app', SampleConfig))
    }

    handlers {
        get('configprops') { SampleConfig config ->
            render(prettyPrint(toJson(config)))
        }
    }

}

private void loadExternalConfiguration(final ConfigDataBuilder configDataBuilder) {

    final List<String> configurationLocations =
            ['application.yml',
             'application.json',
             'application.properties',
             'config/application.yml',
             'config/application.json',
             'config/application.properties']

    configurationLocations.each { configurationLocation ->
        loadClasspathConfiguration(configDataBuilder, configurationLocation)
    }

    configurationLocations.each { configurationLocation ->
        loadFileSystemConfiguration(configDataBuilder, configurationLocation)
    }
}

private void loadClasspathConfiguration(
        final ConfigDataBuilder configDataBuilder,
        final String configurationName) {

    try {
        final URL configurationResource = Resources.getResource(configurationName)
        switch (configurationName) {
            case yaml():
                configDataBuilder.yaml(configurationResource)
                break
            case json():
                configDataBuilder.json(configurationResource)
                break
            case properties():
                configDataBuilder.props(configurationResource)
                break
            default:
                break
        }
    } catch (IllegalArgumentException ignore) {
        // Configuration not found.
    }

}

private void loadFileSystemConfiguration(
        final ConfigDataBuilder configDataBuilder,
        final String configurationFilename) {

    final Path configurationPath = Paths.get(configurationFilename)
    if (Files.exists(configurationPath)) {
        switch (configurationFilename) {
            case yaml():
                configDataBuilder.yaml(configurationPath)
                break
            case json():
                configDataBuilder.json(configurationPath)
                break
            case properties():
                configDataBuilder.props(configurationPath)
                break
            default:
                break
        }
    }
}

private def yaml() {
    return hasExtension('yml')
}

private def json() {
    return hasExtension('json')
}

private def properties() {
    return hasExtension('properties')
}

private def hasExtension(final String extension) {
    return { filename -> filename ==~ /.*\.${extension}$/ }
}

Далее мы создаем несколько внешних файлов конфигурации:

# File: src/ratpack/application.yml
---
app:
  mongoUri: mongodb://mongo:27017/test
# File: src/ratpack/application.properties
app.externalServiceUrl = http://remote:9000/api
app.message = Ratpack rules!

Давайте запустим приложение и посмотрим вывод configpropsконечной точки:

$ http localhost:5050/configprops
...
{
    "externalServiceUrl": "http://remote:9000/api",
    "useProxy": true,
    "message": "Ratpack rules!",
    "mongoUri": "mongodb://mongo:27017/test"
}

Затем мы останавливаем приложение и запускаем его с системным свойством Java -Dsample.app.useProxy=falseи переменной среды SAMPLE_APP__MESSAGE='Ratpack rocks!'. Мы configpropsснова проверяем конечную точку:

$ http localhost:5050/configprops
...
{
    "externalServiceUrl": "http://remote:9000/api",
    "useProxy": false,
    "message": "Ratpack rocks!",
    "mongoUri": "mongodb://mongo:27017/test"
}

Написано с помощью Ratpack 1.0.0.

Это тысячный пост в блоге Сообщения от mrhaki. Я писал в блоге на разные темы, начиная с Apache Cocoon, Netbeans, затем следуя технологиям Groovy и Groovy, таким как Grails, Gradle. Также о других темах разработчиков, таких как Asciidoctor и многое другое. Я надеюсь, вам понравилась первая тысяча, потому что я еще не закончил и буду продолжать писать в блоге о великолепных технологиях.