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

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

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

Lors d'un hackathon auquel j'ai participé en tant que développeur back-end, la question s'est posée de savoir quelle technologie utiliser pour écrire le nouveau microservice. Tout ce qui avait été écrit jusqu'à présent l'avait été par moi et mon collègue en langage Python, car mon collègue était un spécialiste dans ce domaine et était professionnellement impliqué dans le développement de backends, tandis que j'étais plutôt développeur de systèmes embarqués et écrivais en C++ tout en ayant seulement appris un peu de Python à l'université.

Ainsi, nous avions pour mission d'écrire un service hautement charge, dont la tâche principale était le prétraitement des données entrantes et leur enregistrement dans une base de données. Après une pause, mon camarade m'a proposé, en tant que développeur C++, d'écrire ce service en C++. Il a argumenté que cela serait plus rapide, plus performant, et surtout, le jury serait impressionné par notre capacité à gérer les ressources de l'équipe. À quoi j'ai répondu que je n'avais jamais fait ce genre de choses en C++ et que je pouvais facilement consacrer les 20 heures restantes à chercher, compiler et lier des bibliothèques appropriées. En d'autres termes, j'ai eu peur. Nous avons donc décidé de tout terminer tranquillement en Python.

Maintenant, pendant cette période de confinement, j'ai décidé de m'attaquer à la question de l'écriture de services en C++. La première chose à faire était de choisir la bibliothèque appropriée. Mon choix s'est porté sur POCO, car elle était écrite dans un style orienté objet et disposait d'une documentation appropriée. De plus, la question du choix du système de construction s'est posée. Jusqu'à présent, je n'avais travaillé qu'avec Visual Studio, IAR et des makefiles « nus ». Aucun de ces systèmes ne me séduisait car je prévoyais d'exécuter le service entier dans un conteneur docker. J'ai donc décidé d'essayer de comprendre cmake et un gestionnaire de paquets intéressant, conan. Ce gestionnaire de paquets permettait de spécifier toutes les dépendances dans un seul fichier

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

[generators]
cmake

et grâce à une simple commande « conan install . », d'installer les bibliothèques nécessaires. Évidemment, il a également été nécessaire de modifier le

CMakeLists.txt

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

Après cela, j'ai commencé à chercher une bibliothèque pour travailler avec PostgreSQL, car j'avais un peu d'expérience avec elle, et nos services en Python interagissaient également avec. Et savez-vous ce que j'ai découvert ? Elle existe dans POCO ! Mais conan ne sait pas qu'elle existe dans POCO et ne sait pas comment la construire, le fichier de configuration dans le dépôt est obsolète (j'ai déjà signalé cette erreur aux créateurs de POCO). Donc, je devrai chercher une autre bibliothèque.

C'est alors que j'ai opté pour une bibliothèque moins populaire libpg. Et j'ai eu beaucoup de chance, elle était déjà dans conan et était même prête à être construite et liée.

La prochaine étape était d'écrire un modèle de service capable de traiter des requêtes.
Nous devons hériter de notre classe TemplateServerApp de Poco::Util::ServerApplication et redéfinir la méthode main.

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 << "Serveur démarré" << endl;

    waitForTerminationRequest();  // attendre CTRL-C ou kill

    cerr << "Arrêt en cours..." << endl;
    s.stop();

    return Application::EXIT_OK;
}

Dans la méthode main, nous devons définir les paramètres : le port, le nombre de threads et la taille de la file d'attente. Et surtout, nous devons définir le gestionnaire des requêtes entrantes. Cela se fait en créant une fabrique

TemplateRequestHandlerFactory

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

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

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 << "Méthode : " << 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. Pour exécuter une requête SQL simple, comme créer une table, il existe une méthode ExecuteSQL(). Pour des requêtes plus complexes ou pour obtenir des données, il faudra obtenir une connexion via GetConnection() et utiliser l'API libpg. (Je corrigerai peut-être 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 pour se connecter à la base de données sont extraits de l'environnement, donc vous devez également créer et configurer un fichier .env

.env

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

Vous pouvez consulter tout le code sur GitHub.

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

Et nous sommes arrivés à la dernière étape de l'écriture du dockerfile et du docker-compose.yml. Honnêtement, cela a pris une grande partie du temps, non seulement parce que je suis novice et qu'il fallait chaque fois recompiler les bibliothèques, mais à cause des pièges de conan. Par exemple, pour que conan télécharge, installe et construise les dépendances requises, il ne suffit pas de faire "conan install .", il faut aussi passer le paramètre -s compiler.libcxx=libstdc++11, sinon vous risquez de rencontrer de nombreuses erreurs lors de l'édition de votre application. J'ai lutté avec cette erreur pendant plusieurs heures, et j'espère que cet article aidera d'autres personnes à résoudre ce problème plus rapidement.

Ensuite, après avoir écrit le docker-compose.yml, sur les conseils d'un ami, j'ai ajouté le support cookiecutter et maintenant vous pouvez obtenir un véritable modèle pour un service REST API en C++, avec un environnement configuré et PostgreSQL en marche, simplement en entrant dans le terminal «cookiecutter https://github.com/KovalevVasiliy/cpp_rest_api_template.git». Puis «docker-compose up —build».

J'espère que ce modèle aidera les débutants dans leur chemin difficile de développement d'applications REST API dans ce grand et puissant, mais si peu flexible, langage qu'est C++.
De plus, je recommande fortement de lire cet cet article. Il y explique plus en détail comment travailler avec POCO et écrire son propre service REST API.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster