
En este artículo, hablaré sobre cómo creé una plantilla (cookiecutter) y configuré el entorno para escribir un servicio REST API en C++ utilizando docker/docker-compose y el gestor de paquetes conan.
Durante un hackathon en el que participé como desarrollador backend, surgió la pregunta de qué utilizar para escribir otro microservicio. Todo lo que se había escrito hasta ese momento, lo había hecho yo y mi en Python, ya que mi colega era un especialista en este campo y se dedicaba profesionalmente al desarrollo de backend, mientras que yo era más bien un desarrollador de sistemas embebidos y escribía en el gran y temido C++, y solo había aprendido Python en la universidad.
Así que, se nos planteó la tarea de escribir un servicio de alta carga, cuya principal tarea era el preprocesamiento de los datos entrantes y su almacenamiento en la base de datos. Y después de un descanso, mi compañero me sugirió, como desarrollador de C++, que escribiera este servicio en C++. Argumentó que sería más rápido, más eficiente y que, en general, el jurado se sorprendería de cómo manejamos los recursos del equipo. A lo que respondí que nunca había hecho este tipo de cosas en C++ y que podría fácilmente dedicar las restantes 20+ horas a buscar, compilar y ensamblar las bibliotecas adecuadas. En resumen, me asusté. Así que decidimos que seguiríamos escribiendo todo en Python.
Ahora, durante la autoaislamiento obligatorio, decidí aprender a escribir servicios en C++. Lo primero que debía hacer era elegir una biblioteca adecuada. Mi elección recayó en , ya que estaba escrita en un estilo orientado a objetos, y también contaba con una documentación decente. Asimismo, surgió la cuestión de elegir un sistema de construcción. Hasta ese momento, solo había trabajado con Visual Studio, IAR y makefiles 'puros'. Y ninguno de estos sistemas me atraía, ya que planeaba ejecutar todo el servicio en un contenedor de docker. Entonces decidí intentar entender cmake y un interesante gestor de paquetes llamado . Este gestor de paquetes permitía definir todas las dependencias en un solo archivo
conanfile.txt
[requires]
poco/1.9.3
libpq/11.5
[generators]
cmake
y, con un simple comando "conan install .", instalar las bibliotecas necesarias. Por supuesto, también era necesario hacer cambios en
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries( ${CONAN_LIBS})
Después de eso, comencé a buscar una biblioteca para trabajar con PostgreSQL, ya que tenía un poco de experiencia con ella, y también era la que utilizaban nuestros servicios en Python. ¿Y saben qué descubrí? ¡Está en POCO! Pero conan no sabe que está en POCO y no puede construirla, en el repositorio hay un archivo de configuración obsoleto (ya informé de este error a los creadores de POCO). Así que tendré que buscar otra biblioteca.
Y entonces mi elección recayó en una biblioteca menos popular . Y tuve una suerte increíble, ya que ya estaba en conan y se compilaba y enlazaba correctamente.
El siguiente paso fue escribir la plantilla de un servicio capaz de procesar solicitudes.
Debemos heredar nuestra clase TemplateServerApp de Poco::Util::ServerApplication y sobreescribir el método 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 << "Servidor iniciado" << endl;
waitForTerminationRequest(); // espera por CTRL-C o kill
cerr << "Apagando..." << endl;
s.stop();
return Application::EXIT_OK;
}En el método main debemos establecer parámetros: puerto, número de hilos y tamaño de la cola. Y lo más importante, debemos establecer el manejador de solicitudes entrantes. Esto se realiza creando una fábrica
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};En mi caso, simplemente crea el mismo manejador cada vez: TemplateServerAppHandler. Justo aquí es donde podemos ubicar nuestra lógica de negocio.
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 << "Método: " << 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 he creado una plantilla de clase para trabajar con PostgreSQL. Para ejecutar una SQL simple, como crear una tabla, hay un método ExecuteSQL(). Para consultas más complejas o para obtener datos, será necesario obtener la conexión a través de GetConnection() y utilizar la API de libpg. (Quizás más tarde 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, así que también necesitas crear y configurar un archivo .env
.env
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432Puedes ver todo el código en

Y llegó la última etapa de escribir el dockerfile y docker-compose.yml. Debo decir que esto consumió la mayor parte del tiempo, no solo porque soy un novato y necesitaba reconstruir las bibliotecas cada vez, sino debido a los escollos de conan. Por ejemplo, para que conan descargue, instale y compile las dependencias necesarias, no basta con intentar 'conan install .', también necesita pasar el parámetro -s compiler.libcxx=libstdc++11, de lo contrario, corres el riesgo de recibir un montón de errores en la etapa de enlazado de tu aplicación. Pasé varias horas lidiando con este error, y espero que este artículo ayude a otras personas a resolver este problema en menos tiempo.
A continuación, después de escribir el docker-compose.yml, siguiendo el consejo de un amigo, añadí soporte para y ahora puedes obtener una plantilla completa para un servicio REST API en C++, con el entorno configurado y PostgreSQL levantada, simplemente ingresando en la consola 'cookiecutter '. Luego 'docker-compose up —build'.
Espero que esta plantilla ayude a los principiantes en su difícil camino hacia el desarrollo de aplicaciones REST API en un lenguaje grande y poderoso, pero tan torpe como C++.
También recomiendo encarecidamente leer este artículo. En él se explica con más detalle cómo trabajar con POCO y escribir tu propio servicio REST API.
Fuente: habr.com
