Microservicios en C++. ¿Ficción o realidad?

Microservicios en C++. ¿Ficción o realidad?

En este artículo hablaré sobre cómo creé una plantilla (cookiecutter) y configuré un entorno para escribir un servicio API REST en C++ usando docker/docker-compose y el administrador de paquetes conan.

Durante el siguiente hackathon, en el que participé como desarrollador backend, surgió la pregunta sobre qué usar para escribir el próximo microservicio. Todo lo que se ha escrito hasta ahora fue escrito por mí y mi compañero en Python, ya que mi colega era un experto en este campo y desarrollaba backends profesionalmente, mientras que yo generalmente era un desarrollador de sistemas integrados y escribía en el genial y terrible C++, y acabo de aprender Python en la universidad.

Entonces, nos enfrentamos a la tarea de escribir un servicio de alta carga, cuya tarea principal era preprocesar los datos que recibía y escribirlos en la base de datos. Y después de otra pausa para fumar, un amigo me sugirió que yo, como desarrollador de C++, escribiera este servicio utilizando los profesionales. Argumentar esto es que será más rápido, más productivo y en general, el jurado estará encantado de cómo sabemos gestionar los recursos del equipo. A lo que respondí que nunca había hecho tales cosas en C++ y que fácilmente podría dedicar las más de 20 horas restantes a buscar, compilar y vincular bibliotecas adecuadas. En pocas palabras, me acobardé. Eso es lo que decidimos y completamos todo con calma en Python.

Ahora, durante el autoaislamiento forzado, decidí descubrir cómo escribir servicios en C++. Lo primero que había que hacer era decidirse por una biblioteca adecuada. Mi elección recayó en POCO, ya que fue escrito en un estilo orientado a objetos y también contaba con documentación normal. Además, surgió la duda sobre la elección del sistema de montaje. Hasta este punto sólo he trabajado con Visual Studio, IAR y archivos MAKE básicos. Y ninguno de estos sistemas me atrajo, ya que planeaba ejecutar todo el servicio en un contenedor acoplable. Entonces decidí intentar descubrir cmake y un administrador de paquetes interesante. conan. Este administrador de paquetes le permitió registrar todas las dependencias en un solo archivo

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

[generadores] cmake

y con un simple comando "conan install". Instale las bibliotecas necesarias. Naturalmente, también fue necesario realizar cambios en

CMakeLists.txt

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

Después de eso, comencé a buscar una biblioteca para trabajar con PostgreSQL, ya que era con la que tenía poca experiencia trabajando y también era con la que interactuaban nuestros servicios de Python. ¿Y sabes lo que aprendí? ¡Está en POCO! Pero Conan no sabe que está en POCO y no sabe cómo construirlo, hay un archivo de configuración desactualizado en el repositorio (ya les escribí sobre este error a los creadores de POCO). Esto significa que tendrás que buscar otra biblioteca.

Y luego mi elección recayó en una biblioteca menos popular. libpg. Y tuve una suerte increíble, ya estaba en Conan e incluso lo estaban armando y armando.

El siguiente paso fue escribir una plantilla de servicio que pueda procesar solicitudes.
Debemos heredar nuestra clase TemplateServerApp de Poco::Util::ServerApplication y anular el 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;
}

En el método principal debemos configurar los parámetros: puerto, número de hilos y tamaño de la cola. Y lo más importante, debe especificar un controlador para las solicitudes entrantes. Esto se hace creando una fábrica.

PlantillaRequestHandlerFactory

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

En mi caso, simplemente crea el mismo controlador cada vez: TemplateServerAppHandler. Aquí es donde podemos colocar nuestra lógica 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();
    }
};

También creé una plantilla de clase para trabajar con PostgreSQL. Para realizar SQL simple, como crear una tabla, existe un método EjecutarSQL(). Para consultas más complejas o recuperación de datos, deberá obtener una conexión a través de ObtenerConexión() y utilice la API libpg. (Quizás más adelante corrija esta injusticia).

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 los parámetros para conectarse a la base de datos se toman del entorno, por lo que también necesita crear y configurar el archivo .env.

.env

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

Puedes ver todo el código en github.

Microservicios en C++. ¿Ficción o realidad?

Y ahora viene la etapa final de escribir dockerfile y docker-compose.yml. Para ser honesto, esto tomó la mayor parte del tiempo, y no solo porque soy un novato, porque era necesario reconstruir las bibliotecas cada vez, sino también por las trampas de Conan. Por ejemplo, para que Conan descargue, instale y cree las dependencias necesarias, no basta con descargar "conan install.", también debe pasar el parámetro -s compiler.libcxx=libstdc++11; de lo contrario, corre el riesgo de recibir un montón de errores en la etapa de vinculación de su aplicación. He estado atascado con este error durante varias horas y espero que este artículo ayude a otras personas a resolver este problema en menos tiempo.

Luego, después de escribir docker-compose.yml, siguiendo el consejo de mi amigo, agregué soporte cortador de galletas y ahora puede obtener una plantilla completa para un servicio API REST en C++, con un entorno personalizado y PostgreSQL instalado, simplemente ingresando "cookiecutter" en la consola. https://github.com/KovalevVasiliy/cpp_rest_api_template.git" Y luego "docker-compose up -build".

Espero que esta plantilla ayude a los principiantes en el difícil camino de desarrollar aplicaciones API REST en un lenguaje excelente y potente, pero tan torpe, como C++.
Además, recomiendo leer aquí. este artículo. Explica con más detalle cómo trabajar con POCO y escribir su propio servicio API REST.

Fuente: habr.com

Añadir un comentario