Mga microservice sa C++. Fiction o realidad?

Mga microservice sa C++. Fiction o realidad?

Sa artikulong ito ay pag-uusapan ko kung paano ako gumawa ng template (cookiecutter) at nag-set up ng environment para sa pagsusulat ng REST API service sa C++ gamit ang docker/docker-compose at ang conan package manager.

Sa susunod na hackathon, kung saan lumahok ako bilang isang backend developer, bumangon ang tanong tungkol sa kung ano ang gagamitin para isulat ang susunod na microservice. Lahat ng naisulat sa ngayon ay isinulat ko at ng aking kasama sa Python, dahil ang aking kasamahan ay isang dalubhasa sa larangang ito at propesyonal na bumuo ng mga backend, habang ako ay karaniwang isang naka-embed na developer ng system at nagsulat sa mahusay at kakila-kilabot na C++, at natutunan ko lang ang Python sa unibersidad.

Kaya, nahaharap kami sa gawain ng pagsulat ng isang serbisyong may mataas na pagkarga, ang pangunahing gawain kung saan ay i-preprocess ang data na dumarating dito at isulat ito sa database. At pagkatapos ng isa pang smoke break, iminungkahi ng isang kaibigan na ako, bilang isang developer ng C++, ay isulat ang serbisyong ito gamit ang mga pro. Ang pangangatwiran nito ay magiging mas mabilis, mas produktibo, at sa pangkalahatan, matutuwa ang hurado sa kung paano natin malalaman kung paano pamahalaan ang mga mapagkukunan ng koponan. Kung saan sumagot ako na hindi ko pa nagawa ang mga ganoong bagay sa C++ at madaling italaga ang natitirang 20+ na oras sa paghahanap, pag-compile at pag-link ng mga angkop na aklatan. Sa madaling salita, nag-chick out ako. Iyon ang napagpasyahan namin at mahinahong natapos ang lahat sa Python.

Ngayon, sa panahon ng sapilitang pag-iisa sa sarili, nagpasya akong malaman kung paano magsulat ng mga serbisyo sa C++. Ang unang bagay na dapat gawin ay magpasya sa isang angkop na aklatan. Ang aking pinili ay nahulog sa POCO, dahil isinulat ito sa isang object-oriented na istilo at ipinagmamalaki rin ang normal na dokumentasyon. Gayundin, lumitaw ang tanong tungkol sa pagpili ng isang sistema ng pagpupulong. Hanggang sa puntong ito ay nagtrabaho lamang ako sa Visual Studio, IAR at mga hubad na makefile. At wala sa mga system na ito ang umapela sa akin, dahil pinlano kong patakbuhin ang buong serbisyo sa isang docker container. Pagkatapos ay nagpasya akong subukang malaman ang cmake at isang kawili-wiling manager ng package conan. Pinahintulutan ka ng manager ng package na ito na irehistro ang lahat ng dependencies sa isang file

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

[mga generator] cmake

at may simpleng command na "conan install ." i-install ang mga kinakailangang aklatan. Natural, kailangan ding gumawa ng mga pagbabago sa

CMakeLists.txt

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

Pagkatapos noon, nagsimula akong maghanap ng library para magtrabaho sa PostgreSQL, dahil ito ang kakaunting karanasan ko sa pagtatrabaho, at ito rin ang nakipag-ugnayan sa aming mga serbisyo sa Python. At alam mo ba kung ano ang natutunan ko? Ito ay nasa POCO! Ngunit hindi alam ni conan na ito ay nasa POCO at hindi alam kung paano ito itatayo; mayroong isang hindi napapanahong configuration file sa repositoryo (naisulat ko na ang tungkol sa error na ito sa mga tagalikha ng POCO). Nangangahulugan ito na kailangan mong maghanap ng isa pang aklatan.

At pagkatapos ay nahulog ang aking pinili sa isang hindi gaanong sikat na library libpg. At ako ay hindi kapani-paniwalang masuwerte, ito ay naka-conan na at kahit na ini-assemble at binuo.

Ang susunod na hakbang ay ang pagsulat ng template ng serbisyo na maaaring magproseso ng mga kahilingan.
Dapat nating mamana ang aming klase ng TemplateServerApp mula sa Poco::Util::ServerApplication at i-override ang pangunahing paraan.

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

Sa pangunahing paraan dapat nating itakda ang mga parameter: port, bilang ng mga thread at laki ng pila. At higit sa lahat, kailangan mong tukuyin ang isang handler para sa mga papasok na kahilingan. Ginagawa ito sa pamamagitan ng paglikha ng isang pabrika

TemplateRequestHandlerFactory

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

Sa aking kaso, lumilikha lamang ito ng parehong handler sa bawat oras - TemplateServerAppHandler. Dito natin mailalagay ang lohika ng ating negosyo.

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

Gumawa din ako ng template ng klase upang gumana sa PostgreSQL. Upang maisagawa ang simpleng SQL, tulad ng paglikha ng isang talahanayan, mayroong isang paraan ExecuteSQL(). Para sa mas kumplikadong mga query o data retrieval, kakailanganin mong kumuha ng koneksyon sa pamamagitan ng GetConnection() at gamitin ang libpg API. (Marahil mamaya ay itatama ko itong kawalang-katarungan).

Database

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

Ang lahat ng mga parameter para sa pagkonekta sa database ay kinuha mula sa kapaligiran, kaya kailangan mo ring lumikha at i-configure ang .env file

.env

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

Makikita mo ang lahat ng code sa github.

Mga microservice sa C++. Fiction o realidad?

At ngayon ay darating ang huling yugto ng pagsulat ng dockerfile at docker-compose.yml. Sa totoo lang, madalas itong tumagal, at hindi lamang dahil ako ay isang noob, dahil kinakailangan na muling itayo ang mga aklatan sa bawat oras, ngunit dahil sa mga pitfalls ng conan. Halimbawa, upang mai-download, mai-install at mabuo ni conan ang mga kinakailangang dependency, hindi sapat na i-download nito ang β€œconan install .”, kailangan din nitong ipasa ang -s compiler.libcxx=libstdc++11 na parameter, kung hindi. nanganganib kang makakuha ng maraming error sa yugto ng pagli-link ng iyong aplikasyon. Ilang oras na akong natigil sa error na ito at umaasa akong makakatulong ang artikulong ito sa ibang tao na malutas ang problemang ito sa mas kaunting oras.

Susunod, pagkatapos magsulat ng docker-compose.yml, sa payo ng aking kaibigan, nagdagdag ako ng suporta cookiecutter at ngayon maaari kang makakuha ng iyong sarili ng isang ganap na template para sa isang REST API na serbisyo sa C++, na may customized na kapaligiran, at PostgreSQL na naka-install, sa pamamagitan lamang ng pagpasok ng "cookiecutter" sa console https://github.com/KovalevVasiliy/cpp_rest_api_template.git" At pagkatapos ay "docker-compose up -build".

Umaasa ako na ang template na ito ay makakatulong sa mga nagsisimula sa kanilang mahirap na landas ng pagbuo ng mga REST API application sa mahusay at makapangyarihan, ngunit tulad ng isang clumsy na wika tulad ng C++.
Gayundin, lubos kong inirerekumenda ang pagbabasa dito ito artikulo. Ipinapaliwanag nito nang mas detalyado kung paano magtrabaho sa POCO at magsulat ng sarili mong serbisyo ng REST API.

Pinagmulan: www.habr.com

Magdagdag ng komento