Mikrotjänster i C++. Fiktion eller verklighet?

Mikrotjänster i C++. Fiktion eller verklighet?

I den här artikeln kommer jag att prata om hur jag skapade en mall (cookiecutter) och satte upp en miljö för att skriva en REST API-tjänst i C++ med hjälp av docker/docker-compose och conan package manager.

Under nästa hackathon, där jag deltog som backend-utvecklare, uppstod frågan om vad man skulle använda för att skriva nästa mikrotjänst. Allt som har skrivits hittills är skrivet av mig och min kamrat i Python, eftersom min kollega var expert på detta område och professionellt utvecklade backends, medan jag i allmänhet var en utvecklare av inbyggda system och skrev i det fantastiska och hemska C++, och jag har precis lärt mig Python på universitetet.

Så vi stod inför uppgiften att skriva en högbelastningstjänst, vars huvuduppgift var att förbehandla de data som kom till den och skriva den till databasen. Och efter ännu en rökpaus föreslog en vän att jag som C++-utvecklare skulle skriva den här tjänsten med hjälp av proffsen. Att argumentera för detta är att det kommer att bli snabbare, mer produktivt, och i allmänhet kommer juryn att vara nöjd med hur vi vet hur vi ska hantera teamets resurser. Varpå jag svarade att jag aldrig gjort sådana saker i C++ och lätt kunde ägna de återstående 20+ timmarna åt att söka, sammanställa och länka lämpliga bibliotek. Enkelt uttryckt, jag kyckling ut. Det var vad vi bestämde oss för och avslutade lugnt allt i Python.

Nu, under den påtvingade självisoleringen, bestämde jag mig för att ta reda på hur man skriver tjänster i C++. Det första man skulle göra var att välja ett lämpligt bibliotek. Mitt val föll på LITEN, eftersom det var skrivet i en objektorienterad stil och även skröt med normal dokumentation. Frågan uppstod också om att välja ett monteringssystem. Hittills har jag bara arbetat med Visual Studio, IAR och bare make-filer. Och inget av dessa system tilltalade mig, eftersom jag planerade att köra hela tjänsten i en hamnarcontainer. Sedan bestämde jag mig för att försöka lista ut cmake och en intressant pakethanterare conan. Denna pakethanterare tillät dig att registrera alla beroenden i en fil

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

[generatorer] cmake

och med ett enkelt kommando "conan install ." installera de nödvändiga biblioteken. Naturligtvis var det också nödvändigt att göra ändringar i

CMakeLists.txt

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

Efter det började jag leta efter ett bibliotek att arbeta med PostgreSQL, eftersom det var det jag hade liten erfarenhet av att arbeta med, och det var också det som våra Python-tjänster interagerade med. Och vet du vad jag lärde mig? Det är i POCO! Men conan vet inte att det är i POCO och vet inte hur man bygger det; det finns en föråldrad konfigurationsfil i förvaret (jag har redan skrivit om detta fel till skaparna av POCO). Det betyder att du måste leta efter ett annat bibliotek.

Och då föll mitt val på ett mindre populärt bibliotek libpg. Och jag hade otroligt tur, den fanns redan i conan och höll på att monteras och monteras.

Nästa steg var att skriva en tjänstemall som kan behandla förfrågningar.
Vi måste ärva vår TemplateServerApp-klass från Poco::Util::ServerApplication och åsidosätta huvudmetoden.

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 huvudmetoden måste vi ställa in parametrarna: port, antal trådar och köstorlek. Och viktigast av allt, du 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 det helt enkelt samma hanterare varje gång - TemplateServerAppHandler. Det är här vi kan 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 << "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();
    }
};

Jag skapade också en klassmall för att arbeta med PostgreSQL. För att utföra enkel SQL, som att skapa en tabell, finns det en metod ExecuteSQL(). För mer komplexa frågor eller datahämtning måste du skaffa en anslutning via GetConnection() och använd libpg API. (Kanske senare kommer jag att rätta till denna orättvisa).

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 att ansluta till databasen är hämtade från miljön, så du måste också skapa och konfigurera .env-filen

.env

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

Du kan se all kod på github.

Mikrotjänster i C++. Fiktion eller verklighet?

Och nu kommer det sista steget av att skriva dockerfilen och docker-compose.yml. Om jag ska vara ärlig tog detta det mesta av tiden, och inte bara för att jag är en noob, för att det var nödvändigt att bygga om biblioteken varje gång, utan på grund av fallgroparna med conan. Till exempel, för att conan ska kunna ladda ner, installera och bygga de nödvändiga beroenden, räcker det inte med att ladda ner "conan install .", det måste också skicka parametern -s compiler.libcxx=libstdc++11, annars du riskerar att få en massa fel vid länkningsstadiet för din ansökan. Jag har fastnat med det här felet i flera timmar och jag hoppas att den här artikeln kommer att hjälpa andra att lösa det här problemet på kortare tid.

Därefter, efter att ha skrivit docker-compose.yml, på inrådan av min vän, lade jag till stöd kakskärare och nu kan du skaffa dig en fullfjädrad mall för en REST API-tjänst i C++, med en anpassad miljö och PostgreSQL installerad, helt enkelt genom att ange "cookiecutter" i konsolen https://github.com/KovalevVasiliy/cpp_rest_api_template.git" Och sedan "docker-compose up -build".

Jag hoppas att den här mallen kommer att hjälpa nybörjare på deras svåra väg att utveckla REST API-applikationer i det fantastiska och kraftfulla, men så klumpiga språket som C++.
Dessutom rekommenderar jag starkt att läsa här detta artikel. Den förklarar mer i detalj hur du arbetar med POCO och skriver din egen REST API-tjänst.

Källa: will.com

Lägg en kommentar