Microservices in C++. Fiction or reality?

Microservices in C++. Fiction or reality?

In this article, I will explain how I created a template (cookiecutter) and set up the environment to write a REST API service in C++ using Docker/Docker Compose and the Conan package manager.

During a recent hackathon, where I participated as a backend developer, the question arose about what to use for writing the next microservice. All that had been written up to that moment was done by me and my friend in Python, as my colleague was a specialist in this area and was professionally engaged in backend development, while I was generally a developer for embedded systems and wrote in the great and terrible C++, having only slightly learned Python at university.

So, we faced the task of writing a high-load service, whose main task was to preprocess incoming data and write it to a database. After another break, my friend suggested that I, as a C++ developer, write this service in C++. He argued that it would be faster, more efficient, and that the jury would be impressed with how we utilized the team's resources. To which I replied that I had never done such things in C++ and could easily spend the remaining 20+ hours searching for, compiling, and assembling suitable libraries. In simpler terms, I got scared. So we decided to finish everything in Python.

Now, during the forced self-isolation, I decided to figure out how to write services in C++. The first thing I needed to do was choose a suitable library. I settled on POCO, as it was written in an object-oriented style and also boasted good documentation. Additionally, there was the question of selecting a build system. Until that moment, I had only worked with Visual Studio, IAR, and ‘bare’ makefiles. None of these systems appealed to me, as I planned to run the entire service in a Docker container. So, I decided to try to understand CMake and an interesting package manager called Conan. This package manager allowed me to specify all 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 necessary libraries. Naturally, modifications also had to be made to the

CMakeLists.txt

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

After that, I started looking for a library to work with PostgreSQL, as I had some experience with it, and it was also used by our services in Python. And do you know what I found out? It is available in POCO! But conan does not know that it exists in POCO and cannot build it; there is an outdated configuration file in the repository (I have already notified the POCO creators about this mistake). So, I will have to look for another library.

And then I chose a less popular library libpg. And I was incredibly lucky, it was already in conan and was even built and linked.

The next step was to write a service template that can handle requests.
We need to inherit our class TemplateServerApp 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 &)
{
    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 need to set the parameters: port, number of threads, and queue size. And most importantly, we need to specify the incoming request handler. 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 simply creates the same handler each 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. To execute a simple SQL command, such as creating a table, there is a method ExecuteSQL(). For more complex queries or data retrieval, you will need to obtain a connection through GetConnection() and use the libpg API. (I may fix this oversight 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 will also need to create and set up a .env file

.env

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

You can view the entire code on GitHub.

Microservices in C++. Fiction or reality?

And the final stage was writing the dockerfile and docker-compose.yml. To be honest, this took a significant amount of time, and not just because I am a newbie who had to rebuild libraries each time, but due to the pitfalls of conan. For example, for conan to download, install, and build the required dependencies, it is not enough to just run "conan install ."; you also need to pass the parameter -s compiler.libcxx=libstdc++11, otherwise you risk getting a bunch of errors at the linking stage of your application. I spent several hours with this error, and I hope this article helps others solve this issue more quickly.

Next, after writing the docker-compose.yml, at the suggestion of a friend, I added support for cookiecutter and now you can easily get a complete template for a REST API service in C++, with a configured environment and PostgreSQL running, simply by entering "cookiecutter https://github.com/KovalevVasiliy/cpp_rest_api_template.git" in the console. Then, run "docker-compose up —build".

I hope this template assists newcomers on their challenging journey of developing REST API applications in the great and powerful, yet somewhat unwieldy, language of C++.
Also, I highly recommend reading this this article. It explains in more detail how to work with POCO and create your own REST API service.

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster