
Në këtë artikull do të flas për mënyrën si krijova një template (cookiecutter) dhe konfigurita ambientin për zhvillimin e një shërbimi REST API në C++ duke përdorur docker/docker-compose dhe menaxherin e paketave conan.
Gjatë një hackathoni tjetër, ku unë merrja pjesë si zhvillues backend, u shtrua çështja se mbi çfarë do të shkruhej mikrosherbimi i radhës. Gjithçka që ishte shkruar deri në atë moment, ishte shkruar nga unë dhe miku im në gjuhën Python, pasi kolegu im ishte specialist në këtë fushë dhe merrej profesionalisht me zhvillimin e backend-eve, ndërsa unë isha në thelb një zhvillues për sisteme të integruara dhe shkruaja në të madhen dhe të tmerrshmen C++, e kurse Python e kisha mësuar pak në universitet.
Pra, përpara nesh doli detyra për të shkruar një shërbim me ngarkesë të lartë, me detyrën kryesore të përpunimit të të dhënave që i vijnë dhe ruajtjen e tyre në DB. Dhe pas një pauze të zakonshme, shoku më sugjeroi, si zhvillues C++, që ta shkruaja këtë shërbim në C++. Duke argumentuar se do të ishte më shpejt, më produktiv, dhe gjithashtu, juria do të ishte e impresionuar nga aftësitë tona për të menaxhuar burimet e ekipit. Për këtë, unë u përgjigja se nuk kisha bërë kurrë diçka të tillë në C++ dhe mund të kaloja lehtësisht 20+ orët e mbetura duke kërkuar, kompajluar dhe lidhur libraritë e përshtatshme. Në thelb, u tremba. Kështu u vendos dhe përfundimisht e shkruam gjithçka në Python.
Tani, gjatë izolimit të detyruar, vendosa të merrem me atë se si të shkruaja shërbime në C++. E para që duhej bërë ishte të përcaktoja një bibliotekë të përshtatshme. Zgjedhja ime ra mbi , pasi ajo ishte shkruar në stilin objekt-orientues dhe gjithashtu kishte dokumentacion të mirë. Gjithashtu, u shtrua çështja e zgjedhjes së sistemit të ndërtimit. Deri në atë moment kisha punuar vetëm me Visual Studio, IAR dhe "makefile" të pastër. Asnjë nga këto sisteme nuk më entuziazmonte, pasi planifikoja të aktivizoja të gjithë shërbimin në një kontejner docker. Atëherë vendosa të përpiqem të kuptoj cmake dhe një menaxher paketash interesant, . Ky menaxher paketash lejonte të shkruaja të gjitha varësitë në një skedë të vetme
conanfile.txt
[requires]
poco/1.9.3
libpq/11.5
[generators]
cmake
dhe me një komandë të thjeshtë "conan install ." të instaloj libraritë e nevojshme. Natyrisht, gjithashtu do të ishte e nevojshme të bëja ndryshime në
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries( ${CONAN_LIBS})
Pas kësaj, fillova të kërkoj një bibliotekë për të punuar me PostgreSQL, pasi që kisha disa përvojë me të, si dhe shërbimet tona në Python bashkëpunonin me të. Dhe e dini çfarë zbuloj? Ajo është në POCO! Por conan nuk e di që ajo është në POCO dhe nuk di ta ndërtojë atë, në depo ndodhet një skedë konfiguruese e vjetruar (kisha shkruar tashmë për këtë gabim tek krijuesit e POCO). Pra, do të duhet të kërkoj një bibliotekë tjetër.
Dhe kështu, zgjedhja ime ra te një bibliotekë më pak të njohur . Dhe kam pasur një fat të madh, ajo ishte tashmë në conan dhe madje ishte ndërtuar dhe kompozuar.
Hapi tjetër ishte të shkruaja një shembull shërbimi që mund të trajtojë kërkesat.
Ne duhet ta trashëgojmë klasën tonë TemplateServerApp nga Poco::Util::ServerApplication dhe të rivendosim metodën 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 << "Server started" << endl;
waitForTerminationRequest(); // wait for CTRL-C or kill
cerr << "Shutting down..." << endl;
s.stop();
return Application::EXIT_OK;
}Në metodën main duhet të përcaktojmë parametrat: portin, sasinë eThreads dhe madhësinë e radhës. Dhe e gjithë e rëndësishmja, duhet të përcaktojmë trajtuesin për kërkesat që vijnë. Kjo bëhet përmes krijimit të një fabrike
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};Në rastin tim, ajo thjesht krijon çdo herë të njëjtin trajtues — TemplateServerAppHandler. Pikërisht këtu mund të vendosim logjikën tonë të biznesit.
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();
}
};Gjithashtu kam krijuar një shabllon klase për të punuar me PostgreSQL. Për të ekzekutuar një SQL të thjeshtë, si p.sh. krijimi i një tavoline, ekziston metoda ExecuteSQL(). Për kërkesa më të ndërlikuara ose për të marrë të dhëna, do t'ju duhet të merrni lidhjen përmes GetConnection() dhe të përdorni API-në e libpg. (Ndoshta më vonë do ta korrigjoj këtë padrejtësi).
Baza e të dhënave
#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;
};Të gjithë parametrat për lidhjen me bazën e të dhënave merren nga mjedisi, kështu që duhet të krijoni dhe konfiguroni gjithashtu skedarin .env
.env
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432Mund ta shikoni të gjithë kodin në

Dhe erdhi radha e fundit për të shkruar dockerfile dhe docker-compose.yml. Do ta them sinqerisht, kjo mori pjesën më të madhe të kohës, dhe jo vetëm për shkak se isha fillestar, që ishte e nevojshme të rindërtohej çdo herë libraritë, por për shkak të pengesave me conan. Për shembull, që conan të shkarkojë, instaloje dhe ndërtojnë varësitë e nevojshme, nuk mjafton të shkruhet «conan install .», duhet të transmetoni gjithashtu parametrin -s compiler.libcxx=libstdc++11, nd otherwise you risk getting a lot of errors at the linking stage of your application. Kam kaluar disa orë me këtë gabim, dhe shpresoj se ky artikull do t'i ndihmojë të tjerët ta zgjidhin këtë problem në një kohë më të shkurtër.
Më pas, pas shkruarjes së docker-compose.yml, me rekomandimin e mikut tim, kam shtuar mbështetje dhe tani mund të merrni një shabllon të plotë për një shërbim REST API në C++, me një mjedis të konfiguruar, dhe një PostgreSQL të ngritur, thjesht duke shkruar në konsolën «cookiecutter ». Pastaj «docker-compose up —build».
Shpresoj që ky shabllon të ndihmojë të rinjtë në rrugën e tyre të vështirë të zhvillimit të aplikacioneve REST API në gjuhën e madhe dhe të fuqishme, por kaq të ngadalshme si C++.
Gjithashtu, ju rekomandoj shumë të lexoni këtë Në të, shpjegohet më në detaje se si të punoni me POCO dhe të shkruani shërbimin tuaj REST API.
Burimi: habr.com
