Mikrotjenester i C++. Fiktion eller virkelighed?

Mikrotjenester i C++. Fiktion eller virkelighed?

I denne artikel vil jeg tale om, hvordan jeg oprettede en skabelon (cookiecutter) og opsatte et miljø til at skrive en REST API-tjeneste i C++ ved hjælp af docker/docker-compose og conan-pakkehåndteringen.

Under det næste hackathon, hvor jeg deltog som backend-udvikler, opstod spørgsmålet om, hvad man skulle bruge til at skrive den næste mikroservice. Alt hvad der er skrevet indtil nu er skrevet af mig og min kammerat i Python, da min kollega var ekspert på dette område og professionelt udviklede backends, mens jeg generelt var embedded systems-udvikler og skrev i det store og forfærdelige C++, og jeg har lige lært Python på universitetet.

Så vi stod over for opgaven med at skrive en højbelastningstjeneste, hvis hovedopgave var at forbehandle de data, der kom til den, og skrive dem til databasen. Og efter endnu en røgpause foreslog en ven, at jeg som C++-udvikler skulle skrive denne tjeneste ved hjælp af de professionelle. Det argumenteres for, at det vil være hurtigere, mere produktivt, og generelt vil juryen glæde sig over, hvordan vi ved, hvordan vi administrerer holdets ressourcer. Hvortil jeg svarede, at jeg aldrig havde gjort sådanne ting i C++ og sagtens kunne bruge de resterende 20+ timer på at søge, kompilere og linke passende biblioteker. Simpelthen sagde jeg chicken out. Det var det, vi besluttede os for og roligt gennemførte alt i Python.

Nu, under den tvungne selvisolering, besluttede jeg at finde ud af, hvordan man skriver tjenester i C++. Den første ting at gøre var at beslutte sig for et passende bibliotek. Mit valg faldt på POCO, da det var skrevet i en objektorienteret stil og også pralede af normal dokumentation. Spørgsmålet opstod også om valg af montagesystem. Indtil nu har jeg kun arbejdet med Visual Studio, IAR og bare make-filer. Og ingen af ​​disse systemer tiltalte mig, da jeg planlagde at køre hele tjenesten i en docker-container. Så besluttede jeg at prøve at finde ud af cmake og en interessant pakkehåndtering Conan. Denne pakkehåndtering tillod dig at registrere alle afhængigheder i én fil

conanfile.txt
[kræver]poco/1.9.3
libpq/11.5

[generatorer] cmake

og med en simpel kommando "conan install ." installere de nødvendige biblioteker. Det var naturligvis også nødvendigt at lave ændringer i

CMakeLists.txt

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

Derefter begyndte jeg at lede efter et bibliotek til at arbejde med PostgreSQL, da det var det, jeg havde lidt erfaring med at arbejde med, og det var også det, vores Python-tjenester interagerede med. Og ved du, hvad jeg har lært? Det er i POCO! Men conan ved ikke, at det er i POCO og ved ikke, hvordan man bygger det; der er en forældet konfigurationsfil i depotet (jeg har allerede skrevet om denne fejl til skaberne af POCO). Det betyder, at du bliver nødt til at lede efter et andet bibliotek.

Og så faldt mit valg på et mindre populært bibliotek libpg. Og jeg var utrolig heldig, den var allerede i conan og var endda ved at blive samlet og monteret.

Næste trin var at skrive en serviceskabelon, der kan behandle anmodninger.
Vi skal arve vores TemplateServerApp-klasse fra Poco::Util::ServerApplication og tilsidesætte hovedmetoden.

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

I hovedmetoden skal vi indstille parametrene: port, antal tråde og køstørrelse. Og vigtigst af alt, skal du angive en behandler for indgående anmodninger. Dette gøres ved at skabe en fabrik

TemplateRequestHandlerFactory

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

I mit tilfælde opretter det simpelthen den samme handler hver gang - TemplateServerAppHandler. Det er her, vi kan placere vores forretningslogik.

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

Jeg har også lavet en klasseskabelon til at arbejde med PostgreSQL. For at udføre simpel SQL, såsom oprettelse af en tabel, er der en metode ExecuteSQL(). For mere komplekse forespørgsler eller datahentning skal du opnå en forbindelse via GetConnection() og brug libpg API. (Måske senere vil jeg rette op på denne uretfærdighed).

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

Alle parametre for tilslutning til databasen er taget fra miljøet, så du skal også oprette og konfigurere .env-filen

.env

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

Du kan se hele koden på github.

Mikrotjenester i C++. Fiktion eller virkelighed?

Og nu kommer den sidste fase med at skrive dockerfilen og docker-compose.yml. For at være ærlig tog dette det meste af tiden, og ikke kun fordi jeg er en noob, fordi det var nødvendigt at genopbygge bibliotekerne hver gang, men på grund af conans faldgruber. For eksempel, for at conan kan downloade, installere og bygge de nødvendige afhængigheder, er det ikke nok for det at downloade “conan install .”, det skal også sende parameteren -s compiler.libcxx=libstdc++11, ellers du risikerer at få en masse fejl i forbindelse med din ansøgning. Jeg har siddet fast med denne fejl i flere timer, og jeg håber, at denne artikel vil hjælpe andre mennesker med at løse dette problem på kortere tid.

Dernæst, efter at have skrevet docker-compose.yml, på råd fra min ven, tilføjede jeg støtte kageskærer og nu kan du få dig en fuldgyldig skabelon til en REST API-tjeneste i C++, med et tilpasset miljø og PostgreSQL installeret, blot ved at indtaste "cookiecutter" i konsollen https://github.com/KovalevVasiliy/cpp_rest_api_template.git" Og så "docker-compose up -build".

Jeg håber, at denne skabelon vil hjælpe begyndere på deres vanskelige vej med at udvikle REST API-applikationer i det store og kraftfulde, men så klodsede sprog som C++.
Desuden kan jeg varmt anbefale at læse her dette artikel. Den forklarer mere detaljeret, hvordan du arbejder med POCO og skriver din egen REST API-tjeneste.

Kilde: www.habr.com

Tilføj en kommentar