Микросервизи на C++. Измислица или реалност?

Микросервизи на C++. Измислица или реалност?

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

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

И така, пред нас стоеше задачата да напишем високонагружен сервис, основната задача на който беше препроцесирането на получаващите се данни и записването им в базата данни. И след поредната почивка, приятелят ми предложи, като разработчик на C++, да напиша този сервис на 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.

Микросервизи на C++. Измислица или реалност?

И дойде последният етап на написването на 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