Mikrotjänster på C++. Fantasi eller verklighet?

Mikrotjänster på C++. Fantasi eller verklighet?

I den här artikeln kommer jag att berätta om hur jag skapade en mall (cookiecutter) och ställde in en miljö för att skriva en REST API-tjänst i C++ med hjälp av docker/docker-compose och paketförvaltaren conan.

Under en hackathon där jag deltog som backendutvecklare, uppstod frågan om vilket språk vi skulle använda för att skriva nästa mikrotjänst. Allt som hade skrivits hittills hade jag och min kollega skrivit på Python, eftersom min kollega var specialist inom detta område och professionellt sysslade med backendutveckling, medan jag egentligen var utvecklare för inbyggda system och skrev på det stora och fruktansvärda C++, medan jag bara hade lärt mig Python på universitetet.

Så, vi hade som uppgift att skriva en högpresterande tjänst vars huvudsyfte var att för-processa de inkommande data och skriva dem till databasen. Efter en paus föreslog min kollega, som C++-utvecklare, att jag skulle skriva denna tjänst i C++. Han argumenterade med att det skulle bli snabbare, mer effektivt, och juryn skulle verkligen bli imponerad av hur vi hanterade teamets resurser. På vilket jag svarade att jag aldrig hade gjort sådana saker i C++ och att jag med lätthet kunde ägna de återstående 20+ timmarna åt att söka, kompilera och sammanfoga lämpliga bibliotek. Kort sagt, jag blev feg. Så vi bestämde oss för att fortsätta och skriva allt i Python.

Nu, under den tvingande isoleringen, bestämde jag mig för att förstå hur man skriver tjänster i C++. Det första jag behövde göra var att välja ett lämpligt bibliotek. Mitt val föll på POCO, eftersom det var skrivet i objektorienterad stil och också kunde skryta med bra dokumentation. Dessutom stod valet av byggsystem på spel. Fram tills dess hade jag bara arbetat med Visual Studio, IAR och 'ren' makefile. Ingen av dessa system tilltalade mig, eftersom jag planerade att köra hela tjänsten i en docker-behållare. Då bestämde jag mig för att försöka förstå mig på cmake och den intressanta paketförvaltaren conan. Denna paketförvaltare gjorde det möjligt att ange alla beroenden i en fil

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

[generators]
cmake

och med ett enkelt kommando "conan install ." installera de nödvändiga biblioteken. Naturligtvis krävdes det också att jag gjorde ändringar i

CMakeLists.txt

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

Efter detta började jag leta efter ett bibliotek för att arbeta med PostgreSQL, eftersom jag hade lite erfarenhet av det och våra tjänster interagerade med det på Python. Och vet ni vad jag fick veta? Det finns i POCO! Men conan vet inte att det finns i POCO och kan inte bygga det, det finns en föråldrad konfigurationsfil i repositoryt (jag har redan informerat skaparna av POCO om detta fel). Så, jag får leta efter ett annat bibliotek.

Och då föll mitt val på ett mindre populärt bibliotek libpg. Och jag hade otrolig tur, det fanns redan i conan och kunde till och med byggas och länkas.

Nästa steg var att skriva en mall för tjänsten som kan hantera förfrågningar.
Vi måste ärva vår klass TemplateServerApp från Poco::Util::ServerApplication och överskrida metoden 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 << "Servern startad" << endl;

    waitForTerminationRequest();  // vänta på CTRL-C eller kill

    cerr << "Stänger ner..." << endl;
    s.stop();

    return Application::EXIT_OK;
}

I metoden main måste vi ange parametrarna: port, antal trådar och storlek på kön. Och viktigast av allt, vi måste ange en hanterare för inkommande förfrågningar. Detta görs genom att skapa en fabrik

TemplateRequestHandlerFactory

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

I mitt fall skapar den helt enkelt samma hanterare varje gång — TemplateServerAppHandler. Här kan vi placera vår affärslogik.

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

Jag har också skapat en klassmall för att arbeta med PostgreSQL. För att köra en enkel SQL-fråga, som att skapa en tabell, finns det en metod ExecuteSQL(). För mer avancerade frågor eller datainsamling måste du få en anslutning via GetConnection() och använda libpg:s API. (Jag kanske åtgärdar denna orättvisa senare).

Databas

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

Alla parametrar för databaskopplingen hämtas från miljön, så du behöver också skapa och konfigurera en .env-fil

.env

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

Du kan se hela koden på GitHub.

Mikrotjänster på C++. Fantasi eller verklighet?

Och den sista etappen var att skriva dockerfile och docker-compose.yml. Jag ska vara ärlig, det tog en stor del av min tid, inte bara för att jag var en nybörjare som behövde återkompilerar biblioteken varje gång, utan också på grund av valfångst med conan. Till exempel, för att conan ska ladda ner, installera och bygga nödvändiga beroenden räcker det inte med att köra "conan install .", du måste också skicka parametern -s compiler.libcxx=libstdc++11, annars riskerar du att få en massa fel under länkningen av din applikation. Jag satt med det här felet i flera timmar, och jag hoppas att denna artikel hjälper andra att lösa detta problem snabbare.

Vidare, efter att ha skrivit docker-compose.yml, följde jag rådet från en vän och lade till stöd för cookiecutter och nu kan du få en fullständig mall för en REST API-tjänst i C++, med en konfigurerad miljö och en uppsatt PostgreSQL, bara genom att skriva in i konsolen "cookiecutter https://github.com/KovalevVasiliy/cpp_rest_api_template.git'. Sen "docker-compose up --build".

Jag hoppas att denna mall hjälper nybörjare på deras svåra väg mot att utveckla REST API-applikationer i det stora och mäktiga, men så klumpiga språket C++.
Jag rekommenderar också starkt att läsa den här denna artikeln. Den förklarar mer detaljerat hur man arbetar med POCO och skriver sin egen REST API-tjänst.

Källa: habr.com

Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar 🔥 Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster