Микросервисы на С++. Выдумка или реальность?

—

от автора

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

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

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

Сейчас же, во время вынужденной самоизоляции я решился разобраться в том, как писать сервисы на С++. Первое, что нужно было сделать, это определиться с подходящей библиотекой. Мой выбор пал на POCO, так как она была написана в объектно-ориентированном стиле, а также могла похвастаться нормальной документацией. Также, встал вопрос о выборе системы сборки. Я до этого момента работал только с Visual Studio, IAR и «голыми» makefile. И ни одна из этих систем меня не прельщала, так как я планировал запускать весь сервис в docker-контейнере. Тогда я решил попробовать разобраться с cmake и интересным пакетным менеджером conan. Этот пакетный менеджер позволял прописать все зависимости в одном файле

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. (Возможно потом я исправлю эту несправедливость).

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

Все параметры для подключения к базе данных берутся из окружения, так что вам также нужно создать и настроить файл .env

.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 сервиса на С++, c настроенным окружением, и поднятой PostgreSQL, просто введя в консоль «cookiecutter https://github.com/KovalevVasiliy/cpp_rest_api_template.git». А затем «docker-compose up —build».

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

ссылка на оригинал статьи https://habr.com/ru/post/501330/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *