U ovom članku ću govoriti o tome kako sam kreirao šablon (cookiecutter) i podesio okruženje za pisanje REST API servisa u C++ koristeći docker/docker-compose i conan menadžer paketa.
Tokom sljedećeg hackathona, u kojem sam učestvovao kao backend developer, postavilo se pitanje šta koristiti za pisanje sljedećeg mikroservisa. Sve što je do sada napisano napisali smo ja i moji
Dakle, bili smo suočeni sa zadatkom da napišemo servis sa velikim opterećenjem, čiji je glavni zadatak bio da predobradi podatke koji mu dolaze i upiše ih u bazu podataka. I nakon još jedne pauze za dim, prijatelj mi je predložio da ja, kao C++ programer, napišem ovu uslugu koristeći profesionalce. Tvrdi se da će to biti brže, produktivnije i općenito, žiri će biti oduševljen kako znamo upravljati resursima tima. Na šta sam odgovorio da nikada nisam radio takve stvari u C++-u i da bih lako mogao posvetiti preostalih 20+ sati traženju, kompajliranju i povezivanju odgovarajućih biblioteka. Jednostavnije rečeno, propao sam. Na to smo se odlučili i mirno završili sve u Pythonu.
Sada, tokom prisilne samoizolacije, odlučio sam da smislim kako da napišem servise na C++. Prvo što je trebalo učiniti je odlučiti se za odgovarajuću biblioteku. Moj izbor je pao
conanfile.txt
[zahtijeva]poco/1.9.3
libpq/11.5
i jednostavnom komandom "conan install ." instalirati potrebne biblioteke. Naravno, bilo je potrebno i izvršiti promjene u
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries(<target_name> ${CONAN_LIBS})
Nakon toga sam počeo da tražim biblioteku za rad sa PostgreSQL-om, budući da je to bila ona sa kojom sam imao malo iskustva u radu, a takođe je bila i ona sa kojom su komunicirali naši Python servisi. I znate li šta sam naučio? U POCO je! Ali conan ne zna da je u POCO-u i ne zna kako da ga napravi; u spremištu postoji zastarjela konfiguracijska datoteka (već sam pisao o ovoj grešci kreatorima POCO-a). To znači da ćete morati potražiti drugu biblioteku.
A onda je moj izbor pao na manje popularnu biblioteku
Sljedeći korak je bio pisanje predloška usluge koji može obraditi zahtjeve.
Moramo naslijediti našu TemplateServerApp klasu od Poco::Util::ServerApplication i nadjačati glavni metod.
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;
}
U glavnoj metodi moramo postaviti parametre: port, broj niti i veličinu reda. I što je najvažnije, morate navesti rukovatelja za dolazne zahtjeve. To se radi stvaranjem fabrike
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};
U mom slučaju, jednostavno svaki put kreira isti rukovalac - TemplateServerAppHandler. Tu možemo postaviti našu poslovnu logiku.
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();
}
};
Napravio sam i predložak klase za rad sa PostgreSQL-om. Za izvođenje jednostavnog SQL-a, kao što je kreiranje tablice, postoji metoda ExecuteSQL(). Za složenije upite ili pronalaženje podataka, morat ćete uspostaviti vezu putem GetConnection() i koristite libpg API. (Možda ću kasnije ispraviti ovu nepravdu).
baza podataka
#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;
};
Svi parametri za povezivanje sa bazom podataka preuzeti su iz okruženja, tako da je potrebno kreirati i konfigurisati .env fajl
.NS
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432
Sve kodove možete vidjeti na
I sada dolazi zadnja faza pisanja dockerfile-a i docker-compose.yml. Da budem iskren, to mi je oduzimalo većinu vremena, i to ne samo zato što sam noob, jer je svaki put bilo potrebno obnavljati biblioteke, već i zbog zamki Conana. Na primjer, da bi conan preuzeo, instalirao i izgradio potrebne ovisnosti, nije dovoljno da preuzme "conan install .", već mora proći i parametar -s compiler.libcxx=libstdc++11, inače rizikujete da dobijete gomilu grešaka u fazi povezivanja vaše aplikacije. Zaglavio sam s ovom greškom nekoliko sati i nadam se da će ovaj članak pomoći drugim ljudima da riješe ovaj problem za kraće vrijeme.
Zatim, nakon što sam napisao docker-compose.yml, po savjetu mog prijatelja, dodao sam podršku
Nadam se da će ovaj šablon pomoći početnicima na njihovom teškom putu razvoja REST API aplikacija na sjajnom i moćnom, ali tako nezgrapnom jeziku kao što je C++.
Također, toplo preporučujem čitanje ovdje
izvor: www.habr.com