Microservices in C++. Fiktion oder Realität?

Microservices in C++. Fiktion oder Realität?

In diesem Artikel werde ich darüber sprechen, wie ich eine Vorlage (Cookiecutter) erstellt und eine Umgebung zum Schreiben eines REST-API-Dienstes in C++ mithilfe von Docker/Docker-Compose und dem Conan-Paketmanager eingerichtet habe.

Beim nächsten Hackathon, an dem ich als Backend-Entwickler teilnahm, stellte sich die Frage, womit man den nächsten Microservice schreiben sollte. Alles, was bisher geschrieben wurde, wurde von mir und mir geschrieben Kumpel in Python, da mein Kollege ein Experte auf diesem Gebiet war und professionell Backends entwickelte, während ich im Allgemeinen ein Entwickler eingebetteter Systeme war und im großartigen und schrecklichen C++ schrieb und Python gerade an der Universität gelernt habe.

Wir standen also vor der Aufgabe, einen Hochlastdienst zu schreiben, dessen Hauptaufgabe darin bestand, die eingehenden Daten vorzuverarbeiten und in die Datenbank zu schreiben. Und nach einer weiteren Rauchpause schlug mir ein Freund als C++-Entwickler vor, diesen Dienst mit den Profis zu schreiben. Dies wird dadurch begründet, dass es schneller und produktiver sein wird und die Jury im Allgemeinen davon begeistert sein wird, wie wir die Ressourcen des Teams verwalten können. Darauf antwortete ich, dass ich solche Dinge noch nie in C++ gemacht hätte und die verbleibenden mehr als 20 Stunden problemlos dem Suchen, Kompilieren und Verknüpfen geeigneter Bibliotheken widmen könnte. Einfach gesagt, ich habe mich vertan. Dafür haben wir uns entschieden und in aller Ruhe alles in Python fertiggestellt.

Jetzt, während der erzwungenen Selbstisolation, beschloss ich, herauszufinden, wie man Dienste in C++ schreibt. Als erstes galt es, sich für eine geeignete Bibliothek zu entscheiden. Meine Wahl fiel auf POCO, da es in einem objektorientierten Stil geschrieben war und auch über eine normale Dokumentation verfügte. Außerdem stellte sich die Frage nach der Wahl eines Montagesystems. Bisher habe ich nur mit Visual Studio, IAR und Bare-Makefiles gearbeitet. Und keines dieser Systeme gefiel mir, da ich vorhatte, den gesamten Dienst in einem Docker-Container auszuführen. Dann beschloss ich, cmake und einen interessanten Paketmanager herauszufinden conan. Mit diesem Paketmanager konnten Sie alle Abhängigkeiten in einer Datei registrieren

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

[Generatoren] cmake

und mit einem einfachen Befehl „conan install“. Installieren Sie die erforderlichen Bibliotheken. Natürlich mussten auch Änderungen vorgenommen werden

CMakeLists.txt

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

Danach begann ich, nach einer Bibliothek für die Arbeit mit PostgreSQL zu suchen, da ich mit dieser nur wenig Erfahrung hatte und mit ihr auch unsere Python-Dienste interagierten. Und wissen Sie, was ich gelernt habe? Es ist in POCO! Aber Conan weiß nicht, dass es sich in POCO befindet und weiß nicht, wie man es erstellt; es gibt eine veraltete Konfigurationsdatei im Repository (ich habe den Erstellern von POCO bereits über diesen Fehler geschrieben). Das bedeutet, dass Sie sich nach einer anderen Bibliothek umsehen müssen.

Und dann fiel meine Wahl auf eine weniger beliebte Bibliothek libpg. Und ich hatte unglaubliches Glück, es war bereits in Conan und wurde sogar zusammengebaut und zusammengebaut.

Der nächste Schritt bestand darin, eine Servicevorlage zu schreiben, die Anfragen verarbeiten kann.
Wir müssen unsere TemplateServerApp-Klasse von Poco::Util::ServerApplication erben und die Hauptmethode überschreiben.

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 der Hauptmethode müssen wir die Parameter festlegen: Port, Anzahl der Threads und Warteschlangengröße. Und am wichtigsten ist, dass Sie einen Handler für eingehende Anfragen angeben müssen. Dies geschieht durch die Schaffung einer Fabrik

TemplateRequestHandlerFactory

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

In meinem Fall wird einfach jedes Mal derselbe Handler erstellt – TemplateServerAppHandler. Hier können wir unsere Geschäftslogik platzieren.

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

Ich habe auch eine Klassenvorlage für die Arbeit mit PostgreSQL erstellt. Um einfaches SQL auszuführen, beispielsweise das Erstellen einer Tabelle, gibt es eine Methode ExecuteSQL(). Für komplexere Abfragen oder Datenabfragen müssen Sie eine Verbindung über herstellen GetConnection() und verwenden Sie die libpg-API. (Vielleicht werde ich diese Ungerechtigkeit später korrigieren).

Datenbase

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

Alle Parameter für die Verbindung zur Datenbank werden aus der Umgebung übernommen, daher müssen Sie auch die .env-Datei erstellen und konfigurieren

.env

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

Den gesamten Code finden Sie unter Github.

Microservices in C++. Fiktion oder Realität?

Und jetzt kommt die letzte Phase des Schreibens der Docker-Datei und der Docker-Compose.yml. Um ehrlich zu sein, hat das die meiste Zeit gedauert, und das nicht nur, weil ich ein Neuling bin, weil es jedes Mal notwendig war, die Bibliotheken neu aufzubauen, sondern auch wegen der Fallstricke von Conan. Damit Conan beispielsweise die erforderlichen Abhängigkeiten herunterladen, installieren und erstellen kann, reicht es nicht aus, „conan install“ herunterzuladen, andernfalls muss auch der Parameter -s Compiler.libcxx=libstdc++11 übergeben werden Sie riskieren, in der Verknüpfungsphase Ihrer Bewerbung eine Reihe von Fehlern zu erhalten. Dieser Fehler beschäftigt mich seit mehreren Stunden und ich hoffe, dass dieser Artikel anderen Leuten hilft, dieses Problem in kürzerer Zeit zu lösen.

Nachdem ich docker-compose.yml geschrieben hatte, fügte ich auf Anraten meines Freundes Unterstützung hinzu Ausstechform Und jetzt können Sie sich eine vollwertige Vorlage für einen REST-API-Dienst in C++ mit einer angepassten Umgebung und installiertem PostgreSQL besorgen, indem Sie einfach „cookiecutter“ in die Konsole eingeben https://github.com/KovalevVasiliy/cpp_rest_api_template.git" Und dann „Docker-Compose Up – Build“.

Ich hoffe, dass diese Vorlage Anfängern auf ihrem schwierigen Weg der Entwicklung von REST-API-Anwendungen in der großartigen und leistungsstarken, aber so schwerfälligen Sprache wie C++ hilft.
Außerdem empfehle ich dringend, hier zu lesen diese Artikel. Es erklärt ausführlicher, wie Sie mit POCO arbeiten und Ihren eigenen REST-API-Dienst schreiben.

Source: habr.com

Kommentar hinzufügen