Mikroslužby v C++. Fikce nebo realita?

Mikroslužby v C++. Fikce nebo realita?

V tomto článku budu mluvit o tom, jak jsem vytvořil šablonu (cookiecutter) a nastavil prostředí pro psaní služby REST API v C++ pomocí docker/docker-compose a správce balíčků conan.

Během dalšího hackathonu, kterého jsem se účastnil jako backend developer, vyvstala otázka, co použít k napsání další mikroslužby. Vše, co bylo dosud napsáno, jsem napsal já a můj společník v Pythonu, protože můj kolega byl odborníkem v této oblasti a profesionálně vyvíjel backendy, zatímco já jsem byl obecně vývojář embedded systémů a psal ve skvělém a hrozném C++, a Python jsem se právě naučil na univerzitě.

Stáli jsme tedy před úkolem napsat vysoce zátěžovou službu, jejímž hlavním úkolem bylo předzpracovat data, která k ní přicházejí, a zapsat je do databáze. A po další kouřové přestávce mi přítel navrhl, abych jako vývojář C++ napsal tuto službu pomocí profesionálů. Argumentuje tím, že to bude rychlejší, produktivnější a obecně bude porota potěšena tím, jak víme, jak spravovat zdroje týmu. Na což jsem odpověděl, že jsem takové věci v C++ nikdy nedělal a zbývajících 20+ hodin bych mohl klidně věnovat hledání, kompilaci a propojování vhodných knihoven. Jednoduše řečeno, zbláznil jsem se. To jsme se rozhodli a v klidu vše dokončili v Pythonu.

Nyní, během nucené sebeizolace, jsem se rozhodl přijít na to, jak psát služby v C++. První věcí bylo rozhodnout se pro vhodnou knihovnu. Moje volba padla POCO, protože byl napsán objektově orientovaným stylem a také se chlubil normální dokumentací. Vyvstala také otázka výběru montážního systému. Do této chvíle jsem pracoval pouze s Visual Studio, IAR a bare makefiles. A žádný z těchto systémů mě neoslovil, protože jsem plánoval provozovat celou službu v docker kontejneru. Pak jsem se rozhodl, že zkusím přijít na cmake a zajímavého správce balíčků Conan. Tento správce balíčků vám umožnil zaregistrovat všechny závislosti do jednoho souboru

conanfile.txt
[vyžaduje] poco/1.9.3
libpq/11.5

[generátory] cmake

a jednoduchým příkazem "conan install ." nainstalovat potřebné knihovny. Přirozeně bylo také nutné provést změny

CMakeLists.txt

include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries(<target_name> ${CONAN_LIBS})

Poté jsem začal hledat knihovnu pro práci s PostgreSQL, protože to byla knihovna, se kterou jsem měl pramálo zkušeností, a také to byla ta, se kterou naše Python služby interagovaly. A víte, co jsem se naučil? Je to v POCO! Conan ale neví, že je v POCO a neví, jak to postavit, v úložišti je zastaralý konfigurační soubor (o této chybě jsem již psal tvůrcům POCO). To znamená, že budete muset hledat jinou knihovnu.

A pak moje volba padla na méně oblíbenou knihovnu libpg. A měl jsem neuvěřitelné štěstí, už byl v conanu a dokonce se montoval a montoval.

Dalším krokem bylo napsat šablonu služby, která dokáže zpracovávat požadavky.
Musíme zdědit naši třídu TemplateServerApp z Poco::Util::ServerApplication a přepsat hlavní metodu.

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;
}

V hlavní metodě musíme nastavit parametry: port, počet vláken a velikost fronty. A co je nejdůležitější, musíte zadat handler pro příchozí požadavky. To se provádí vytvořením továrny

TemplateRequestHandlerFactory

class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
    virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
    {
        return new TemplateServerAppHandler;
    }
};

V mém případě jednoduše vytvoří pokaždé stejný handler - TemplateServerAppHandler. Zde můžeme umístit naši obchodní 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();
    }
};

Vytvořil jsem také šablonu třídy pro práci s PostgreSQL. Pro provedení jednoduchého SQL, jako je vytvoření tabulky, existuje metoda ExecuteSQL(). Pro složitější dotazy nebo získávání dat budete muset získat připojení přes GetConnection() a použijte libpg API. (Snad později tuto nespravedlnost napravím).

Databáze

#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;
};

Všechny parametry pro připojení k databázi jsou převzaty z prostředí, takže je potřeba vytvořit a nakonfigurovat i soubor .env

.env

DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432

Celý kód můžete vidět na github.

Mikroslužby v C++. Fikce nebo realita?

A nyní přichází poslední fáze psaní dockerfile a docker-compose.yml. Abych byl upřímný, zabralo to většinu času, a to nejen proto, že jsem noob, protože bylo nutné pokaždé znovu vybudovat knihovny, ale také kvůli nástrahám conanu. Například, aby si conan stáhl, nainstaloval a vytvořil potřebné závislosti, nestačí mu stáhnout „conan install .“, ale musí předat také parametr -s kompilátor.libcxx=libstdc++11, jinak riskujete, že se ve fázi propojování vaší aplikace objeví spousta chyb. S touto chybou jsem uvízl několik hodin a doufám, že tento článek pomůže ostatním vyřešit tento problém za kratší dobu.

Poté, co jsem napsal docker-compose.yml, na radu mého přítele jsem přidal podporu vykrajovátka na sušenky a nyní si můžete pořídit plnohodnotnou šablonu pro službu REST API v C++, s přizpůsobeným prostředím a nainstalovaným PostgreSQL, jednoduše zadáním „cookiecutter“ do konzole https://github.com/KovalevVasiliy/cpp_rest_api_template.git" A pak „docker-compose up —build“.

Doufám, že tato šablona pomůže začátečníkům na jejich obtížné cestě vývoje aplikací REST API ve skvělém a výkonném, ale tak neohrabaném jazyce, jako je C++.
Také vřele doporučuji k přečtení zde tento článek. Podrobněji vysvětluje, jak pracovat s POCO a psát vlastní službu REST API.

Zdroj: www.habr.com

Přidat komentář