Microservices en C++. Fiction ou réalité ?

Microservices en C++. Fiction ou réalité ?

Dans cet article, je vais parler de la façon dont j'ai créé un template (cookiecutter) et mis en place un environnement pour écrire un service API REST en C++ en utilisant docker/docker-compose et le gestionnaire de package conan.

Lors du prochain hackathon, auquel j'ai participé en tant que développeur back-end, la question s'est posée de savoir comment écrire le prochain microservice. Tout ce qui a été écrit à ce jour a été écrit par moi et mon compagnon dans le langage Python, puisque mon collègue était un spécialiste dans ce domaine et développait professionnellement des backends, alors que j'étais généralement un développeur de systèmes embarqués et écrivais dans le grand et terrible C++, et que Python venait d'apprendre à l'université.

Nous étions donc confrontés à la tâche d'écrire un service très chargé, dont la tâche principale était de prétraiter les données qui lui parvenaient et de les écrire dans la base de données. Et après une autre pause cigarette, un ami m'a suggéré, en tant que développeur C ++, d'écrire ce service sur les avantages. Arguant que ce sera plus rapide, plus productif, et en général, le jury sera ravi de la façon dont nous savons gérer les ressources de l'équipe. À quoi j'ai répondu que je n'avais jamais fait de telles choses en C ++ et que je pouvais facilement consacrer les 20 heures restantes à trouver, compiler et lier des bibliothèques appropriées. Bref, j'ai dégonflé. Là-dessus, ils ont décidé et ont calmement tout ajouté en Python.

Maintenant, pendant l'auto-isolement forcé, j'ai décidé de comprendre comment écrire des services en C ++. La première chose à faire était de choisir une bibliothèque appropriée. Mon choix s'est porté sur POCO, puisqu'il a été écrit dans un style orienté objet et qu'il disposait également d'une documentation normale. De plus, la question s'est posée du choix du système de construction. Jusqu'à présent, je n'ai travaillé qu'avec Visual Studio, IAR et des makefiles nus. Et aucun de ces systèmes ne m'a plu, puisque j'avais prévu d'exécuter l'intégralité du service dans un conteneur docker. Ensuite, j'ai décidé d'essayer de gérer cmake et un gestionnaire de paquets intéressant conan. Ce gestionnaire de paquets vous a permis d'écrire toutes les dépendances dans un seul fichier

conanfile.txt
[nécessite]poco/1.9.3
libpq/11.5

[générateurs] cmake

et avec une simple commande "conan install ." installez les bibliothèques requises. Naturellement, il était également nécessaire d'apporter des modifications à

CMakeLists.txt

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

Après cela, j'ai commencé à chercher une bibliothèque pour travailler avec PostgreSQL, car c'est avec elle que j'avais peu d'expérience, et c'est aussi avec elle que nos services Python interagissaient. Et tu sais ce que j'ai découvert ? Elle est dans POCO! Mais conan ne sait pas qu'il est dans POCO et ne sait pas comment le construire, le référentiel contient un fichier de configuration obsolète (j'ai déjà écrit à propos de cette erreur aux créateurs de POCO). Donc, vous devez chercher une autre bibliothèque.

Et puis mon choix s'est porté sur une bibliothèque moins populaire libpg. Et j'ai eu une chance incroyable, elle était déjà dans Conan et même montée et montée.

L'étape suivante consistait à écrire un modèle de service capable de traiter les demandes.
Nous devons dériver notre classe TemplateServerApp de Poco::Util::ServerApplication et remplacer la méthode main.

ModèleServeurApp

#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;
}

Dans la méthode principale, nous devons définir les paramètres : port, nombre de threads et taille de la file d'attente. Et surtout, vous devez définir le gestionnaire pour les demandes entrantes. Cela se fait en créant une usine

TemplateRequestHandlerFactory

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

Dans mon cas, il crée simplement le même gestionnaire à chaque fois - TemplateServerAppHandler. C'est là que nous pouvons placer notre logique métier.

Gestionnaire de modèles de serveurs d'applications

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();
    }
};

J'ai également créé un modèle de classe pour travailler avec PostgreSQL. Afin d'effectuer des requêtes SQL simples, telles que la création d'une table, il existe une méthode ExécuterSQL(). Pour des requêtes plus complexes ou pour obtenir des données, vous devrez obtenir la connexion via ObtenirConnexion() et utilisez l'API libpg. (Peut-être que je corrigerai cette injustice plus tard).

Base de données

#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;
};

Tous les paramètres de connexion à la base de données sont extraits de l'environnement, vous devez donc également créer et configurer le fichier .env

.env

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

Vous pouvez voir tout le code sur github.

Microservices en C++. Fiction ou réalité ?

Et la dernière étape de l'écriture du dockerfile et du docker-compose.yml est arrivée. Pour être honnête, cela a pris la plupart du temps, et pas seulement parce que je suis un noob, qu'il fallait reconstruire les bibliothèques à chaque fois, mais à cause des pièges de conan. Ainsi par exemple, pour que conan télécharge, installe et construise les dépendances nécessaires, il ne lui suffit pas de télécharger "conan install.", il doit aussi passer le paramètre -s compiler.libcxx=libstdc++11, sinon, vous risquez d'obtenir un tas d'erreurs lors de l'étape de liaison de votre application. Je suis bloqué avec cette erreur depuis plusieurs heures et j'espère que cet article aidera d'autres personnes à résoudre ce problème plus rapidement.

De plus, après avoir écrit docker-compose.yml, sur les conseils de mon ami, j'ai ajouté le support pour cuisinier et maintenant vous pouvez vous procurer un modèle à part entière pour un service API REST en C ++, avec un environnement configuré et un PostgreSQL surélevé, simplement en entrant «cookiecutter» dans la console https://github.com/KovalevVasiliy/cpp_rest_api_template.git". Et puis "docker-compose up --build".

J'espère que ce modèle aidera les débutants sur leur chemin difficile de développement d'applications API REST dans un langage génial et puissant, mais aussi maladroit que C ++.
Aussi, je recommande fortement de lire ici cette article. Il explique plus en détail comment travailler avec POCO et écrire votre propre service API REST.

Source: habr.com

Ajouter un commentaire