Microservices that have state often work with database to store their data and replicate that state across instances of the microservice. In this tutorial we will write a service that is a simple key-value storage on top of Redis database. The service would have the following Rest API:
HTTP POST by path /v1/key-value with query parameters key and value stores the provided key and value or 409 Conflict if such key already exists
HTTP GET by path /v1/key-value with query parameter key returns the value if it exists or 404 Not Found if it is missing
HTTP DELETE by path /v1/key-value with query parameter key deletes the key if it exists and returns number of deleted keys (cannot be more than 1, since keys are unique in Redis database)
Note that the component holds a storages::redis::ClientPtr - a client to the Redis database. That client is thread safe, you can use it concurrently from different threads and tasks.
Initializing the database
To access the database from our new component we need to find the Redis component and request a client to a specific cluster by its name. After that we are ready to make requests.
In this sample we use a single handler to deal with all the HTTP methods. The KeyValue::HandleRequestThrow member function mostly dispatches the request to one of the member functions that actually implement the key-value storage logic:
Handle* functions are invoked concurrently on the same instance of the handler class. In this sample the KeyValue component only uses the thread safe DB client. In more complex cases synchronization primitives should be used or data must not be mutated.
KeyValue::GetValue
Executing a query to the Redis database is as simple as calling the corresponding method of storages::redis::ClientPtr.
Note that some methods return an optional result, which must be checked. Here it can indicate a missing key value.
All the values are described in a separate section Dynamic configs .
A production ready service would dynamically retrieve the above options at runtime from a configuration service. See Writing your own configs server for insights on how to change the above options on the fly, without restarting the service.
int main()
Finally, after writing down the dynamic config values into file at dynamic-config-fallbacks.fallback-path, we add our component to the components::MinimalServerComponentList(), and start the server with static config kStaticConfig.
To build the sample, execute the following build steps at the userver root directory:
mkdir build_release
cd build_release
cmake -DCMAKE_BUILD_TYPE=Release ..
make userver-samples-redis_service
The sample could be started by running make start-userver-samples-redis_service. The command would invoke testsuite start target that sets proper paths in the configuration files, prepares and starts the DB, and starts the service.
To start the service manually start the DB server and run ./samples/redis_service/userver-samples-redis_service -c </path/to/static_config.yaml> (do not forget to prepare the configuration files!).
Now you can send a request to your service from another terminal:
bash
$ curl -X POST 'localhost:8088/v1/key-value?key=hello&value=world' -i