
In diesem Artikel erkläre ich, wie ich ein Template (Cookiecutter) erstellt und die Umgebung für die Entwicklung eines REST API Services in C++ mit Docker/Docker-Compose und dem Paketmanager Conan eingerichtet habe.
Während eines Hackathons, an dem ich als Backend-Entwickler teilnahm, stellte sich die Frage, worauf wir den nächsten Microservice entwickeln sollten. Alles, was bisher geschrieben wurde, wurde von mir und meinem in Python umgesetzt, da mein Kollege ein Spezialist auf diesem Gebiet war und professionell an Backend-Lösungen arbeitete, während ich ursprünglich Embedded Systems Entwickler war und auf dem großartigen und furchtbaren C++ programmierte, während ich Python nur während meines Studiums erlernt hatte.
Nun standen wir vor der Herausforderung, einen hochbelastbaren Service zu entwickeln, dessen Hauptaufgabe die Vorverarbeitung der eingehenden Daten und deren Speicherung in einer Datenbank war. Nach einer weiteren Kaffeepause schlug ein Kollege, als C++-Entwickler, vor, diesen Service in C++ zu schreiben. Er argumentierte, dass dies schneller und leistungsfähiger wäre und das Jury-Feedback positiv ausfallen würde, da wir zeigen könnten, wie gut wir die Ressourcen des Teams nutzen. Daraufhin erwiderte ich, dass ich nie zuvor mit solchen Aufgaben in C++ gearbeitet hatte und mit Leichtigkeit die restlichen 20+ Stunden damit verbringen könnte, geeignete Bibliotheken zu suchen, zu kompilieren und zu verlinken. Kurz gesagt, ich hatte Angst. So einigten wir uns darauf, alles in Python weiter zu schreiben.
Jetzt, während der gegebenen Selbstisolation, habe ich mich entschlossen, zu lernen, wie man Services in C++ schreibt. Das erste, was ich tun musste, war, die passende Bibliothek zu wählen. Meine Wahl fiel auf , da es objektorientiert geschrieben wurde und über eine angemessene Dokumentation verfügte. Auch die Wahl des Build-Systems war eine Überlegung wert. Bis zu diesem Zeitpunkt hatte ich nur mit Visual Studio, IAR und „reinen“ Makefiles gearbeitet. Keines dieser Systeme sprach mich an, da ich plante, den gesamten Dienst in einem Docker-Container zu betreiben. Daher beschloss ich, mich mit CMake und einem 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 mithilfe des einfachen Befehls „conan install .“ konnten die benötigten Bibliotheken installiert werden. Natürlich mussten auch Änderungen an
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries( ${CONAN_LIBS})
Danach begann ich, eine Bibliothek für die Arbeit mit PostgreSQL zu suchen, da ich damit bereits einige Erfahrungen gesammelt hatte und unsere Dienste in Python darauf zugriffen. Und wissen Sie, was ich herausgefunden habe? Sie ist in POCO verfügbar! Aber Conan wusste nicht, dass sie in POCO vorhanden ist und kann sie nicht bauen; im Repository befindet sich eine veraltete Konfigurationsdatei (ich habe die Entwickler von POCO bereits über diesen Fehler informiert). Das bedeutet, ich muss nach einer anderen Bibliothek suchen.
Also fiel meine Wahl auf eine weniger bekannte Bibliothek . Und ich hatte großes Glück, sie war bereits in Conan vorhanden und ließ sich sogar kompilieren und links.
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;
}Im Hauptteil der Methode müssen wir Parameter festlegen: Port, Anzahl der Threads und die Größe der Warteschlange. Und am wichtigsten, wir müssen einen 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 den gleichen 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 << "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();
}
};Ich habe auch eine Klassenschablone für die Arbeit mit PostgreSQL erstellt. Um eine einfache SQL-Abfrage auszuführen, wie zum Beispiel eine Tabelle zu erstellen, gibt es die Methode ExecuteSQL(). Für komplexere Abfragen oder zum Abrufen von Daten müssen Sie die Verbindung über GetConnection() herstellen und die API libpg 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 die Datei .env 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 nun ist die letzte Etappe beim Schreiben der dockerfile und docker-compose.yml erreicht. Ehrlich gesagt hat das den Großteil der Zeit in Anspruch genommen, und das nicht nur, weil ich ein Anfänger bin, der ständig die Bibliotheken neu kompilieren musste, sondern auch wegen der Fallstricke von conan. Damit conan die notwendigen Abhängigkeiten herunterladen, installieren und erstellen kann, reicht es nicht aus, einfach "conan install ." auszuführen. Es ist auch erforderlich, den Parameter -s compiler.libcxx=libstdc++11 zu übergeben, sonst riskiert man eine Vielzahl von Fehlern beim Verlinken der Anwendung. Ich habe mehrere Stunden mit diesem Fehler verbracht und hoffe, dass dieser Artikel anderen hilft, dieses Problem schneller zu lösen.
Als nächstes, nach dem Schreiben der docker-compose.yml, habe ich auf den Rat eines Freundes hin Unterstützung hinzugefügt und jetzt können Sie sich einfach ein voll funktionsfähiges Template für einen REST-API-Dienst in C++ mit einer konfigurierten Umgebung und einer laufenden PostgreSQL-Datenbank holen, indem Sie einfach in die Konsole "cookiecutter " eingeben. Und dann "docker-compose up —build".
Ich hoffe, dass dieses Template Anfängern auf ihrem beschwerlichen Weg zur Entwicklung von REST-API-Anwendungen in der großartigen, mächtigen, aber schwerfälligen Sprache C++ hilft.
Auch empfehle ich dringend, dies hier zu lesen. In diesem Artikel wird näher erläutert, wie man mit POCO arbeitet und einen eigenen REST API-Service erstellt.
Quelle: habr.com
