
Neste artigo, vou falar sobre como criei um template (cookiecutter) e configurei o ambiente para escrever um serviço REST API em C++ usando docker/docker-compose e o gerenciador de pacotes conan.
Durante um hackathon em que participei como desenvolvedor backend, surgiu a questão de qual tecnologia usar para escrever o novo microsserviço. Tudo que havia sido escrito até aquele momento fora criado por mim e meu , que era especialista na área e trabalhava profissionalmente com desenvolvimento de backends, enquanto eu era desenvolvedor de sistemas embarcados e programava na grande e temida linguagem C++, tendo aprendido um pouco de Python na universidade.
A tarefa era desenvolver um serviço de alta carga, cuja principal função seria o pré-processamento dos dados recebidos e sua gravação em um banco de dados. Após um intervalo para fumar, meu colega sugeriu que, como desenvolvedor C++, eu escrevesse esse serviço em C++. Ele argumentou que isso seria mais rápido, mais eficiente e que o júri ficaria impressionado com nossa capacidade de gerenciar os recursos da equipe. Respondi que nunca havia feito algo assim em C++ e que poderia facilmente dedicar as próximas 20+ horas buscando, compilando e montando as bibliotecas adequadas. Em outras palavras, eu desisti. Assim, decidimos e completamos tudo em Python.
Agora, durante esta autoisolação forçada, decidi entender como escrever serviços em C++. A primeira coisa que precisava fazer era escolher a biblioteca apropriada. Minha escolha recaiu sobre , pois era escrita em um estilo orientado a objetos e também tinha uma documentação decente. Além disso, surgiu a questão sobre qual sistema de construção utilizar. Até aquele momento, eu havia trabalhado apenas com Visual Studio, IAR e makefiles "puras". Nenhum desses sistemas me atraía, pois planejava executar todo o serviço em um contêiner docker. Então, decidi tentar entender o cmake e um gerenciador de pacotes interessante chamado . Esse gerenciador de pacotes permitia listar todas as dependências em um único arquivo
conanfile.txt
[requires]
poco/1.9.3
libpq/11.5
[generators]
cmake
e, com um simples comando "conan install .", instalar as bibliotecas necessárias. Naturalmente, também era necessário fazer algumas alterações no
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries( ${CONAN_LIBS})
Depois disso, comecei a procurar uma biblioteca para trabalhar com PostgreSQL, pois eu tinha um pouco de experiência com ela e era com ela que nossos serviços em Python interagiam. E sabe o que eu descobri? Ela está no POCO! Mas o conan não sabe que ela está no POCO e não consegue construí-la, pois o repositório contém um arquivo de configuração desatualizado (já escrevi sobre esse erro para os criadores do POCO). Portanto, vou ter que procurar outra biblioteca.
E então, minha escolha recaiu sobre uma biblioteca menos popular . E eu tive a sorte de encontrá-la, ela já estava no conan e até era construída e vinculada.
O próximo passo foi escrever um modelo de serviço capaz de processar requisições.
Devemos herdar nossa classe TemplateServerApp de Poco::Util::ServerApplication e sobrescrever o 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 ou kill
cerr << "Desligando..." << endl;
s.stop();
return Application::EXIT_OK;
}No método main, devemos definir os parâmetros: porta, número de threads e tamanho da fila. E o mais importante, devemos definir o manipulador de requisições entrantes. Isso é feito criando uma fábrica
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};No meu caso, ela simplesmente cria toda vez o mesmo manipulador — TemplateServerAppHandler. É aqui que podemos colocar nossa lógica de negócios.
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();
}
};Eu também criei um modelo de classe para trabalhar com PostgreSQL. Para executar um SQL simples, como criar uma tabela, existe o método ExecuteSQL(). Para consultas mais complexas ou para obter dados, será necessário conseguir uma conexão através de GetConnection() e usar a API libpg. (Talvez eu corrija essa injustiça mais tarde).
Banco de dados
#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 a conexão com o banco de dados são extraídos do ambiente, então você também precisa criar e configurar um arquivo .env
.env
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432Você pode ver todo o código no

E chegamos à última etapa de escrever o dockerfile e o docker-compose.yml. Para ser sincero, isso levou a maior parte do tempo, não apenas porque sou iniciante e precisava recompilar as bibliotecas a cada vez, mas devido aos obstáculos do conan. Por exemplo, para que o conan baixasse, instalasse e construísse as dependências necessárias, não basta apenas executar "conan install .", é preciso também passar o parâmetro -s compiler.libcxx=libstdc++11, caso contrário, você corre o risco de receber muitas mensagens de erro durante a vinculação do seu aplicativo. Passei horas preso a esse erro e espero que este artigo ajude outras pessoas a resolverem esse problema em menos tempo.
Em seguida, após escrever o docker-compose.yml, a pedido de um amigo, adicionei suporte a e agora você pode obter um modelo completo para um serviço REST API em C++, com o ambiente configurado e o PostgreSQL em funcionamento, simplesmente digitando no console "cookiecutter ". E depois "docker-compose up --build".
Espero que este modelo ajude os novatos em seu difícil caminho de desenvolvimento de aplicativos REST API na grande e poderosa, mas tão pesada, linguagem C++.
Além disso, recomendo muito ler este artigo. Ele explica em mais detalhes como trabalhar com POCO e criar seu próprio serviço REST API.
Fonte: habr.com
