Quality: Silver Tier.
Easy
is the userver library for easy prototyping. Service functionality is described in code in a short and declarative way. Static configs and database schema are embedded into the binary to simplify deployment and are applied automatically at service start.
Migration of a service on easy library to a more functional pg_service_template is straightforward.
Hello world
service with the easy libraryLet's write a service that responds Hello world
to any request by URL /hello
. To do that, just describe the handler in main.cpp
file:
Build it with a trivial CMakeLists.txt
:
Note the userver_testsuite_add_simple(DUMP_CONFIG True)
usage. It automatically adds the testsuite
directory and tells the testsuite to retrieve static config from the binary itself, so that the new service can be easily tested from python. Just add a testsuite/conftest.py
file:
And put the tests in any other file, for example testsuite/test_basic.py
:
Tests can be run in a usual way, for example
The easy library works well with any callables, so feel free to move the logic out of lambdas to functions or functional objects:
Key-Value storage
service with the easy libraryLet's write a service that stores a key if it was HTTP POSTed by URL /kv
with URL arguments key
and value
. For example HTTP POST /kv?key=the_key&value=the_value
store the the_value
by key the_key
. To retrieve a key, an HTTP GET request for the URL /kv?key=KEY
is done.
For this service we will need a database. To add a database to the service an easy::PgDep dependency should be added to the easy::HttpWith. After that, the dependency can be retrieved in the handlers:
Note the easy::HttpWith::DbSchema usage. PostgreSQL database requires schema, so it is provided in place.
To test the service we should instruct the testsuite to retrieve the schema from the service. Content of testsuite/conftest.py
file is:
After that tests can be written in a usual way, for example testsuite/test_basic.py
:
As you may noticed from a previous example, passing arguments in an URL may not be comfortable, especially when the request is complex. In this example, let's change the previous service to accept JSON request and answers with JSON, and force the keys to be integers.
If the function for a path accepts formats::json::Value the easy library attempts to automatically parse the request as JSON. If the function returns formats::json::Value, then the content type is automatically set to application/json
:
Note the request_json.As<schemas::KeyRequest>()
usage. This example uses JSON schema codegen - the Chaotic to generate the parsers and serializers via CMakeLists.txt
:
Content of testsuite/conftest.py
file did not change, the testsuite/test_basic.py
now uses JSON:
When the logic of your service becomes complicated and big it is a natural desire to move parts of the logic into a separate entities. Component system is the solution for userver based services.
Consider the example, where an HTTP client to a remote service is converted to a component:
To use that component with the easy library a dependency type should be written, that has a constructor from a single const components::ComponentContext&
parameter to retrieve the clients/components and has a static void RegisterOn(easy::HttpBase& app)
member function, to register the required component and configs in the component list.
Multiple dependencies can be used with easy::HttpWith in the following way:
See the full example, including tests:
At some point a prototype service becomes a production ready solution. From that point usually more control over databases, configurations and deployment is required than the easy library provides. In that case, migration to an opensourse service template is recommended.
Let's take a look, how a service from a previous example can be migrated to the pg_service_template.
First of all, follow the instructions at service template to make a new service from a template.
To get the up to date static configs from the easy library, just build the service and run it with --dump-config
and the binary will output the config to STDOUT
. You can also provide a path to dump the static config, for example ./your_prototype --dump-config pg_service_template_based_service/configs/static_config.yaml
.
If there's any userver_testsuite_add_simple(DUMP_CONFIG True)
in a CMakeLists.txt
then do not forget to remove DUMP_CONFIG True
to stop the testsuite from taking the static configuration from the binary.
To get the up to date database schemas from an easy library, just build the binary and run it with --dump-db-schema
and the binary will output the database schema to STDOUT
. You can also provide a path to dump the schema, for example ./your_prototype --dump-db-schema pg_service_template_based_service/postgresql/schemas/db_1.sql
.
Another option is to take the schema from easy::HttpWith::DbSchema(). In any case, do not forget to remove the DbSchema call and do not forget to remove schema from source code.
If there's any pgsql_local
customizations in testsuite tests, then remove db_dump_schema_path
fixture usage and use the default version of the fixture from the service template. Note the postgresql/data
directory in the service template, that is a good place to store tests data separately from the schema.
You can keep using parts of the easy library in the service template for quite some time. Just move the code to new locations.
As a result you should get something close to the following:
After that, if you fell that easy::HttpWith gets in the way then you can remove it while still using easy::Dependencies. For example the following code with easy::HttpWith:
Becomes: