
In diesem Artikel werde ich darüber berichten, wie ich eine Vorlage (cookiecutter) erstellt und die Umgebung für die Entwicklung eines REST-API-Dienstes in C++ mit Docker/Docker-Compose und dem Paketmanager Conan eingerichtet habe.
Während eines weiteren Hackathons, an dem ich als Backend-Entwickler teilnahm, stellte sich die Frage, in welcher Sprache wir den nächsten Mikrodienst schreiben sollten. Alles, was bis zu diesem Zeitpunkt geschrieben worden war, hatte ich und mein in Python geschrieben, da mein Kollege ein Spezialist auf diesem Gebiet war und professionell Backends entwickelte, während ich eigentlich Entwickler für Embedded-Systeme war und in dem großartigen und schrecklichen C++ programmierte, während ich Python nur an der Universität gelernt hatte.
So standen wir vor der Aufgabe, einen stark belastbaren Dienst zu schreiben, dessen Hauptaufgabe die Vorverarbeitung der eintreffenden Daten und deren Speicherung in einer Datenbank war. Nach einer weiteren Kaffeepause schlug mein Kollege mir, als C++-Entwickler, vor, diesen Dienst in C++ zu schreiben. Er argumentierte, dass es schneller und leistungsfähiger wäre und das Jurypanel begeistert sein würde von unserem Umgang mit den Ressourcen des Teams. Woraufhin ich antwortete, dass ich noch nie solche Dinge in C++ gemacht hatte und mit Leichtigkeit die verbleibenden 20+ Stunden mit der Suche, dem Kompilieren und dem Zusammenstellen passender Bibliotheken verbringen könnte. Einfach gesagt, ich hatte Angst. Daraufhin entschieden wir, alles in Python zu schreiben.
Jetzt, während der Zwangs-Selbstisolierung, habe ich mich entschlossen, herauszufinden, wie man Dienste in C++ schreibt. Das erste, was ich tun musste, war, eine geeignete Bibliothek auszuwählen. Meine Wahl fiel auf , da sie im objektorientierten Stil geschrieben war und zudem über eine ordentliche Dokumentation verfügte. Außerdem stellte sich die Frage nach dem Auswahl der Build-Systeme. Bis zu diesem Zeitpunkt hatte ich nur mit Visual Studio, IAR und "nackten" Makefiles gearbeitet. Und keines dieser Systeme hat mich gereizt, da ich plante, den gesamten Dienst in einem Docker-Container auszuführen. Daher entschloss ich mich, mich mit CMake und dem interessanten Paketmanager zu beschäftigen. Dieser Paketmanager ermöglichte es, alle Abhängigkeiten in einer Datei zu definieren.
conanfile.txt
[requires]
poco/1.9.3
libpq/11.5
[generators]
cmake
und mit dem einfachen Befehl „conan install .“ die notwendigen Bibliotheken zu installieren. Natürlich waren auch Änderungen in
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries( ${CONAN_LIBS})
Dan begann ich, eine Bibliothek für die Arbeit mit PostgreSQL zu suchen, da ich mit dieser bereits ein wenig Erfahrung hatte und unsere Dienste in Python damit interagierten. Und wissen Sie, was ich herausgefunden habe? Sie ist in POCO! Aber conan weiß nicht, dass sie in POCO vorhanden ist und kann sie nicht bauen; im Repository liegt eine veraltete Konfigurationsdatei (ich habe die Entwickler von POCO bereits auf diesen Fehler hingewiesen). Das bedeutet, ich muss eine andere Bibliothek suchen.
Und dann fiel meine Wahl auf eine weniger populäre Bibliothek . Und ich hatte das große Glück, dass sie bereits in conan enthalten war und sogar gebaut und verknüpft wurde.
Der nächste Schritt war das Schreiben einer Vorlage für einen Dienst, der Anfragen bearbeiten kann.
Wir müssen unsere Klasse TemplateServerApp von Poco::Util::ServerApplication ableiten und die Methode main überschreiben.
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 gestartet" << endl;
waitForTerminationRequest(); // warte auf CTRL-C oder kill
cerr << "Herunterfahren..." << endl;
s.stop();
return Application::EXIT_OK;
}In der Methode main müssen wir die Parameter angeben: Port, Anzahl der Threads und die Größe der Warteschlange. Und am wichtigsten ist, dass wir den Handler für eingehende Anfragen festlegen. Dies geschieht durch die Erstellung einer Fabrik
TemplateRequestHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};In meinem Fall erstellt sie jedes Mal denselben Handler – TemplateServerAppHandler. Hier können wir unsere Geschäftslogik unterbringen.
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 << "Methode: " << 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();
}
};Ich habe auch eine Klassenschablone für die Arbeit mit PostgreSQL erstellt. Um eine einfache SQL-Anweisung auszuführen, beispielsweise eine Tabelle zu erstellen, gibt es die Methode ExecuteSQL(). Für komplexere Abfragen oder zum Abrufen von Daten müssen Sie eine Verbindung über GetConnection() herstellen und die libpg-API verwenden. (Vielleicht werde ich diese Ungerechtigkeit später beheben).
Datenbank
#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;
};Alle Parameter für die Datenbankverbindung werden aus der Umgebung entnommen, daher müssen Sie auch eine .env-Datei erstellen und konfigurieren
.env
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432Sie können den gesamten Code auf

Und es kam die letzte Phase der Erstellung des Dockerfiles und der docker-compose.yml. Ehrlich gesagt hat dies den Großteil der Zeit in Anspruch genommen, und das nicht nur, weil ich ein Anfänger bin, der jedes Mal die Bibliotheken neu kompilieren musste, sondern wegen der Tücken von Conan. So muss beispielsweise Conan, um die erforderlichen Abhängigkeiten herunterzuladen, zu installieren und zu bauen, nicht nur "conan install ." ausführen, sondern auch den Parameter -s compiler.libcxx=libstdc++11 übergeben, sonst riskieren Sie, eine Menge Fehler im Kompilierungsprozess Ihrer Anwendung zu erhalten. Ich habe einige Stunden mit diesem Fehler verbracht und hoffe, dass dieser Artikel anderen Menschen hilft, dieses Problem schneller zu lösen.
Danach, nach dem Schreiben der docker-compose.yml, habe ich auf den Rat eines Freundes hin die Unterstützung für hinzugefügt und jetzt können Sie sich eine vollständige Vorlage für einen REST-API-Dienst in C++ mit konfiguriertem Umfeld und installiertem PostgreSQL holen, indem Sie einfach in die Konsole "cookiecutter " eingeben. Und dann "docker-compose up —build."
Ich hoffe, dass diese Vorlage Neuankömmlingen auf ihrem schwierigen Weg zur Entwicklung von REST-API-Anwendungen in der großartigen und mächtigen, aber so unhandlichen Sprache C++ helfen wird.
Außerdem empfehle ich dringend, diesen Artikel zu lesen. Darin wird ausführlicher erklärt, wie man mit POCO arbeitet und einen eigenen REST-API-Dienst erstellt.
Quelle: habr.com
