C ++ Enterprise Edition
O que é a "edição empresarial"

Surpreendentemente, durante todo o tempo em que trabalhei em TI, nunca ouvi ninguém dizer "edição empresarial" sobre uma linguagem de programação, exceto Java. Afinal, as pessoas escrevem aplicativos para o segmento corporativo em muitas linguagens de programação, e as entidades nas quais os programadores operam, se não são idênticas, são semelhantes. E para o c ++ em particular, eu gostaria de preencher a lacuna do empreendedorismo, pelo menos falando sobre isso.
Em relação ao C ++, a "edição corporativa" é um subconjunto da linguagem e das bibliotecas que permite o desenvolvimento "rápido" de aplicativos [multiplataforma] para sistemas modulares fracamente acoplados com arquitetura distribuída e / ou de cluster e com lógica comercial aplicada e, como regra geral, alta carga de trabalho.
Para continuar nossa conversa, antes de tudo, é necessário introduzir os conceitos de aplicativo , módulo e plug-in
- Um aplicativo é um arquivo executável que pode operar como um serviço do sistema, tem sua própria configuração, pode aceitar parâmetros de entrada e pode ter uma estrutura de plug-in modular.
- Um módulo é uma implementação de uma interface que vive dentro de um aplicativo ou lógica de negócios.
- Um plug - in é uma biblioteca carregada dinamicamente que implementa uma ou mais interfaces, ou parte de uma lógica de negócios.
Todos os aplicativos, executando seu trabalho exclusivo, geralmente precisam de mecanismos de todo o sistema, como acesso a dados (DBMS), troca de informações por um barramento comum (JMS), execução de scripts distribuídos e locais com preservação de consistência (Transações), processamento de solicitações por exemplo, via protocolo http (s) (fastcgi) ou através de websockets, etc ... Cada aplicativo deve ter a capacidade de orquestrar seus módulos (OSGI), e em um sistema distribuído deve poder orquestrar aplicativos.
Um exemplo de um sistema distribuído fracamente conectado

App
Um exemplo de um esquema de aplicativo de servidor corporativo corporativo.

Eu já dei uma definição geral do aplicativo, então vamos ver o que há agora no mundo C ++ para implementar esse conceito. Os primeiros a mostrar a implementação do aplicativo foram estruturas gráficas como Qt e GTK, mas suas versões do aplicativo inicialmente assumiram que o aplicativo era uma “janela” gráfica com seu contexto e somente depois de um tempo uma visão geral do aplicativo apareceu, inclusive como um serviço do sistema, por exemplo, qtservice . Mas eu realmente não quero arrastar uma estrutura condicionalmente gráfica para uma tarefa de serviço, então vamos olhar para bibliotecas não gráficas. E o impulso vem primeiro ... Mas, infelizmente, a lista de bibliotecas oficiais não inclui o Boost.Application e similares. Há um projeto separado Boost.Application . O projeto é muito interessante, mas, na minha opinião, detalhado, embora a ideologia do impulso seja respeitada. Aqui está um exemplo de aplicativo do 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; }
O exemplo acima define o aplicativo myapp
com seu thread principal de trabalho e o mecanismo para iniciar esse aplicativo.
Além disso, darei um exemplo semelhante da estrutura 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)
Chamo a atenção para o fato de que o aplicativo deve conter mecanismos para registro, download de configurações e opções de processamento.
Por exemplo, para processar opções, existem:
Para configurar:
Para registro no diário:
Fraqueza, módulos e plugins.
A fraqueza no sistema "corporativo" é a possibilidade de uma substituição rápida e indolor de certos mecanismos. Isso se aplica aos módulos do aplicativo e aos próprios aplicativos, bem como às implementações, por exemplo, microsserviços. O que temos, do ponto de vista do C ++. Tudo está ruim com os módulos, embora eles implementem interfaces, mas eles morem dentro de um aplicativo "compilado", portanto não haverá mudanças rápidas, mas os plugins serão úteis! Com a ajuda de bibliotecas dinâmicas, você pode não apenas organizar a substituição rápida, mas também a operação simultânea de duas versões diferentes. Existe um mundo inteiro de "chamada de procedimento remoto", também conhecido como RPC. Os mecanismos de pesquisa para implementações de interface, juntamente com o RPC, geraram algo semelhante ao OSGI no mundo Java. É ótimo ter suporte para isso no ecossistema C ++ e existe um, aqui estão alguns exemplos:
Módulo de amostra para o POCO OSP "servidor de aplicativos"
#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!"); } }; }
Módulo de amostra para o "servidor de aplicativos" do 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); }
Deve-se notar que em sistemas distribuídos fracamente conectados é necessário ter mecanismos que garantam operações transacionais, mas no momento não há nada semelhante para C ++. I.e. assim como não há como, dentro de um aplicativo, fazer uma solicitação ao banco de dados, arquivo e esb dentro de uma transação, não existe maneira para operações distribuídas. Claro, tudo pode ser escrito, mas não há nada generalizado. Alguém dirá que existe software transactional memory
, sim, é claro, mas isso facilitará apenas a gravação dos mecanismos de transação por conta própria.
Toolkit
De todas as muitas ferramentas auxiliares, quero destacar serialização e DSL, porque a presença deles permite implementar muitos outros componentes e cenários.
Serialização
Serialização é o processo de converter uma estrutura de dados em uma sequência de bits. O inverso da operação de serialização é a operação de desserialização (estruturação) - restaurando o estado inicial da estrutura de dados da sequência de bits da wikipedia . No contexto do C ++, é importante entender que hoje não há como serializar objetos e transferi-los para outro programa que anteriormente não sabia nada sobre esse objeto. Nesse caso, quero dizer um objeto como uma implementação de uma determinada classe com campos e métodos. Portanto, destacarei duas abordagens principais usadas no mundo C ++:
- serialização para formato binário
- serialização para o formato formal
Na literatura e na Internet, geralmente há uma separação em formato binário e de texto, mas acho que essa separação não é totalmente correta, por exemplo, MsgPack não salva informações sobre o tipo de objeto, portanto, o programador recebe controle sobre a exibição correta e o formato MsgPack é binário. O Protobuf , pelo contrário, salva todas as meta-informações em uma representação intermediária, o que permite que seja usado entre diferentes linguagens de programação, enquanto o Protobuf também é binário.
Portanto, o processo de serialização, por que precisamos dele. Para divulgar todas as nuances, precisamos de outro artigo, tentarei explicar com exemplos. A serialização permite, enquanto permanece em termos de linguagem de programação, "empacotar" entidades de software (classes, estruturas) para transferi-las pela rede, para armazenamento persistente, por exemplo, em arquivos e outros scripts que, sem serialização, nos forçam a inventar nossos próprios protocolos e levar em consideração o hardware e plataforma de software, codificação de texto etc.
Aqui estão algumas bibliotecas de amostra para serialização:
DSL
Linguagem específica do domínio - uma linguagem de programação para a sua área de assunto. De fato, quando estamos envolvidos na automação de uma determinada empresa, somos confrontados com a área de assunto do cliente e descrevemos todos os processos de negócios em termos da área de assunto, mas assim que se trata de programação, programadores, juntamente com analistas, estão envolvidos no mapeamento de conceitos de processos de negócios em conceitos framework e linguagem de programação. E se não houver um certo número de processos de negócios e a área de assunto for definida estritamente o suficiente, faz sentido criar sua própria DSL, implementar a maioria dos cenários existentes e adicionar novos. No mundo C ++, não há muitas oportunidades para implementação "rápida" de sua DSL. É claro que existem mecanismos para incorporar lua, javascript e outras linguagens de programação no C ++ - um programa, mas quem precisa de vulnerabilidades e um mecanismo de execução potencialmente descontrolado para "tudo" ?! Então, analisaremos as ferramentas que permitem que você faça o DSL você mesmo.
A biblioteca Boost.Proto foi projetada apenas para criar sua própria DSL, esse é seu objetivo direto, aqui está um exemplo
#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 e Bison são usados para gerar lexers e analisadores para sua gramática. A sintaxe não é simples, mas resolve o problema com eficiência.
Código de amostra para gerar um lexer
%{ #include <math.h> %} DIGIT [0-9] ID [az][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: %s\n", yytext ); } {ID} printf( "An identifier: %s\n", yytext ); "+"|"-"|"*"|"/" printf( "An operator: %s\n", yytext ); "{"[^}\n]*"}" /* eat up one-line comments */ [ \t\n]+ /* eat up whitespace */ . printf( "Unrecognized character: %s\n", 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(); }
E, no entanto, existe a especificação SCXML - XML de gráfico de estado: notação de máquina de estado para abstração de controle, uma descrição de uma máquina de estado na marcação semelhante a XML. Isso não é exatamente DSL, mas também um mecanismo conveniente para automatizar processos sem programação. O Qt SCXML possui excelente implementação. Existem outras implementações, mas elas não são tão flexíveis.
Este é um exemplo de um cliente FTP na notação SCXML, um exemplo retirado do site da documentação Qt
<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>
E assim parece no visualizador SCXML

Acesso a dados e integração
Este é talvez um dos tópicos mais "doloridos" do mundo com o ++. O mundo dos dados para um desenvolvedor de c ++ está sempre conectado à necessidade de poder exibi-los na essência de uma linguagem de programação. Uma linha em uma tabela está em um objeto ou estrutura, json está em uma classe e assim por diante. Na ausência de reflexão - este é um problema enorme, mas nós, com ++ - apelidos, não nos desesperamos e encontramos várias maneiras de sair da situação. Vamos começar com o DBMS.
Agora serei comum, mas o ODBC é o único mecanismo universal para acessar DBMSs relacionais, nenhuma outra opção foi inventada ainda, mas C ++ - a comunidade não fica parada e hoje existem bibliotecas e estruturas que fornecem interfaces de acesso generalizadas a vários DBMSs.
Primeiro, mencionarei bibliotecas que fornecem acesso unificado ao DBMS usando bibliotecas cliente e SQL
Todos eles são bons, mas fazem você se lembrar das nuances da exibição de dados do banco de dados em objetos e estruturas C ++, além da eficiência das consultas SQL que caem imediatamente sobre seus ombros.
Os exemplos a seguir são ORMs em C ++. Sim existem! A propósito, o SOCI suporta mecanismos ORM por meio da especialização soci :: type_conversion, mas eu não o incluí intencionalmente, pois esse não é seu objetivo direto.
- LiteSQL C ++ - ORM, que permite interagir com o DBMS SQLite3, PostgreSQL, MySQL. Essa biblioteca requer que o programador pré-projete arquivos xml com uma descrição de objetos e relacionamentos para gerar fontes adicionais usando litesql-gen.
- O ODB do Code Synthesis é um ORM muito interessante, permite que você permaneça no C ++, sem usar arquivos de descrição intermediários, aqui está um pequeno exemplo:
#pragma db object class person {
- Wt ++ é uma estrutura grande, você pode escrever um artigo separado sobre isso em geral, mas também contém ORM que pode interagir com o DBMS Sqlite3, Firebird, MariaDB / MySQL, MSSQL Server, PostgreSQL e Oracle.
- Eu também gostaria de mencionar o ORM sobre sqlite sqlite_orm e hiberlite . Como o sqlite é um DBMS incorporado e o ORM para ele consulta consultas e, de fato, toda a interação com o banco de dados, em tempo de compilação, a ferramenta se torna muito conveniente para rápida implantação e criação de protótipos.
- QHibernate - ORM para Qt5 com suporte ao Postgresql. Encharcado com idéias de hibernação de java.
Embora a integração por meio de um DBMS seja considerada "integração", prefiro deixá-lo fora dos colchetes e seguir para a integração por meio de protocolos e APIs.
RPC - chamada de processo remoto, uma técnica bem conhecida para a interação do "cliente" com o "servidor". Como no caso do ORM, a principal dificuldade é escrever / gerar vários arquivos auxiliares para vincular o protocolo a funções reais no código. Intencionalmente, não mencionarei os vários RPCs implementados diretamente no sistema operacional, mas vou me concentrar em soluções de plataforma cruzada.
- O grpc é uma estrutura do Google para chamadas de procedimento remoto, uma estrutura muito popular e eficiente do google. Basicamente, ele usa o google protobuf, mencionei na seção de serialização, suporta muitas linguagens de programação, mas é novo no ambiente corporativo.
- json-rpc - RPC, onde JSON é usado como protocolo, um bom exemplo de implementação é a biblioteca libjson-rpc-cpp , e aqui está um exemplo de arquivo de descrição:
[ { "name": "sayHello", "params": { "name": "Peter" }, "returns" : "Hello Peter" }, { "name" : "notifyServer" } ]
Com base nessa descrição, são gerados códigos de cliente e servidor que podem ser usados em seu aplicativo. Em geral, há uma especificação para JSON-RPC 1.0 e 2.0 . Portanto, chamar uma função de um aplicativo Web e processá-lo em C ++ não é difícil.
- XML-RPC e SOAP - o líder claro aqui - é o gSOAP , uma biblioteca muito poderosa, acho que não existem alternativas dignas. Como no exemplo anterior, criamos um arquivo intermediário com conteúdo xml-rpc ou soap, configuramos o gerador, obtemos o código e o usamos. Exemplos típicos de solicitação e resposta na notação 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 é um projeto muito interessante do pocoproject. Permite determinar quais classes, funções etc. pode ser chamado remotamente usando anotações nos comentários. Aqui está um exemplo
typedef unsigned long GroupID; typedef std::string GroupName;
Para gerar código auxiliar, seu próprio "compilador" é usado. Por um longo tempo, essa funcionalidade esteve apenas na versão paga do POCO Framework, mas com o advento do projeto macchina.io , você pode usá-lo gratuitamente.
O sistema de mensagens é um conceito um tanto amplo, mas o analisarei do ponto de vista das mensagens através de um barramento de dados comum, a saber, passarei por bibliotecas e servidores que implementam o Java Message Service usando vários protocolos, por exemplo, AMQP ou STOMP . O barramento de dados comum, também chamado de Enterprise Servise Bus (ESB), é muito comum em soluções de segmento corporativo, como permite integrar rapidamente vários elementos da infraestrutura de TI entre si, usando um padrão ponto a ponto e publicação-assinatura. Existem alguns intermediários de mensagens industriais escritos em C ++, eu sei dois: Apache Qpid e UPMQ , e o segundo é escrito por mim. Há o Eclipse Mosquitto , mas está escrito em si. A beleza do JMS para java é que seu código não depende do protocolo que o cliente e o servidor usam, JMS como ODBC, declara funções e comportamento, portanto, você pode alterar o provedor JMS pelo menos uma vez por dia e não reescrever o código, por C ++, infelizmente, não é. Você precisará reescrever a parte do cliente para cada provedor. Na minha opinião, listarei as bibliotecas C ++ mais populares para os intermediários de mensagens menos populares:
O princípio de que essas bibliotecas fornecem funcionalidade geral geralmente é consistente com a especificação JMS. Nesse sentido, existe o desejo de reunir um grupo de pessoas com idéias semelhantes e escrever algum tipo de ODBC, mas para os intermediários de mensagens, para que todo programador de C ++ sofra um pouco menos do que o habitual.
Conectividade de rede
Eu deixei deliberadamente tudo o que estava conectado diretamente com a interação da rede até o fim, porque Nesse campo, os desenvolvedores de C ++ têm menos problemas, na minha opinião. Resta apenas escolher o padrão mais próximo da sua decisão e a estrutura que a implementa. Antes de listar as bibliotecas mais populares, quero observar um detalhe importante no desenvolvimento de seus próprios aplicativos de rede. Se você decidir criar seu próprio protocolo através de TCP ou UDP, esteja preparado para que todos os tipos de ferramentas de segurança "inteligentes" bloqueiem seu tráfego. Portanto, tenha cuidado ao compactar seu protocolo, por exemplo, em https ou poderá haver problemas. Então bibliotecas:
- Boost.Asio e Boost.Beast - uma das implementações mais populares para comunicação de rede assíncrona, há suporte para HTTP e WebSockets
- O Poco :: Net também é uma solução muito popular e, além da interação bruta, você pode usar classes prontas do TCP Server Framework, Reactor Framework, bem como classes de cliente e servidor para HTTP, FTP e E-Mail. Também há suporte para WebSockets
- ACE - nunca usou essa biblioteca, mas os colegas dizem que ela também é uma biblioteca valiosa, com uma abordagem integrada para implementar aplicativos de rede e muito mais.
- Rede Qt - no Qt, a parte da rede está bem implementada. O único ponto controverso são os sinais e slots para soluções de servidor, embora o servidor esteja no Qt !?
Total
Então, o que eu queria dizer é uma visão geral dessas bibliotecas. Se eu consegui, você tem a impressão de que existe uma "edição corporativa", mas não há soluções para sua implementação e uso, apenas um zoológico de bibliotecas. Então é mesmo. Existem bibliotecas mais ou menos completas para o desenvolvimento de aplicativos para o segmento corporativo, mas não há solução padrão. Por conta própria, só posso recomendar o pocoproject e o maccina.io como ponto de partida na pesquisa de soluções para o back-end e o aprimoramento de qualquer caso, e é claro que estou procurando pessoas afins para promover o conceito de "C ++ Enterprise Edition"!