Microservizos en C++. ¿Ficción ou realidade?

Microservizos en C++. ¿Ficción ou realidade?

Neste artigo falarei de como creei un modelo (cookiecutter) e configurei un ambiente para escribir un servizo de API REST en C++ usando docker/docker-compose e o xestor de paquetes conan.

Durante o seguinte hackathon, no que participei como programador backend, xurdiu a pregunta sobre que usar para escribir o próximo microservizo. Todo o que se escribiu ata agora escribímolo eu e miña camarada en Python, xa que o meu colega era un experto neste campo e desenvolveu profesionalmente backends, mentres eu, en xeral, era un programador de sistemas embebidos e escribía no xenial e terrible C++, e acabo de aprender Python na universidade.

Entón, enfrontámonos á tarefa de escribir un servizo de alta carga, cuxa tarefa principal era preprocesar os datos que chegaban a el e escribilos na base de datos. E despois doutra pausa de fume, un amigo suxeriu que eu, como desenvolvedor de C++, escriba este servizo usando os profesionais. Argumentando isto é que será máis rápido, máis produtivo e, en xeral, o xurado estará encantado de como sabemos xestionar os recursos do equipo. Ao que respondín que nunca fixera tales cousas en C++ e que podía dedicar facilmente as máis de 20 horas restantes a buscar, compilar e vincular bibliotecas adecuadas. En pocas palabras, quedei. Iso foi o que decidimos e completamos todo con calma en Python.

Agora, durante o autoillamento forzado, decidín descubrir como escribir servizos en C++. O primeiro que había que facer foi decidir unha biblioteca adecuada. A miña elección recaeu pozo, xa que estaba escrito nun estilo orientado a obxectos e tamén contaba cunha documentación normal. Ademais, xurdiu a pregunta sobre a elección dun sistema de montaxe. Ata este momento só traballei con Visual Studio, IAR e makefiles. E ningún destes sistemas me atraeu, xa que pensaba executar todo o servizo nun contedor docker. Entón decidín tentar descubrir cmake e un xestor de paquetes interesante conan. Este xestor de paquetes permitiuche rexistrar todas as dependencias nun ficheiro

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

[xeradores] cmake

e cun simple comando "conan install". instalar as bibliotecas necesarias. Por suposto, tamén foi necesario facer cambios

CMakeLists.txt

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

Despois diso, comecei a buscar unha biblioteca para traballar con PostgreSQL, xa que era coa que tiña pouca experiencia traballando, e tamén coa que interactuaban os nosos servizos Python. E sabes o que aprendín? Está en POCO! Pero conan non sabe que está en POCO e non sabe como crealo; hai un ficheiro de configuración desactualizado no repositorio (xa escribín sobre este erro aos creadores de POCO). Isto significa que terás que buscar outra biblioteca.

E entón a miña elección recaeu nunha biblioteca menos popular libpg. E tiven unha sorte incrible, xa estaba en conan e ata se estaba a montar e montar.

O seguinte paso foi escribir un modelo de servizo que poida procesar as solicitudes.
Debemos herdar a nosa clase TemplateServerApp de Poco::Util::ServerApplication e anular o método principal.

Aplicación TemplateServer

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

No método principal debemos establecer os parámetros: porto, número de fíos e tamaño da cola. E o máis importante, debes especificar un controlador para as solicitudes entrantes. Isto faise creando unha fábrica

TemplateRequestHandlerFactory

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

No meu caso, simplemente crea o mesmo controlador cada vez: TemplateServerAppHandler. Aquí é onde podemos situar a nosa lóxica empresarial.

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

Tamén creei un modelo de clase para traballar con PostgreSQL. Para realizar SQL simple, como crear unha táboa, hai un método ExecuteSQL(). Para consultas máis complexas ou recuperación de datos, terá que obter unha conexión a través de GetConnection() e use a API libpg. (Quizais máis adiante corrixa esta inxustiza).

Base de datos

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

Todos os parámetros para conectarse á base de datos son tomados do entorno, polo que tamén cómpre crear e configurar o ficheiro .env

.env

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

Podes ver todo o código en github.

Microservizos en C++. ¿Ficción ou realidade?

E agora chega a fase final de escribir o dockerfile e docker-compose.yml. Para ser sincero, isto levou a maior parte do tempo, e non só porque son un novato, porque era necesario reconstruír as bibliotecas cada vez, senón polas trampas de conan. Por exemplo, para que conan descargue, instale e constrúa as dependencias necesarias, non é suficiente que descargue "conan install .", tamén ten que pasar o parámetro -s compiler.libcxx=libstdc++11, en caso contrario corre o risco de obter unha morea de erros na fase de vinculación da súa aplicación. Estiven atascado con este erro durante varias horas e espero que este artigo axude a outras persoas a resolver este problema en menos tempo.

A continuación, despois de escribir docker-compose.yml, por consello do meu amigo, engadín soporte cortador de galletas e agora podes conseguir un modelo completo para un servizo de API REST en C++, cun ambiente personalizado e PostgreSQL instalado, simplemente introducindo "cookiecutter" na consola https://github.com/KovalevVasiliy/cpp_rest_api_template.git" E despois "docker-compose up -build".

Espero que este modelo axude aos principiantes no seu difícil camiño de desenvolver aplicacións da API REST nunha linguaxe xenial e poderosa, pero tan torpe como C++.
Ademais, recomendo encarecidamente ler aquí isto artigo. Explica con máis detalle como traballar con POCO e escribir o teu propio servizo de API REST.

Fonte: www.habr.com

Engadir un comentario