Microservizi in C++. Finzione o realtà?

Microservizi in C++. Finzione o realtà?

In questo articolo parlerò di come ho creato un modello (cookiecutter) e impostato un ambiente per scrivere un servizio API REST in C++ utilizzando docker/docker-compose e il gestore pacchetti conan.

Durante il successivo hackathon, al quale ho partecipato come sviluppatore backend, è sorta la domanda su cosa utilizzare per scrivere il prossimo microservizio. Tutto quello che è stato scritto finora è stato scritto da me e dal mio compagno in Python, poiché il mio collega era un esperto in questo campo e sviluppava backend professionalmente, mentre io generalmente ero uno sviluppatore di sistemi embedded e scrivevo nel grande e terribile C++, e ho appena imparato Python all'università.

Quindi, ci siamo trovati di fronte al compito di scrivere un servizio ad alto carico, il cui compito principale era preelaborare i dati in arrivo e scriverli nel database. E dopo un'altra pausa per fumare, un amico mi ha suggerito, come sviluppatore C++, di scrivere questo servizio utilizzando i professionisti. Sostenere questo è che sarà più veloce, più produttivo e, in generale, la giuria sarà contenta di come sappiamo gestire le risorse del team. Al che ho risposto che non avevo mai fatto cose del genere in C++ e avrei potuto facilmente dedicare le restanti 20+ ore alla ricerca, compilazione e collegamento di librerie adatte. In poche parole, mi sono tirato indietro. Questo è ciò che abbiamo deciso e con calma abbiamo completato tutto in Python.

Ora, durante l'autoisolamento forzato, ho deciso di capire come scrivere servizi in C++. La prima cosa da fare era decidere una biblioteca adatta. La mia scelta è caduta POCO, poiché era scritto in uno stile orientato agli oggetti e vantava anche una documentazione normale. Inoltre, è sorta la domanda sulla scelta di un sistema di assemblaggio. Fino a questo punto ho lavorato solo con Visual Studio, IAR e makefile bare. E nessuno di questi sistemi mi attirava, poiché avevo pianificato di eseguire l'intero servizio in un contenitore docker. Quindi ho deciso di provare a capire cmake e un interessante gestore di pacchetti conan. Questo gestore di pacchetti ti ha permesso di registrare tutte le dipendenze in un unico file

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

[generatori] cmake

e con un semplice comando "conan install". installare le librerie necessarie. Naturalmente è stato necessario apportare modifiche anche a

CMakeLists.txt

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

Successivamente, ho iniziato a cercare una libreria con cui lavorare con PostgreSQL, poiché era quella con cui avevo poca esperienza di lavoro, ed era anche quella con cui interagivano i nostri servizi Python. E sai cosa ho imparato? È in POCO! Ma conan non sa che è in POCO e non sa come costruirlo; nel repository c'è un file di configurazione obsoleto (ho già scritto di questo errore ai creatori di POCO). Ciò significa che dovrai cercare un'altra libreria.

E poi la mia scelta è caduta su una libreria meno frequentata libpg. E sono stato incredibilmente fortunato, era già in Conan e veniva persino assemblato e assemblato.

Il passo successivo è stato scrivere un modello di servizio in grado di elaborare le richieste.
Dobbiamo ereditare la nostra classe TemplateServerApp da Poco::Util::ServerApplication e sovrascrivere il metodo 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<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;
}

Nel metodo principale dobbiamo impostare i parametri: porta, numero di thread e dimensione della coda. E, cosa più importante, devi specificare un gestore per le richieste in arrivo. Questo viene fatto creando una fabbrica

TemplateRequestHandlerFactory

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

Nel mio caso, crea semplicemente ogni volta lo stesso gestore: TemplateServerAppHandler. È qui che possiamo collocare la nostra logica aziendale.

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

Ho anche creato un modello di classe per lavorare con PostgreSQL. Per eseguire un semplice SQL, come la creazione di una tabella, esiste un metodo EseguiSQL(). Per interrogazioni più complesse o recupero dati sarà necessario collegarsi tramite OttieniConnessione() e utilizzare l'API libpg. (Forse più tardi correggerò questa ingiustizia).

Banca Dati

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

Tutti i parametri per la connessione al database vengono presi dall'ambiente, quindi è necessario creare e configurare anche il file .env

.env

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

Puoi vedere tutto il codice su github.

Microservizi in C++. Finzione o realtà?

E ora arriva la fase finale della scrittura del dockerfile e del docker-compose.yml. Ad essere onesti, questo ha richiesto la maggior parte del tempo, e non solo perché sono un noob, perché era necessario ricostruire le librerie ogni volta, ma a causa delle insidie ​​di Conan. Ad esempio, affinché conan possa scaricare, installare e creare le dipendenze necessarie, non è sufficiente scaricare “conan install .”, deve anche passare il parametro -s compiler.libcxx=libstdc++11, altrimenti rischi di ricevere una serie di errori nella fase di collegamento della tua applicazione. Sono rimasto bloccato con questo errore per diverse ore e spero che questo articolo possa aiutare altre persone a risolvere questo problema in meno tempo.

Successivamente, dopo aver scritto docker-compose.yml, su consiglio del mio amico, ho aggiunto il supporto tagliabiscotti e ora puoi procurarti un modello completo per un servizio API REST in C++, con un ambiente personalizzato e PostgreSQL installato, semplicemente inserendo "cookiecutter" nella console https://github.com/KovalevVasiliy/cpp_rest_api_template.git" E poi "docker-compose up —build".

Spero che questo modello aiuti i principianti nel loro difficile percorso di sviluppo di applicazioni API REST nel linguaggio fantastico e potente, ma così goffo come C++.
Inoltre, consiglio vivamente di leggere qui questo articolo. Spiega in modo più dettagliato come lavorare con POCO e scrivere il proprio servizio API REST.

Fonte: habr.com

Aggiungi un commento