Project: userver (App Definition and Development / Streaming & Messaging)
File: config_service.md

Make sure that you can compile and run core tests and read a basic example @ref scripts/docs/en/userver/tutorial/hello_service.md. An ability to change service behavior at runtime without restarting the service is priceless! We have that ability, it is called dynamic configs and it allows you: to control logging (@ref USERVERNOLOGSPANS, @ref USERVERLOGREQUEST, @ref USERVERLOGREQUESTHEADERS) to control RPS and deal with high loads (@ref HTTPCLIENTCONNECTTHROTTLE, @ref USERVERRPSCCONTROL, @ref USERVERTASKPROCESSORQOS) to dynamically switch from one HTTP proxy to another or turn off proxying (@ref USERVERHTTPPROXY) to write your own runtime dynamic configs: to create experiments that could be adjusted or turned on/off without service restarts to do whatever you like In previous example we made a simple HTTP server with some dynamic configs set in stone. To make the dynamic configs dynamic for real the following steps should be done: components::DynamicConfigClientUpdater and components::DynamicConfigClient components should be added to the service component list the above components should be configured to retrieve dynamic configs from the configuration service: ``` dynamic-config-client: # A client that knows how to request configs via HTTP config-url: http://localhost:8083/ # URL of dynamic config service http-retries: 5 http-timeout: 20s service-name: configs-service fallback-to-no-proxy: false # On error do not attempt to retrieve configs dynamic-config-client-updater: # A component that periodically uses `dynamic-config-client` to retrieve new values update-interval: 5s # Request for new configs every 5 seconds full-update-interval: 1m config-settings: false first-update-fail-ok: true ``` Now let's create a configuration service. Dynamic configs are requested via JSON requests, so we need to create a simple JSON handler that is responding with config values. There are two ways to write a JSON handler: We could do that by creating a component derived from server::handlers::HttpHandlerBase as in the @ref scripts/docs/en/userver/tutorial/hello_service.md example. In that case we would need to parse the input via formats::json::FromString and to send the result by serializing a JSON to std::string via formats::json::ToString. Or we could just take a server::handlers::HttpHandlerJsonBase that does all the above steps for us. We are going to take the second approach: @snippet samples/configservice/configservice.cpp Config service sample - component @warning `Handle` functions are invoked concurrently on the same instance of the handler class. Use @ref scripts/docs/en/userver/synchronization.md "synchronization primitives" or do not modify shared data in `Handle`. Note the rcu::Variable. There may be (and will be!) multiple concurrent requests and the `HandleRequestJsonThrow` would be invoked concurrently on the same instance of `ConfigDistributor`. The rcu::Variable allows us to atomically update the config value, concurrently with the `HandleRequestJsonThrow`

Function `ConfigDistributor::SetNewValues` is meant for setting config values to send. For example, you can write another component that accepts JSON requests with new config values and puts those values into `ConfigDistributor` via `SetNewValues(new_values)`. All the interesting things happen in the `HandleRequestJsonThrow` function, where we grab a rcu::Variable snapshot, fill the update time and the configuration from it: @snippet samples/configservice/configservice.cpp Config service sample - HandleRequestJsonThrow The "configs" field is formed in the `MakeConfigs` function depending on the request parameters: @snippet samples/configservice/configservice.cpp Config service sample - MakeConfigs Note that the service name is sent in the "service" field of the JSON request body. Using it you can make service specific dynamic configs. Now we have to configure our new HTTP handle. The configuration is quite straightforward: @snippet samples/configservice/staticconfig.yaml Config service sample - handler static config Finally, we add required components to the `components::MinimalServerComponentList()`, and start the server with static config `kStaticConfig`. @snippet samples/configservice/configservice.cpp Config service sample - main To build the sample, execute the following build steps at the userver root directory: ``` bash mkdir build_release cd build_release cmake -DCMAKEBUILDTYPE=Release .. make userver-samples-config_service ``` The sample could be started by running `make start-userver-samples-config_service`. The command would invoke @ref scripts/docs/en/userver/functional_testing.md "testsuite start target" that sets proper paths in the configuration files and starts the service. To start the service manually run `./samples/configservice/userver-samples-configservice -c </path/to/static_config.yaml>`. Now you can send a request to your server from another terminal: ``` bash $ curl -X POST -d '{}' 127.0.0.1:8083/configs/values | jq { "configs": { "USERVER_DUMPS": {}, "USERVERLRUCACHES": {}, "USERVER_CACHES": {}, "USERVERLOGREQUEST": true, "USERVERTASKPROCESSOR_QOS": { "default-service": { "default-task-processor": { "waitqueueoverload": { "action": "ignore", "length_limit": 5000, "timelimitus": 3000 } } } }, "USERVERTASKPROCESSORPROFILERDEBUG": {}, "USERVERLOGREQUEST_HEADERS": true, "USERVERCANCELHANDLEREQUESTBY_DEADLINE": false, "USERVERHTTPPROXY": "" }, "updated_at": "2021-06-29T14:15:31.173239295+0000" } $ curl -X POST -d '{"ids":["USERVERTASKPROCESSOR_QOS"]}' 127.0.0.1:8083/configs/values | jq { "configs": { "USERVERTASKPROCESSOR_QOS": { "default-service": { "default-task-processor": { "waitqueueoverload": { "action": "ignore", "length_limit": 5000, "timelimitus": 3000 } } } } }, "updated_at": "2021-06-29T14:15:31.173239295+0000" } ``` @ref scripts/docs/en/userver/functional_testing.md "Functional tests" for the service could be implemented using the @ref serviceclient "serviceclient fixture" in the following way: @snippet samples/configservice/tests/testconfig.py Functional test Do not forget to add the plugin in conftest.py: @snippet samples/config_service/tests/conftest.py registration Note that there is a ready to use opensource dynamic configs service. Use it for your projects or just disable dynamic config updates and keep developing without a supplementary service. See the full example: @ref samples/configservice/configservice.cpp @ref samples/configservice/staticconfig.yaml @ref samples/config_service/CMakeLists.txt @ref samples/config_service/tests/conftest.py @ref samples/configservice/tests/testconfig.py @htmlonly <div class="bottom-nav"> @endhtmlonly @ref scripts/docs/en/userver/tutorial/helloservice.md | @ref scripts/docs/en/userver/tutorial/productionservice.md @htmlonly </div> @endhtmlonly @example samples/configservice/configservice.cpp @example samples/configservice/staticconfig.yaml @example samples/config_service/CMakeLists.txt @example samples/config_service/tests/conftest.py @example samples/configservice/tests/testconfig.py