У гэтым артыкуле я раскажу пра тое, як стварыў шаблон (cookiecutter) і наладзіў асяроддзе для напісання REST API сэрвісу на З++ з выкарыстаннем docker/docker-compose і пакетнага мэнэджара conan.
Падчас чарговага хакатона, у якім я ўдзельнічаў у якасці бэкенд-распрацоўніка, паўстала пытанне аб тым, на чым пісаць чарговы мікрасэрвіс. Усё, што было напісана на бягучы момант, пісалася мной і маім
Дык вось, перад намі паўстала задача напісаць высоканагружаны сэрвіс, асноўнай задачай якога быў прэпрацэсінг паступаючых да яго дадзеных і запіс іх у БД. І пасля чарговага перакуру таварыш прапанаваў мне, як З++ распрацоўніку, напісаць гэты сэрвіс на плюсах. Аргументуючы гэта тым, што так будзе хутчэй, больш прадукцыйна, ды і наогул, журы будуць у захапленні ад таго, як мы ўмеем распараджацца рэсурсамі каманды. На што я адказаў, што ніколі не займаўся такімі рэчамі на З++ і з лёгкасцю магу пакінутыя 20+ гадзін прысвяціць пошуку, кампіляцыі і кампаноўцы падыходных бібліятэк. Прасцей кажучы, я збаяўся. На тым і парашылі і спакойна дапісалі ўсё на Python.
Цяпер жа, падчас змушанай самаізаляцыі я вырашыўся разабрацца ў тым, як пісаць сэрвісы на З++. Першае, што трэба было зрабіць, гэта вызначыцца з прыдатнай бібліятэкай. Мой выбар упаў на
conanfile.txt
[requires] poco/1.9.3
libpq/11.5
і з дапамогай простай каманды "conan install ." устанавіць неабходныя бібліятэкі. Натуральна, таксама патрабавалася ўнесці змены ў
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries(<target_name> ${CONAN_LIBS})
Пасля гэтага я пачаў шукаць бібліятэку для працы з PostgreSQL, бо менавіта з ёй у мяне меўся невялікі досвед працы, а таксама менавіта з ёй узаемадзейнічалі нашы сэрвісы на Python. І ведаеце, што я даведаўся? Яна ёсць у POCO! Але conan не ведае, што яна ёсць у POCO і не ўмее яе білдзіць, у рэпазітары ляжыць састарэлы канфігурацыйны файл (я ўжо напісаў аб гэтай памылцы стваральнікам POCO). А значыць, давядзецца шукаць іншую бібліятэку.
І тады мой выбар упаў на менш папулярную бібліятэку
Наступным крокам было напісанне шаблону сэрвісу, які ўмее апрацоўваць запыты.
Мы павінны атрымаць у спадчыну наш клас 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, па парадзе свайго таварыша я дадаў падтрымку
Спадзяюся, дадзены шаблон дапаможа пачаткоўцам на іх нялёгкім шляху распрацоўкі REST API прыкладанняў на вялікай і магутнай, але такой непаваротлівай мове, як З++.
Таксама, я вельмі рэкамендую прачытаць вось
Крыніца: habr.com