Mikroserwisy w C++. Fikcja czy rzeczywistość?

Mikroserwisy w C++. Fikcja czy rzeczywistość?

W tym artykule opowiem o tym, jak stworzyłem szablon (cookiecutter) i skonfigurowałem środowisko do pisania usługi REST API w C++ za pomocą docker/docker-compose i menedżera pakietów Conan.

Podczas kolejnego hackathonu, w którym brałem udział jako backend developer, pojawiło się pytanie, czego użyć do napisania kolejnego mikroserwisu. Wszystko, co do tej pory napisano, zostało napisane przeze mnie i moich towarzysz w Pythonie, bo mój kolega był ekspertem w tej dziedzinie i profesjonalnie tworzył backendy, ja zaś ogólnie programistą systemów wbudowanych i pisałem w świetnym i okropnym C++, a Pythona uczyłem się dopiero na studiach.

Stanęliśmy więc przed zadaniem napisania usługi o dużym obciążeniu, której głównym zadaniem było wstępne przetworzenie przychodzących do niej danych i zapisanie ich do bazy danych. A po kolejnej przerwie na papierosa znajomy zasugerował, żebym jako programista C++ napisał tę usługę z wykorzystaniem profesjonalistów. Twierdzę, że będzie szybciej, bardziej produktywnie i ogólnie rzecz biorąc, jury będzie zachwycone tym, jak wiemy, jak zarządzać zasobami zespołu. Na co odpowiedziałem, że nigdy nie robiłem takich rzeczy w C++ i z łatwością mogłem poświęcić pozostałe 20+ godzin na wyszukiwanie, kompilowanie i łączenie odpowiednich bibliotek. Krótko mówiąc, stchórzyłem. Tak zdecydowaliśmy i spokojnie dokończyliśmy wszystko w Pythonie.

Teraz, podczas przymusowej izolacji, postanowiłem wymyślić, jak napisać usługi w C++. Pierwszą rzeczą do zrobienia było wybranie odpowiedniej biblioteki. Mój wybór padł LITTLE, ponieważ został napisany w stylu obiektowym i posiadał także normalną dokumentację. Pojawiło się także pytanie o wybór systemu montażu. Do tego momentu pracowałem tylko z Visual Studio, IAR i gołymi plikami makefile. I żaden z tych systemów nie przypadł mi do gustu, gdyż planowałem uruchomić całą usługę w kontenerze dokera. Potem zdecydowałem się spróbować znaleźć cmake i interesującego menedżera pakietów Conan. Ten menedżer pakietów umożliwił zarejestrowanie wszystkich zależności w jednym pliku

conanfile.txt
[wymaga]poco/1.9.3
libpq/11.5

[generatory] cmake

i za pomocą prostego polecenia „conan install”. zainstaluj niezbędne biblioteki. Naturalnie konieczne było również dokonanie zmian w

CMakeLists.txt

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

Następnie zacząłem szukać biblioteki do pracy z PostgreSQL, ponieważ miałem z nią niewielkie doświadczenie w pracy, a także z nią współdziałały nasze usługi Pythona. I wiesz, czego się nauczyłem? Jest w POCO! Ale Conan nie wie, że jest w POCO i nie wie jak go zbudować; w repozytorium jest nieaktualny plik konfiguracyjny (o tym błędzie pisałem już twórcom POCO). Oznacza to, że będziesz musiał poszukać innej biblioteki.

A potem mój wybór padł na mniej popularną bibliotekę biblioteka. I miałem niesamowite szczęście, było już w Conanie, a nawet było montowane i montowane.

Kolejnym krokiem było napisanie szablonu usługi, który będzie mógł przetwarzać zgłoszenia.
Musimy dziedziczyć naszą klasę TemplateServerApp z Poco::Util::ServerApplication i zastąpić metodę główną.

Aplikacja serwera szablonów

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

W metodzie głównej musimy ustawić parametry: port, liczbę wątków i wielkość kolejki. A co najważniejsze, musisz określić procedurę obsługi przychodzących żądań. Odbywa się to poprzez utworzenie fabryki

Fabryka obsługi szablonów żądań

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

W moim przypadku po prostu tworzy za każdym razem ten sam moduł obsługi - TemplateServerAppHandler. Tutaj możemy umieścić naszą logikę biznesową.

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

Stworzyłem także szablon klasy do pracy z PostgreSQL. Aby wykonać prosty SQL, na przykład utworzyć tabelę, istnieje metoda WykonajSQL(). W przypadku bardziej złożonych zapytań lub pobierania danych konieczne będzie uzyskanie połączenia za pośrednictwem PobierzPołączenie() i użyj interfejsu API libpg. (Być może później naprawię tę niesprawiedliwość).

Baza danych

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

Wszystkie parametry połączenia z bazą danych pobierane są ze środowiska, dlatego też należy utworzyć i skonfigurować plik .env

.env

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

Cały kod możesz zobaczyć na stronie githubie.

Mikroserwisy w C++. Fikcja czy rzeczywistość?

Nadchodzi ostatni etap pisania pliku dockerfile i pliku docker-compose.yml. Szczerze mówiąc, zajęło to najwięcej czasu i nie tylko dlatego, że jestem noobem, ponieważ za każdym razem konieczne było przebudowywanie bibliotek, ale także z powodu pułapek Conana. Na przykład, aby Conan mógł pobrać, zainstalować i zbudować niezbędne zależności, nie wystarczy pobrać „conan install .”, musi jeszcze przekazać parametr -s kompilator.libcxx=libstdc++11, w przeciwnym razie ryzykujesz wystąpieniem wielu błędów na etapie łączenia aplikacji. Utknąłem z tym błędem przez kilka godzin i mam nadzieję, że ten artykuł pomoże innym osobom rozwiązać ten problem w krótszym czasie.

Następnie po napisaniu pliku docker-compose.yml za radą znajomego dodałem wsparcie krajacz ciastek a teraz możesz uzyskać pełnoprawny szablon usługi REST API w C++, z dostosowanym środowiskiem i zainstalowanym PostgreSQL, po prostu wpisując „cookiecutter” w konsoli https://github.com/KovalevVasiliy/cpp_rest_api_template.git" A następnie „docker-compose up —build”.

Mam nadzieję, że ten szablon pomoże początkującym na ich trudnej ścieżce tworzenia aplikacji REST API w świetnym i potężnym, ale tak niezgrabnym języku, jak C++.
Również gorąco polecam lekturę tutaj to artykuł. Wyjaśnia bardziej szczegółowo, jak pracować z POCO i pisać własną usługę REST API.

Źródło: www.habr.com

Dodaj komentarz