Microsserviços em C++. Ficção ou realidade?

Microsserviços em C++. Ficção ou realidade?

Neste artigo falarei sobre como criei um template (cookiecutter) e configurei um ambiente para escrever um serviço REST API em C++ usando docker/docker-compose e o gerenciador de pacotes conan.

Durante o próximo hackathon, no qual participei como desenvolvedor backend, surgiu a questão sobre o que usar para escrever o próximo microsserviço. Tudo o que foi escrito até agora foi escrito por mim e meu companheiro em Python, já que meu colega era um especialista nesta área e desenvolvia back-ends profissionalmente, enquanto eu geralmente era um desenvolvedor de sistemas embarcados e escrevia no excelente e terrível C++, e acabei de aprender Python na universidade.

Assim, nos deparamos com a tarefa de escrever um serviço de alta carga, cuja principal tarefa era pré-processar os dados que chegavam a ele e gravá-los no banco de dados. E depois de outra pausa para fumar, um amigo sugeriu que eu, como desenvolvedor C++, escrevesse este serviço usando os profissionais. Argumentar isto é que será mais rápido, mais produtivo e, em geral, o júri ficará encantado com a forma como sabemos gerir os recursos da equipa. Ao que respondi que nunca tinha feito essas coisas em C++ e poderia facilmente dedicar as mais de 20 horas restantes para pesquisar, compilar e vincular bibliotecas adequadas. Simplificando, eu me acovardei. Foi isso que decidimos e concluímos tudo com calma em Python.

Agora, durante o auto-isolamento forçado, decidi descobrir como escrever serviços em C++. A primeira coisa a fazer foi decidir sobre uma biblioteca adequada. Minha escolha recaiu sobre PEQUENO, já que foi escrito em um estilo orientado a objetos e também possuía documentação normal. Além disso, surgiu a questão sobre a escolha de um sistema de montagem. Até agora trabalhei apenas com Visual Studio, IAR e makefiles simples. E nenhum desses sistemas me atraiu, já que planejei rodar todo o serviço em um contêiner docker. Então decidi tentar descobrir o cmake e um gerenciador de pacotes interessante conan. Este gerenciador de pacotes permitiu registrar todas as dependências em um arquivo

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

[geradores] cmake

e com um simples comando “conan install”. instale as bibliotecas necessárias. Naturalmente, também foi necessário fazer alterações

CMakeLists.txt

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

Depois disso, comecei a procurar uma biblioteca para trabalhar com PostgreSQL, pois era aquela com a qual eu tinha pouca experiência de trabalho e também era aquela com a qual nossos serviços Python interagiam. E sabe o que aprendi? Está no POCO! Mas Conan não sabe que está no POCO e não sabe como construí-lo, existe um arquivo de configuração desatualizado no repositório (já escrevi sobre esse erro para os criadores do POCO). Isso significa que você terá que procurar outra biblioteca.

E então minha escolha recaiu sobre uma biblioteca menos popular libpg. E tive uma sorte incrível, já estava em Conan e até estava sendo montado e montado.

A próxima etapa foi escrever um modelo de serviço que pudesse processar solicitações.
Devemos herdar nossa classe TemplateServerApp de Poco::Util::ServerApplication e substituir o método principal.

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

No método principal devemos definir os parâmetros: porta, número de threads e tamanho da fila. E o mais importante, você deve especificar um manipulador para solicitações recebidas. Isso é feito criando uma fábrica

TemplateRequestHandlerFactory

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

No meu caso, ele simplesmente cria sempre 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 << "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();
    }
};

Também criei um modelo de classe para trabalhar com PostgreSQL. Para executar SQL simples, como criar uma tabela, existe um método ExecutarSQL(). Para consultas mais complexas ou recuperação de dados, será necessário obter uma conexão via ObterConexão() e use a API libpg. (Talvez mais tarde eu corrija esta injustiça).

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 de conexão ao banco de dados são retirados do ambiente, portanto também é necessário criar e configurar o arquivo .env

.env

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

Você pode ver todo o código em GitHub.

Microsserviços em C++. Ficção ou realidade?

E agora vem o estágio final de escrever o dockerfile e o docker-compose.yml. Para ser sincero, isso demorou a maior parte do tempo, e não só porque sou novato, porque era necessário reconstruir as bibliotecas todas as vezes, mas por causa das armadilhas de Conan. Por exemplo, para que o conan baixe, instale e construa as dependências necessárias, não basta baixar “conan install.”, ele também precisa passar o parâmetro -s compiler.libcxx=libstdc++11, caso contrário você corre o risco de receber vários erros no estágio de vinculação do seu aplicativo. Estou com esse erro há várias horas e espero que este artigo ajude outras pessoas a resolver esse problema em menos tempo.

Em seguida, depois de escrever docker-compose.yml, seguindo o conselho do meu amigo, adicionei suporte Cortador de biscoito e agora você pode obter um modelo completo para um serviço REST API em C++, com um ambiente personalizado e PostgreSQL instalado, simplesmente digitando “cookiecutter” no console https://github.com/KovalevVasiliy/cpp_rest_api_template.git" E então “docker-compose up —build”.

Espero que este modelo ajude os iniciantes em seu difícil caminho de desenvolvimento de aplicativos REST API em uma linguagem excelente e poderosa, mas desajeitada, como C++.
Além disso, recomendo fortemente a leitura aqui este artigo. Explica com mais detalhes como trabalhar com POCO e escrever seu próprio serviço REST API.

Fonte: habr.com

Adicionar um comentário