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

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

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

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

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

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

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

[генератори] 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 и да заменим главния метод.

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

В основния метод трябва да зададем параметрите: порт, брой нишки и размер на опашка. И най-важното, трябва да посочите манипулатор за входящи заявки. Това става чрез създаване на фабрика

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

База данни

#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, по съвет на мой приятел добавих поддръжка форма за сладки и сега можете да получите пълноценен шаблон за REST API услуга в C++, с персонализирана среда и инсталиран PostgreSQL, просто като въведете „cookiecutter“ в конзолата https://github.com/KovalevVasiliy/cpp_rest_api_template.git" И след това „docker-compose up —build“.

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

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

Добавяне на нов коментар