C++ Enterprise Edition
Что такое "enterprise edition"
Удивительно, но за все время моей работы в IT, я ниразу не слышал, чтобы кто-то говорил "enterprise edition" относительно языка программирования, кроме как для Java. Но ведь приложения для корпоративного сегмента люди пишут на многих языках программирования, и сущности, которыми оперируют программисты, если не идентичны, то схожи. И для c++ в частности, я бы хотел заполнить пробел enterpr'айзности, хотя бы рассказав об этом.
Применительно к c++, "enterprise edition" — это подмножество языка и библиотек, позволяющее "быстро" разрабатывать [кроссплатформенные] приложения для слабосвязных модульных систем с распределенной и/или кластерной архитектурой и с прикладной бизнес-логикой и, как правило, высокой нагрузкой.
Чтобы продолжить наш разговор, в первую очередь, необходимо ввести понятия приложения, модуля и плагина
- Приложение — это исполняемый файл, который может работать в качестве системной службы, имеет свою конфигурацию, может принимать параметры на вход и может иметь модульно-плагинную структуру.
- Модуль — это реализация интерфейса, живущая внутри приложения или бизнеслогики.
- Плагин — это динамически загружаемая библиотека, в которой реализован один или несколько интерфейсов, либо часть бизнеслогики.
Все приложения, выполняя свою уникальную работу, как правило нуждаются в общесистемных механизмах, таких как, доступ к данным (СУБД), обмен информацией через общую шину (JMS), исполнение распределенных и локальных сценариев с сохранением консистентности (Транзакции), обработка запросов, приходящих, например, по протоколу http(s) (fastcgi) или через websocket'ы и т.д… Каждое приложение должно иметь возможность оркестровки своих модулей (OSGI), а в распределенной системе должна быть возможность оркестровки приложений.
Пример схемы распределенной слабосвязной системы
Приложение
Пример схемы "корпоративного" серверного приложения.
Общее определение приложению, я уже дал, так что разберемся, что же сейчас есть в мире c++ для имплементации этого понятия. Первыми показали реализацию приложения графические фреймворки, такие как Qt и GTK, но их версии приложения изначально предполагали, что приложение — это графическое "окно" со своим контекстом и только спустя время появилось общее видение приложения, в том числе и как системной службы, например, qtservice. Но тащить условно графический фреймворк для сервисной задачи не очень хочется, так что посмотрим в сторону неграфических библиотек. И первым на ум приходит boost… Но к сожалению в списке официальных библиотек нет Boost.Aplication и ей подобных. Есть отдельный проект Boost.Aplication. Проект весьма интересный, но, на мой взгляд, многословный, хотя идеология boost соблюдена. Вот пример приложения от Boost.Application
#define BOOST_ALL_DYN_LINK
#define BOOST_LIB_DIAGNOSTIC
#define BOOST_APPLICATION_FEATURE_NS_SELECT_BOOST
#include <fstream>
#include <iostream>
#include <boost/application.hpp>
using namespace boost;
// my application code
class myapp {
public:
myapp(application::context& context) : context_(context) {}
void worker() {
// ...
while (st->state() != application::status::stopped) {
boost::this_thread::sleep(boost::posix_time::seconds(1));
if (st->state() == application::status::paused)
my_log_file_ << count++ << ", paused..." << std::endl;
else
my_log_file_ << count++ << ", running..." << std::endl;
}
}
// param
int operator()() {
// launch a work thread
boost::thread thread(&myapp::worker, this);
context_.find<application::wait_for_termination_request>()->wait();
return 0;
}
bool stop() {
my_log_file_ << "Stoping my application..." << std::endl;
my_log_file_.close();
return true; // return true to stop, false to ignore
}
private:
std::ofstream my_log_file_;
application::context& context_;
};
int main(int argc, char* argv[]) {
application::context app_context;
// auto_handler will automatically add termination, pause and resume (windows)
// handlers
application::auto_handler<myapp> app(app_context);
// to handle args
app_context.insert<application::args>(
boost::make_shared<application::args>(argc, argv));
// my server instantiation
boost::system::error_code ec;
int result = application::launch<application::server>(app, app_context, ec);
if (ec) {
std::cout << "[E] " << ec.message() << " <" << ec.value() << "> "
<< std::endl;
}
return result;
}
В приведенном примере определяется приложение myapp
со своим основным рабочим потоком worker
и механизм запуска этого приложения.
В качестве дополнения приведу аналогичный пример из фреймворка pocoproject
#include <iostream>
#include <sstream>
#include "Poco/AutoPtr.h"
#include "Poco/Util/AbstractConfiguration.h"
#include "Poco/Util/Application.h"
#include "Poco/Util/HelpFormatter.h"
#include "Poco/Util/Option.h"
#include "Poco/Util/OptionSet.h"
using Poco::AutoPtr;
using Poco::Util::AbstractConfiguration;
using Poco::Util::Application;
using Poco::Util::HelpFormatter;
using Poco::Util::Option;
using Poco::Util::OptionCallback;
using Poco::Util::OptionSet;
class SampleApp : public Application {
public:
SampleApp() : _helpRequested(false) {}
protected:
void initialize(Application &self) {
loadConfiguration();
Application::initialize(self);
}
void uninitialize() { Application::uninitialize(); }
void reinitialize(Application &self) { Application::reinitialize(self); }
void defineOptions(OptionSet &options) {
Application::defineOptions(options);
options.addOption(
Option("help", "h",
"display help information on command line arguments")
.required(false)
.repeatable(false)
.callback(OptionCallback<SampleApp>(this, &SampleApp::handleHelp)));
}
void handleHelp(const std::string &name, const std::string &value) {
_helpRequested = true;
displayHelp();
stopOptionsProcessing();
}
void displayHelp() {
HelpFormatter helpFormatter(options());
helpFormatter.setCommand(commandName());
helpFormatter.setUsage("OPTIONS");
helpFormatter.setHeader(
"A sample application that demonstrates some of the features of the "
"Poco::Util::Application class.");
helpFormatter.format(std::cout);
}
int main(const ArgVec &args) {
if (!_helpRequested) {
logger().information("Command line:");
std::ostringstream ostr;
logger().information(ostr.str());
logger().information("Arguments to main():");
for (const auto &it : args) {
logger().information(it);
}
}
return Application::EXIT_OK;
}
private:
bool _helpRequested;
};
POCO_APP_MAIN(SampleApp)
Обращаю Ваше внимание на то, что приложение должно содержать механизмы журналирования, загрузки конфигураций и обработки опций.
Например, для обработки опций есть :
Для конфигурирования :
Для журналироания :
Слабосвязность, модули и плагины.
Слабосвязность в "корпоративной" системе — это возможность быстрой и безболезненной подмены тех или иных механизмов. Это касается как модулей внутри приложения, так и самих приложений, как реализаций, например, микросервисов. Что же у нас есть, с точки зрения c++. С модулями все плохо, хоть они и реализуют интерфейсы, но живут внутри "скомпилённого" приложения, так что быстрой подмены не будет, но на помощь приходят плагины! С помощью динамических библиотек можно не только организовать быструю подмену, но и одновременную работу двух разных версий. Есть еще целый мир "удаленного вызова процедур" он же RPC. Механизмы поиска реализаций интерфейсов в купе с RPC, породили нечто похожее на OSGI из мира Java. Здорово иметь поддержку этого в экосистеме c++ и она есть, вот пара примеров:
Пример модуля для "сервера приложения" POCO OSP
#include "Poco/OSP/BundleActivator.h"
#include "Poco/OSP/BundleContext.h"
#include "Poco/ClassLibrary.h"
namespace HelloBundle {
class BundleActivator: public Poco::OSP::BundleActivator {
public:
void start(Poco::OSP::BundleContext::Ptr pContext) {
pContext->logger().information("Hello, world!");
}
void stop(Poco::OSP::BundleContext::Ptr pContext) {
pContext->logger().information("Goodbye!");
}
};
} // namespace HelloBundle
POCO_BEGIN_MANIFEST(Poco::OSP::BundleActivator)
POCO_EXPORT_CLASS(HelloBundle::BundleActivator)
POCO_END_MANIFEST
Пример модуля для "серврера приложения" Apache Celix
#include "Bar.h"
#include "BarActivator.h"
using namespace celix::dm;
DmActivator* DmActivator::create(DependencyManager& mng) {
return new BarActivator(mng);
}
void BarActivator::init() {
std::shared_ptr<Bar> bar = std::shared_ptr<Bar>{new Bar{}};
Properties props;
props["meta.info.key"] = "meta.info.value";
Properties cProps;
cProps["also.meta.info.key"] = "also.meta.info.value";
this->cExample.handle = bar.get();
this->cExample.method = [](void *handle, int arg1, double arg2, double *out) {
Bar* bar = static_cast<Bar*>(handle);
return bar->cMethod(arg1, arg2, out);
};
mng.createComponent(bar) //using a pointer a instance. Also supported is lazy initialization (default constructor needed) or a rvalue reference (move)
.addInterface<IAnotherExample>(IANOTHER_EXAMPLE_VERSION, props)
.addCInterface(&this->cExample, EXAMPLE_NAME, EXAMPLE_VERSION, cProps)
.setCallbacks(&Bar::init, &Bar::start, &Bar::stop, &Bar::deinit);
}
Хочется отметить, что в распределенных слабосвязных системах необходимо иметь механизмы обеспечивающие транзакционность операций, но на текущий момент для c++ ничего подобного нет. Т.е. как нет возможности внутри одного приложения выполнить запрос к базе данных, файлу и esb в рамках одной транзакции, так и нет такой возможности для распределенных операций. Безусловно все можно написать, но чего-то обобщённого нет. Кто-то скажет, что есть software transactional memory
, да, конечно, но это лишь облегчит написание транзакционных механизмов собственными силами.
Инструментарий
Из всего множество вспомогательных инструментов хочу выделить сериализацию и DSL, т.к. их наличие позволяет реализовать многие другие компоненты и сценарии.
Сериализация
Сериализация — процесс перевода какой-либо структуры данных в последовательность битов. Обратной к операции сериализации является операция десериализации (структуризации) — восстановление начального состояния структуры данных из битовой последовательности wikipedia. В контексте c++ важно понимать, что на сегодняшний день нет возможности сериализации объектов и передачи их в другую программу, которая об этом объекте ранее ничего не знала. Под объектом в этом случае я понимаю реализацию некоего класса с полями и методами. Поэтому выделю два основных подхода, применяемые в мире c++ :
- сериализация в бинарный формат
- сериализация в формализованный формат
В литературе и интернете часто встречается разделение на бинарный и текстовый формат, но я считаю такое разделение не совсем правильным, например, MsgPack не сохраняет информацию о типе объекта, соответственно, отдается контроль за правильным отображением программисту и формат MsgPack — бинарный. Protobuf, напротив, сохраняет всю метаинформацию в промежуточное представление, что позволяет использовать его между разными языками программирования, при этом, Protobuf тоже бинарный.
Итак процесс сериализации, зачем он нам нужен. Для раскрытия всех нюансов, нужна еще одна статья, я постараюсь объяснить на примерах. Сериализация позволяет, оставаясь в терминах языка программирования, "упаковывать" программные сущности (классы, структуры) для передачи их по сети, для персистентного хранения, например, в файлах и других сценариев, которые, без сериализации, заставляют нас придумывать собственные протоколы, учитывать аппаратную и программную платформу, кодировку текста и.т.д.
Приведу пару примеров библиотек для сериализации:
DSL
Domain Specific Language — язык программирования для вашей предметной области. Действительно, когда мы занимаемся автоматизацией какого-то предприятия, то мы сталкиваемся с предметной областью заказчика и все бизнес-процессы описываем в терминах предметной области, но как только дело доходит до программирования, то программисты вместе с аналитиками занимаются отображением понятий бизнес-процессов в понятия фреймворка и языка программирования. И если бизнес-процессов не определенное количество, а предметная область определена достаточно строго, то имеет смысл создать свой DSL, для имплементации большей части существующих сценариев и добавления новых. В мире с++ не так много возможностей "быстрой" реализации своего DSL. Есть, конечно, механизмы встраивания lua, javascript и других языков программирования в c++-программу, но кому нужны уязвимости и потенциально неконтролируемый движок исполнения "всего" ?! Так что разберем те инструменты, что позволяют делать DSL самому.
Библиотека Boost.Proto как раз создана для создания собственных DSL, это её прямое предназаначение, вот пример
#include <iostream>
#include <boost/proto/proto.hpp>
#include <boost/typeof/std/ostream.hpp>
using namespace boost;
proto::terminal< std::ostream & >::type cout_ = { std::cout };
template< typename Expr >
void evaluate( Expr const & expr ) {
proto::default_context ctx;
proto::eval(expr, ctx);
}
int main() {
evaluate( cout_ << "hello" << ',' << " world" );
return 0;
}
Flex и Bison используются для генерации лексеров и парсеров придуманных Вами грамматик. Синтаксис не простой, но задачу решает эффективно.
Пример кода для генерации лексера
/* scanner for a toy Pascal-like language */
%{
/* need this for the call to atof() below */
#include <math.h>
%}
DIGIT [0-9]
ID [a-z][a-z0-9]*
%%
{DIGIT}+ {
printf( "An integer: %s (%d)n", yytext,
atoi( yytext ) );
}
{DIGIT}+"."{DIGIT}* {
printf( "A float: %s (%g)n", yytext,
atof( yytext ) );
}
if|then|begin|end|procedure|function {
printf( "A keyword: %sn", yytext );
}
{ID} printf( "An identifier: %sn", yytext );
"+"|"-"|"*"|"/" printf( "An operator: %sn", yytext );
"{"[^}n]*"}" /* eat up one-line comments */
[ tn]+ /* eat up whitespace */
. printf( "Unrecognized character: %sn", yytext );
%%
main( argc, argv )
int argc;
char **argv;
{
++argv, --argc; /* skip over program name */
if ( argc > 0 )
yyin = fopen( argv[0], "r" );
else
yyin = stdin;
yylex();
}
А еще, существует спецификация SCXML — State Chart XML: State Machine Notation for Control Abstraction, описание конечного автомата в XML-подобной разметке. Это не совсем DSL, но тоже удобный механизм автоматизации процессов без программирования. Отличная имплементация есть у Qt SCXML. Есть и другие рализации, но они не такие гибкие.
Это пример FTP клиента в SCXML нотации, пример взят с сайта Qt Documentation
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" name="FtpClient"
datamodel="ecmascript">
<state id="G" initial="I">
<transition event="reply" target="E"/>
<transition event="cmd" target="F"/>
<state id="I">
<transition event="reply.2xx" target="S"/>
</state>
<state id="B">
<transition event="cmd.DELE cmd.CWD cmd.CDUP cmd.HELP cmd.NOOP cmd.QUIT cmd.SYST
cmd.STAT cmd.RMD cmd.MKD cmd.PWD cmd.PORT"
target="W.general"/>
<transition event="cmd.APPE cmd.LIST cmd.NLST cmd.REIN cmd.RETR cmd.STOR cmd.STOU"
target="W.1xx"/>
<transition event="cmd.USER" target="W.user"/>
<state id="S"/>
<state id="F"/>
</state>
<state id="W">
<onentry>
<send eventexpr=""submit." + _event.name">
<param name="params" expr="_event.data"/>
</send>
</onentry>
<transition event="reply.2xx" target="S"/>
<transition event="reply.4xx reply.5xx" target="F"/>
<state id="W.1xx">
<transition event="reply.1xx" target="W.transfer"/>
</state>
<state id="W.transfer"/>
<state id="W.general"/>
<state id="W.user">
<transition event="reply.3xx" target="P"/>
</state>
<state id="W.login"/>
</state>
<state id="P">
<transition event="cmd.PASS" target="W.login"/>
</state>
</state>
<final id="E"/>
</scxml>
А так это выглядит в визуализаторе SCXML
Доступ к данным и Интеграция
Это, пожалуй, одна из самых "наболевших" тем в мире с++. Мир данных для с++ разработчика всегда связан с необходимостью уметь отображать их на сущности языка программирования. Строку в таблице — в объект или структуру, json — в класс и так далее. В отсутствие рефлексии — это огромная проблема, но мы, с++-ники, не отчаиваемся и находим различные выходы из ситуации. Начнем с СУБД.
Сейчас буду банален, но единственным универсальным механизмом доступа к реляционным СУБД является ODBC, других вариантов пока не придумали, но с++-сообщество не стоит на месте и на сегодняшний день есть библиотеки и фреймворки, предоставляющих обобщённые интерфейсы доступа к нескольким СУБД.
В первую очередь упомяну библиотеки, предоставляющие унифицированный доступ к СУБД с помощью клиентских библиотек и SQL
Все они хороши, но заставляют помнить о нюансах отображения данных из БД в объекты и структуры с++, плюс эффективность SQL-запросов сразу ложиться на ваши плечи.
Следующими примерами будут ORM на c++. Да такие есть! И кстаи, SOCI, поддерживает механизмы ORM, через специализацию soci::type_conversion, но её я намеренно не включил, так как это не прямое её предназначение.
- LiteSQL C++ — ORM, позволяющий взаимодействовать с СУБД SQLite3, PostgreSQL, MySQL. Эта библиотека требует от программиста предварительного оформить xml-файлы с описанием объектов и отношений, чтобы, с помощью litesql-gen сгенерировать дополнительные исходники.
- ODB от Code Synthesis — очень интересная ORM, позволяет оставаться в рамках c++, не использую промежуточные файлы описания, вот небольшой пример :
#pragma db object
class person {
// ...
private:
friend class odb::access;
person () {}
#pragma db id
string email_;
string name_;
unsigned short age_;
};
// ...
odb::sqlite::database db ("people.db");
person john ("john@doe.org", "John Doe", 31);
odb::transaction t (db.begin ());
db.persist (john);
typedef odb::query<person> person_query;
for (person& p: db.query<person> (person_query::age < 30));
cerr << p << endl;
jane.age (jane.age () + 1);
db.update (jane);
t.commit ();
- Wt++ — большой фреймворк, о нем можно вообще отдельную статью написать, тоже содержит ORM, умеющий взаимодействовать с СУБД Sqlite3, Firebird, MariaDB/MySQL, MSSQL Server, PostgreSQL и Oracle.
- Отдельно хочется упомянуть ORM над sqlite sqlite_orm и hiberlite. Так как sqlite встраиваемая СУБД, а ORM для него проверяет запросы, да и вообще все взаимодействие с БД, в compile-time, то инструмент становится очень удобным для быстрого развертывания и прототипирования.
- QHibernate — ORM для Qt5 с поддержкой Postgresql. Пропитан идеями hibernate от java.
Хоть и интеграцию через СУБД считают "интеграцией", я предпочту оставить это за скобками и перейти к интеграции через протоколы и API.
-
RPC — remote porocess call, всем известная техника взаимодействия "клиента" с "сервером". Как и в случае c ORM, главная трудность — это написание/генерация различных вспомогательных файлов для связывания протокола с реальными функциями в коде. Я намеренно не буду упоминать различные RPC, реализованные непосредственно в операционной системе, а сосредоточусь на кроссплатформенных решениях.
- grpc — фреймворк от Google для удалённого вызова процедур, весьма популярный и эффективный фреймворк от google. В основе своей использует google protobuf, его я упоминал в разделе сериализация, поддерживает множество языков программирования, но в корпрративной среде пока новичок.
- json-rpc — RPC, где в качестве протокола используется JSON, хороший пример реализации — это библиотека libjson-rpc-cpp, вот пример файла описания :
[ { "name": "sayHello", "params": { "name": "Peter" }, "returns" : "Hello Peter" }, { "name" : "notifyServer" } ]
На основе этого описания генерятся клиентский и серврерный код, который можно использовать в своем приложении. Вообще есть спецификация для JSON-RPC 1.0 и 2.0. Так что вызвать функцию из web-приложения, а обработать в c++ труда не составит.
- XML-RPC и SOAP — тут явный лидер — это gSOAP, очень мощная библиотека, не думаю, что есть достойные альтернативы. Также как и в предыдущем примере, содаем промежуточный файлик с xml-rpc или soap содержимым, натравливаем на него генератор, получаем код и пользуемся. Типичные примеры запроса и ответа в нотации xml-rpc:
<?xml version="1.0"?> <methodCall> <methodName>examples.getState</methodName> <params> <param> <value><i4>41</i4></value> </param> </params> </methodCall> <methodResponse> <params> <param> <value><string>State-Ready</string></value> </param> </params> </methodResponse>
- Poco::RemotingNG — очень интересный проект от pocoproject. Позволяет определеять какие классы, функции и т.п. могут быть вызваны удаленно с помощью аннотаций в комментариях. Вот пример,
typedef unsigned long GroupID; typedef std::string GroupName; //@ serialize struct Group { Group(GroupID _id_, const GroupName &_name_) : id(_id_), name(_name_) {} Group() {} //@ mandatory=false GroupID id; //@ mandatory=false GroupName name; }; // ... //@ remote class EXTERNAL_API GInfo { public: typedef Poco::SharedPtr<GInfo> Ptr; GInfo(); virtual Group getGroup() const = 0; virtual ~GInfo(); };
Для генерации вспомогательного кода используется собственный "компилятор". Долгое время эта функциональность была только в платной версии POCO Framework, но с появлением проекта macchina.io, Вы можете пользоваться этим бесплатно.
-
Messaging — несколько широкое понятие, но я разберу его с точки зрения обмена сообщениями через общую шину данных, а именно пройдусь по библиотекам и серврерам реализующим Java Message Service используя различные протоколы, например AMQP или STOMP. Общая шина данных, она же Enterprise Servise Bus (ESB), очень распространена в решениях корпоративного сегмента, т.к. позволяет достаточно быстро интегрировать различные элементы IT-инфраструктуры между собой, используюя паттерн "точка-точка" и "публикация-подписка". Промышленных брокеров сообщений, написанных на c++, мало, я знаю два: Apache Qpid и
UPMQ
, причем второй написан мной и пока является закрытым проектом. Есть еще Eclipse Mosquitto, но он написан на си. Прелесть JMS для java заключается в том, что Ваш код не зависит от протокола, который использует клиент и сервер, JMS как ODBC, декларирует функции и поведение, так что Вы можете хоть деясть раз на дню менять JMS-провайдера и не переписывать код, для c++ такого, к сожалению нет. Вам прийдется переписывыать клиентскую часть под каждого провайдера. Перечислю, на мой взгляд, самые популярные c++ библиотеки для не менее популярных брокеров сообщений:- ActiveMQ-CPP — клиентская библиотека к брокеру Apache AcriveMQ, который написан на java.
- Apache Kafka C++ client — cppkafka
- RabbitMQ C++ libs — наверное самый популярный брокер сообщений, имеет несколько библиотек на с/с++.
- IBM WebSphere MQ C++ classes — проприетарщина, но с очень развитым функционалом.
- Oracle Message Broker C++ API — тоже, он от Oracle
- MSMQ — версия от Microsoft
Принцип по которому эти библиотеки предоставляют функиональность общий, как правило соответствует спецификации JMS. В связи с этим есть желание собрать группу единомышленников и написать некое подобие ODBC, но для брокеров сообщений, чтобы каждый программист на c++ страдал чуть меньше, чем обычно.
Сетевое взаимодействие
Я умышлено оставил все что связано непосредственно с сетевым взаимодействием на последок, т.к. на этом поприще у c++ разработчиков меньше всего проблем, на мой взгляд. Остается лишь выбрать паттерн, который ближе всего к Вашему решению, и фреймворк, его реализующий. Прежде чем перечислить самые популярные библиотеки, хочу отметить важную деталь разработки собственных сетевых приложений. Если Вы решили придумать собственный протокол поверх TCP или UDP, будьте готовы, что всякие "умные" средства защиты будут блокировать Ваш трафик, так что озаботьтесь упаковкаой своего протокола, например, в https или могут быть проблемы. Итак библиотеки :
- Boost.Asio и Boost.Beast — одино из самых популярных реализаций для асинхронного сетевого взаимодействия, есть поддержка HTTP и WebSockets
- Poco::Net — тоже очень популярное решение, причем, помимо "сырого" взаимодействия можно использовать готовые классы TCP Server Framework, Reactor Framework, а также клиентские и серверные классы для HTTP, FTP и E-Mail. Так же есть поддержка WebSockets
- ACE — никогда не пользовался этой библиотекой, но коллеги говорят, что тоже стоящая внимания библиотека, с комплексным подходом для реализации сетевых приложений и не только.
- Qt Network — в Qt неплохо реализована сетевая часть. Единственный спорный момент — сигналы и слоты для серврерных решений, хотя сервер на Qt !?
Итого
Итак, что я хотел сказать обзором этих библиотек. Если у меня получилось, то у Вас осталось впечатление, что "enterprise edition", как бы, есть, а решения для его имплементации и использования — нет, только зоопарк библиотек. Так оно и есть на самом деле. Есть более-менее целостные библиотеки для разработки приложений корпоративного сегмента, но стандартного решения нет. От себя могу лишь рекомендовать pocoproject и maccina.io как отправную точку в исследованиях решений для бэкенда и boost для любых случаев, ну и конечно же ищу единомышленников для продвижения понятия "C++ Enterprise Edition" !
Автор: raiSadam