Микросервизи на С++. Фантастика или реалност?

Микросервизи на С++. Фантастика или реалност?

В тази статия ще споделя как създадох шаблон (cookiecutter) и настроих среда за разработка на REST API услуга на C++ с използване на docker/docker-compose и пакетния мениджър conan.

По време на поредния хакатон, в който участвах като бекенд-разработчик, възникна въпросът с какво да напишем следващия микросервис. Всичко, което беше написано дотук, беше мое и на моя приятел на езика Python, тъй като колегата ми беше специалист в тази област и професионално се занимаваше с разработка на бекенди, докато аз всъщност бях разработчик на вградени системи и пишех на великото и ужасно C++, а Python просто го бях изучил в университета.

И така, пред нас се изправи задачата да напишем високо натоварена услуга, чиято основна задача беше предварителна обработка на постъпващите данни и записването им в БД. След поредната почивка, приятелят ми предложи, като C++ разработчик, да напиша тази услуга на плюсове. Аргументирайки, че така ще бъде по-бързо, по-продуктивно и въобще, журито ще бъде впечатлено от това как умеем да управляваме ресурсите на екипа. На което отговорих, че никога не съм се занимавал с такива неща на C++ и с лекота мога да посветя останалите 20+ часа на търсене, компилиране и компоновка на подходящи библиотеки. Инак казано, се уплаших. На това приключихме и спокойно дописахме всичко на Python.

Сега, по време на принудителна самоизолация, реших да разбера как да пиша услуги на C++. Първото, което трябваше да направя, беше да се определи с подходящата библиотека. Изборът ми падна на POCO, тъй като беше написана в обектно-ориентиран стил и можеше да се похвали с нормална документация. Освен това, възникна въпросът за избора на система за компилация. До този момент работех само с Visual Studio, IAR и „голи“ makefile. И нито една от тези системи не ме привличаше, тъй като планирах да стартирам цялата услуга в docker-контейнер. Тогава реших да опитам да се запозная с cmake и интересния пакетен мениджър conan. Този пакетен мениджър позволяваше да опиша всички зависимости в един файл

conanfile.txt
[изисквания]
poco/1.9.3
libpq/11.5

[генератори]
cmake

и с помощта на простата команда „conan install .“ да инсталирам необходимите библиотеки. Разбира се, трябваше също така да внеса промени в

CMakeLists.txt

include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries( ${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 &)
{
    HTTPServerParams* pParams = new HTTPServerParams;

    pParams->setMaxQueued(100);
    pParams->setMaxThreads(16);

    HTTPServer s(new TemplateRequestHandlerFactory, ServerSocket(8000), pParams);

    s.start();
    cerr << "Сървърът стартира" << endl;

    waitForTerminationRequest();  // изчакайте CTRL-C или убийте

    cerr << "Затваряне..." << 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 << "Метод: " << 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(). За по-сложни заявки или за получаване на данни ще трябва да вземете връзка чрез 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

.env

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

Можете да видите целия код на GitHub.

Микросервизи на С++. Фантастика или реалност?

И настъпи последният етап на написването на dockerfile и docker-compose.yml. Да кажа честно, това отне голяма част от времето, и не само защото съм начинаещ, който постоянно трябваше да изгражда библиотеките наново, а заради подводните камъни на conan. Например, за да може conan да изтегли, инсталира и компилира необходимите зависимости, не е достатъчно просто да се изпълни "conan install .", но също така трябва да се предаде параметър -s compiler.libcxx=libstdc++11, иначе рискувате да получите куп грешки на етапа на свързване на приложението. Прекарах няколко часа с тази грешка и се надявам, че тази статия ще помогне на другите хора да решат този проблем по-бързо.

След написването на docker-compose.yml, по съвет на моя приятел добавих поддръжка на cookiecutter и сега можете да получите пълен шаблон за REST API услуга на C++, с настроено окружение и стартирана PostgreSQL, просто въвеждайки "cookiecutter https://github.com/KovalevVasiliy/cpp_rest_api_template.git" в конзолата. А след това "docker-compose up --build".

Надявам се, че този шаблон ще помогне на новаците в техния труден път на разработване на REST API приложения на великкия и могъщ, но толкова тромав език, какъвто е C++.
Също така, силно препоръчвам да прочетете тази статия. В нея по-подробно се обяснява как да работите с POCO и да напишете своя REST API услуга.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster