
In questo articolo parlerò di come ho creato un template (cookiecutter) e configurato l'ambiente per scrivere un servizio REST API in C++ utilizzando docker/docker-compose e il gestore di pacchetti conan.
Durante un recente hackathon in cui ho partecipato come sviluppatore backend, si è posto il problema di quale tecnologia utilizzare per scrivere il nostro prossimo microservizio. Tutto ciò che era stato scritto fino a quel momento era stato realizzato da me e dal mio in Python, poiché il mio collega era un esperto in questo campo e si occupava professionalmente dello sviluppo di backend, mentre io ero uno sviluppatore di sistemi embedded e scrivevo in C++, il cui uso avevo soltanto approfondito all'università.
Detto ciò, ci siamo trovati di fronte all'obiettivo di scrivere un servizio ad alta capacità, il cui compito principale era il preprocessing dei dati in ingresso e la registrazione in un database. Dopo una pausa, il mio compagno mi ha proposto, come sviluppatore C++, di scrivere questo servizio in C++. Argomentando che così sarebbe stato più veloce e performante, e che, insomma, la giuria sarebbe stata impressionata dalla nostra capacità di gestire le risorse del team. A cui ho risposto che non avevo mai affrontato problemi simili in C++ e che avrei potuto facilmente dedicare le restanti 20+ ore alla ricerca, compilazione e collegamento di librerie adatte. In parole povere, ho esitato. Così abbiamo deciso di continuare e completare tutto in Python.
Ora, durante il periodo forzato di isolamento, ho deciso di approfondire il modo di scrivere servizi in C++. La prima cosa da fare era scegliere la libreria giusta. La mia scelta è ricaduta su , poiché era scritta in uno stile orientato agli oggetti e vantava una documentazione di qualità. Inoltre, si presentava la questione di quale sistema di build adottare. Prima di quel momento, avevo lavorato solo con Visual Studio, IAR e 'bare' makefile. Nessuno di questi sistemi mi attirava, poiché avevo pianificato di eseguire l'intero servizio in un container docker. Così ho deciso di provare a familiarizzare con cmake e il interessante gestore di pacchetti . Questo gestore di pacchetti permetteva di specificare tutte le dipendenze in un unico file
conanfile.txt
[requires]
poco/1.9.3
libpq/11.5
[generators]
cmake
e utilizzando il semplice comando 'conan install .' era possibile installare le librerie necessarie. Naturalmente, era anche necessario apportare modifiche a
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries( ${CONAN_LIBS})
Dopo questo ho iniziato a cercare una libreria per lavorare con PostgreSQL, poiché avevo un po' di esperienza con essa e anche i nostri servizi in Python interagivano con essa. E sapete cosa ho scoperto? È presente in POCO! Ma conan non sa che è in POCO e non sa come compilarla, nel repository c'è un file di configurazione obsoleto (ho già informato i creatori di POCO di questo errore). Quindi, dovrò cercare un'altra libreria.
E così la mia scelta è ricaduta su una libreria meno popolare . E sono stato davvero fortunato, era già presente in conan e veniva persino compilata e linkata.
Il passo successivo è stato scrivere il modello di un servizio capace di gestire le richieste.
Dobbiamo ereditare la nostra classe TemplateServerApp da Poco::Util::ServerApplication e sovrascrivere il metodo 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 avviato" << endl;
waitForTerminationRequest(); // aspetta CTRL-C o kill
cerr << "Spegnimento..." << endl;
s.stop();
return Application::EXIT_OK;
}Nel metodo main dobbiamo impostare i parametri: porta, numero di thread e dimensione della coda. E cosa più importante, dobbiamo impostare il gestore delle richieste in arrivo. Questo viene fatto creando una fabbrica
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};Nel mio caso, crea semplicemente ogni volta lo stesso gestore: TemplateServerAppHandler. È qui che possiamo collocare la nostra logica di business.
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 << "Metodo: " << 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();
}
};Ho anche creato un modello di classe per lavorare con PostgreSQL. Per eseguire una semplice query SQL, come ad esempio creare una tabella, c'è un metodo ExecuteSQL(). Per query più complesse o per ottenere dati, sarà necessario ottenere la connessione tramite GetConnection() e utilizzare l'API libpg. (Forse in seguito correggerò questa ingiustizia).
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;
};Tutti i parametri per la connessione al database vengono presi dall'ambiente, quindi dovete anche creare e configurare un file .env
.env
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432Potete vedere tutto il codice su

E siamo arrivati all'ultima fase di scrittura del dockerfile e docker-compose.yml. A dire il vero, questo ha preso la maggior parte del tempo, non solo perché sono un principiante e dovevo ricompilare le librerie ogni volta, ma a causa delle insidie di conan. Ad esempio, affinché conan scarichi, installi e compili le dipendenze necessarie, non basta eseguire "conan install .", deve anche ricevere l'argomento -s compiler.libcxx=libstdc++11, altrimenti rischiate di ottenere una valanga di errori durante la fase di link del vostro applicativo. Ho passato diverse ore con questo errore e spero che questo articolo aiuti altre persone a risolvere il problema in meno tempo.
Successivamente, dopo aver scritto il docker-compose.yml, su consiglio di un amico ho aggiunto il supporto per e ora potete ottenere un modello completo per un servizio REST API in C++, con un ambiente configurato e PostgreSQL avviato, semplicemente digitando in console "cookiecutter ". E poi "docker-compose up --build".
Spero che questo modello aiuti i principianti nel loro difficile percorso di sviluppo di applicazioni REST API nel grande e potente, ma così ingombrante linguaggio di programmazione, come C++.
Inoltre, consiglio vivamente di leggere questo articolo. Spiega più nel dettaglio come lavorare con POCO e scrivere il proprio servizio REST API.
Fonte: habr.com
