Microservices in C++. Fiction or reality?

Microservices in C++. Fiction or reality?

In this article, I will talk about how I created a template (cookiecutter) and set up an environment for writing a REST API service in C ++ using docker / docker-compose and the conan package manager.

During the next hackathon, in which I participated as a back-end developer, the question arose of how to write the next microservice. Everything that has been written to date has been written by me and my companion in the Python language, since my colleague was a specialist in this area and professionally developed backends, while I was generally an embedded system developer and wrote in the great and terrible C ++, and Python just learned at the university.

So, we were faced with the task of writing a highly loaded service, the main task of which was to preprocess the data coming to it and write them to the database. And after another smoke break, a friend suggested that, as a C ++ developer, I write this service on the pluses. Arguing that it will be faster, more productive, and in general, the jury will be delighted with how we know how to manage the team's resources. To which I replied that I had never done such things in C ++ and could easily devote the remaining 20+ hours to finding, compiling and linking suitable libraries. Simply put, I chickened out. On that, they decided and calmly added everything in Python.

Now, during forced self-isolation, I decided to figure out how to write services in C ++. The first thing to do was to decide on a suitable library. My choice fell on POCO, since it was written in an object-oriented style, and also boasted normal documentation. Also, the question arose about the choice of the build system. Up to this point, I have only worked with Visual Studio, IAR, and bare makefiles. And none of these systems appealed to me, since I planned to run the entire service in a docker container. Then I decided to try to deal with cmake and an interesting package manager Conan. This package manager allowed you to write all the dependencies in one file

conanfile.txt
[requires]poco/1.9.3
libpq/11.5

[generators] cmake

and with a simple command "conan install ." install the required libraries. Naturally, it was also necessary to make changes in

CMakeLists.txt

include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries(<target_name> ${CONAN_LIBS})

After that, I started looking for a library for working with PostgreSQL, since it was with it that I had little experience, and it was also with it that our Python services interacted. And you know what I found out? She is in POCO! But conan does not know that it is in POCO and does not know how to build it, the repository contains an outdated configuration file (I already wrote about this error to the creators of POCO). So, you have to look for another library.

And then my choice fell on a less popular library libpg. And I was incredibly lucky, she was already in conan and even assembled and assembled.

The next step was to write a service template that can process requests.
We must derive our TemplateServerApp class from Poco::Util::ServerApplication and override the main method.

TemplateServerApp

#pragma once

#include <string>
#include <vector>
#include <Poco/Util/ServerApplication.h>

class TemplateServerApp : public Poco::Util::ServerApplication
{
    protected:
        int main(const std::vector<std::string> &);
};

int TemplateServerApp::main(const vector<string> &)
{
    HTTPServerParams* pParams = new HTTPServerParams;

    pParams->setMaxQueued(100);
    pParams->setMaxThreads(16);

    HTTPServer s(new TemplateRequestHandlerFactory, ServerSocket(8000), pParams);

    s.start();
    cerr << "Server started" << endl;

    waitForTerminationRequest();  // wait for CTRL-C or kill

    cerr << "Shutting down..." << endl;
    s.stop();

    return Application::EXIT_OK;
}

In the main method, we must set the parameters: port, number of threads, and queue size. And most importantly, you must set the handler for incoming requests. This is done by creating a factory

TemplateRequestHandlerFactory

class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
    virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
    {
        return new TemplateServerAppHandler;
    }
};

In my case, it just creates the same handler every time - TemplateServerAppHandler. This is where we can place our business logic.

TemplateServerAppHandler

class TemplateServerAppHandler : public HTTPRequestHandler
{
public:
    void handleRequest(HTTPServerRequest &req, HTTPServerResponse &resp)
    {
        URI uri(req.getURI());
        string method = req.getMethod();

        cerr << "URI: " << uri.toString() << endl;
        cerr << "Method: " << req.getMethod() << endl;

        StringTokenizer tokenizer(uri.getPath(), "/", StringTokenizer::TOK_TRIM);
        HTMLForm form(req,req.stream());

        if(!method.compare("POST"))
        {
            cerr << "POST" << endl;
        }
        else if(!method.compare("PUT"))
        {
            cerr << "PUT" << endl;
        }
        else if(!method.compare("DELETE"))
        {
            cerr << "DELETE" << endl;
        }

        resp.setStatus(HTTPResponse::HTTP_OK);
        resp.setContentType("application/json");
        ostream& out = resp.send();

        out << "{"hello":"heh"}" << endl;
        out.flush();
    }
};

I also created a class template for working with PostgreSQL. In order to perform simple SQL, such as creating a table, there is a method ExecuteSQL(). For more complex queries or getting data, you will have to get the connection via GetConnection() and use the libpg API. (Perhaps I will correct this injustice later).

Database

#pragma once

#include <memory>
#include <mutex>
#include <libpq-fe.h>

class Database
{
public:
    Database();
    std::shared_ptr<PGconn> GetConnection() const;
    bool ExecuteSQL(const std::string& sql);

private:
    void establish_connection();
    void LoadEnvVariables();

    std::string m_dbhost;
    int         m_dbport;
    std::string m_dbname;
    std::string m_dbuser;
    std::string m_dbpass;

    std::shared_ptr<PGconn>  m_connection;
};

All parameters for connecting to the database are taken from the environment, so you also need to create and configure the .env file

.env

DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432

You can see the whole code at github.

Microservices in C++. Fiction or reality?

And the last stage of writing the dockerfile and docker-compose.yml has come. To be honest, this took most of the time, and not only because I'm a noob, that it was necessary to rebuild the libraries every time, but because of the pitfalls of conan. So for example, in order for conan to download, install and build the necessary dependencies, it is not enough for him to download “conan install .”, he also needs to pass the -s compiler.libcxx=libstdc++11 parameter, otherwise you risk getting a bunch of errors at the linking stage your application. I've been stuck with this error for several hours, and I hope this article will help other people solve this problem in a shorter time.

Further, after writing docker-compose.yml, on the advice of my friend, I added support for cookiecutter and now you can get yourself a full-fledged template for a REST API service in C ++, with a configured environment, and a raised PostgreSQL, simply by entering “cookiecutter” into the console https://github.com/KovalevVasiliy/cpp_rest_api_template.git". And then "docker-compose up --build".

I hope this template will help beginners on their difficult path of developing REST API applications in the great and powerful, but such a clumsy language like C ++.
Also, I highly recommend reading here this article. It explains in more detail how to work with POCO and write your own REST API service.

Source: habr.com

Add a comment