Мікрасэрвісы на З++. Выдумка ці рэальнасць?

Мікрасэрвісы на З++. Выдумка ці рэальнасць?

У гэтым артыкуле я раскажу пра тое, як стварыў шаблон (cookiecutter) і наладзіў асяроддзе для напісання REST API сэрвісу на З++ з выкарыстаннем docker/docker-compose і пакетнага мэнэджара conan.

Падчас чарговага хакатона, у якім я ўдзельнічаў у якасці бэкенд-распрацоўніка, паўстала пытанне аб тым, на чым пісаць чарговы мікрасэрвіс. Усё, што было напісана на бягучы момант, пісалася мной і маім таварышам на мове Python, бо мой калега быў адмыслоўцам у гэтай вобласці і прафесійна займаўся распрацоўкай бэкендаў, у той час як я наогул з'яўляўся распрацоўнікам пад убудаваныя сістэмы і пісаў на вялікім і жудасным З++, а Python проста падвучыў ва ўніверсітэце.

Дык вось, перад намі паўстала задача напісаць высоканагружаны сэрвіс, асноўнай задачай якога быў прэпрацэсінг паступаючых да яго дадзеных і запіс іх у БД. І пасля чарговага перакуру таварыш прапанаваў мне, як З++ распрацоўніку, напісаць гэты сэрвіс на плюсах. Аргументуючы гэта тым, што так будзе хутчэй, больш прадукцыйна, ды і наогул, журы будуць у захапленні ад таго, як мы ўмеем распараджацца рэсурсамі каманды. На што я адказаў, што ніколі не займаўся такімі рэчамі на З++ і з лёгкасцю магу пакінутыя 20+ гадзін прысвяціць пошуку, кампіляцыі і кампаноўцы падыходных бібліятэк. Прасцей кажучы, я збаяўся. На тым і парашылі і спакойна дапісалі ўсё на Python.

Цяпер жа, падчас змушанай самаізаляцыі я вырашыўся разабрацца ў тым, як пісаць сэрвісы на З++. Першае, што трэба было зрабіць, гэта вызначыцца з прыдатнай бібліятэкай. Мой выбар упаў на спачываюць, бо яна была напісана ў аб'ектна-арыентаваным стылі, а таксама магла пахваліцца нармальнай дакументацыяй. Таксама, паўстала пытанне аб выбары сістэмы зборкі. Я да гэтага моманту працаваў толькі з Visual Studio, IAR і "голымі" makefile. І ні адна з гэтых сістэм мяне не вабіла, бо я планаваў запускаць увесь сэрвіс у docker-кантэйнеры. Тады я вырашыў паспрабаваць разабрацца з cmake і цікавым пакетным мэнэджэрам конан. Гэты пакетны мэнэджар дазваляў прапісаць усе залежнасці ў адным файле.

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

[generators] cmake

і з дапамогай простай каманды "conan install ." устанавіць неабходныя бібліятэкі. Натуральна, таксама патрабавалася ўнесці змены ў

CMakeLists.txt

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

Пасля гэтага я пачаў шукаць бібліятэку для працы з PostgreSQL, бо менавіта з ёй у мяне меўся невялікі досвед працы, а таксама менавіта з ёй узаемадзейнічалі нашы сэрвісы на Python. І ведаеце, што я даведаўся? Яна ёсць у POCO! Але conan не ведае, што яна ёсць у POCO і не ўмее яе білдзіць, у рэпазітары ляжыць састарэлы канфігурацыйны файл (я ўжо напісаў аб гэтай памылцы стваральнікам POCO). А значыць, давядзецца шукаць іншую бібліятэку.

І тады мой выбар упаў на менш папулярную бібліятэку libpg. І мне невымоўна пашанцавала, яна ўжо была ў conan і нават збіралася і кампанавалася.

Наступным крокам было напісанне шаблону сэрвісу, які ўмее апрацоўваць запыты.
Мы павінны атрымаць у спадчыну наш клас TemplateServerApp ад Poco::Util::ServerApplication і перавызначыць метад 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<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;
}

У метадзе main мы павінны задаць параметры: порт, колькасць плыняў і памер чаргі. А самае галоўнае, павінны задаць апрацоўшчык уваходных запытаў. Робіцца гэта праз стварэнне фабрыкі

TemplateRequestHandlerFactory

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

У маім выпадку яна проста кожны раз стварае адзін і той жа апрацоўшчык - TemplateServerAppHandler. Менавіта тут мы і можам размясціць нашу бізнэс-логіку.

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

Таксама я стварыў шаблон класа для працы з PostgreSQL. Для таго, каб выканаць просты SQL, напрыклад стварыць табліцу, ёсць метад ExecuteSQL(). Для больш складаных запытаў або атрымання дадзеных давядзецца атрымліваць connection праз GetConnection() і выкарыстоўваць API libpg. (Магчыма потым я выпраўлю гэтую несправядлівасць).

База дадзеных

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

Усе параметры для падлучэння да базы дадзеных бяруцца з асяроддзя, так што вам таксама трэба стварыць і наладзіць файл.

.env

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

Вы можаце паглядзець увесь код на гітхабе.

Мікрасэрвісы на З++. Выдумка ці рэальнасць?

І надышоў апошні этап напісання dockerfile і docker-compose.yml. Скажу сапраўды, на гэта сышла большая частка часу, і не толькі таму, што я нуб, што неабходна было кожны раз перазбіраць бібліятэкі, а з-за падводных камянёў conan. Так напрыклад, для таго, каб conan запампаваў, усталяваў і пабіў неабходныя залежнасці, яму мала запампаваць "conan install.", яму таксама неабходна перадаць параметр -s compiler.libcxx=libstdc++11, інакш вы рызыкуеце атрымаць кучу памылак на этапе кампаноўкі вашага дадатку. Я прасядзеў з гэтай памылкай некалькі гадзін, і спадзяюся, што гэты артыкул дапаможа іншым людзям вырашыць гэтую праблему за карацейшы час.

Далей, пасля напісання docker-compose.yml, па парадзе свайго таварыша я дадаў падтрымку cookiecutter і зараз вы можаце атрымаць сабе паўнавартасны шаблон для REST API сэрвісу на З++, з настроеным асяроддзем, і паднятай PostgreSQL, проста увёўшы ў кансоль «cookiecutter https://github.com/KovalevVasiliy/cpp_rest_api_template.git». А затым "docker-compose up -build".

Спадзяюся, дадзены шаблон дапаможа пачаткоўцам на іх нялёгкім шляху распрацоўкі REST API прыкладанняў на вялікай і магутнай, але такой непаваротлівай мове, як З++.
Таксама, я вельмі рэкамендую прачытаць вось гэтую артыкул. У ёй падрабязней тлумачыцца як працаваць з POCO і напісаць свой REST API сэрвіс.

Крыніца: habr.com

Дадаць каментар