C ++企业版。 有可能吗

C ++企业版


什么是“企业版”


C ++ EE


出乎意料的是,在我从事IT工作的所有时间里,除了Java之外,我从未听过有人说过关于编程语言的“企业版”。 但是毕竟,人们使用许多编程语言为公司部门编写应用程序,并且程序员所操作的实体(如果不是相同的话)是相似的。 特别是对于c ++,我想填补企业家精神的空白,至少要讲一下。


应用于C ++时,“企业版”是语言和库的子集,可让您“快速”开发具有分布式和/或集群体系结构以及应用业务逻辑(通常是高负载)的松耦合模块系统的[跨平台]应用程序。


要继续我们的对话,首先,有必要介绍应用程序模块插件的概念


  • 应用程序是可执行文件,可以作为系统服务运行,具有自己的配置,可以接受输入参数,并且可以具有模块化的插件结构。
  • 模块是驻留在应用程序或业务逻辑内部的接口的实现。
  • 插件是一种动态加载的库,可实现一个或多个接口或业务逻辑的一部分。

所有执行其独特工作的应用程序通常都需要系统范围的机制,例如数据访问(DBMS),通过公共总线(JMS)进行信息交换,执行分布式和本地脚本并保持一致性(事务),处理即将到来的请求例如,通过http(s)(fastcgi)协议或通过websockets等。每个应用程序应具有协调其模块(OSGI)的能力,并且在分布式系统中应能够协调应用程序。


分布式弱连接系统的示例


系统


应用程式


公司企业服务器应用程序架构的示例。


申请书


我已经给出了应用程序的一般定义,因此让我们看看C ++世界中实现该概念的现状。 首先显示应用程序实现的是Qt和GTK之类的图形框架,但是它们的应用程序版本最初假定该应用程序是带有其上下文的图形“窗口”,并且仅在一段时间后出现了该应用程序的总体愿景,例如,作为系统服务, qtservice 。 但是我并不是真的希望为服务任务拖一个有条件的图形框架,所以让我们看一下非图形库。 首先想到的是Boost,但是不幸的是,官方库列表中没有Boost.Application之类的东西。 有一个单独的项目Boost.Application 。 该项目非常有趣,但我认为这很冗长,尽管尊重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应用程序及其主要工作线程以及启动该应用程序的机制。
另外,我将从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 结合,产生了类似于Java世界中OSGI的东西。 很高兴在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


领域特定语言-主题领域的编程语言。 确实,当我们从事某家企业的自动化时,我们会面对客户的主题领域,并按照主题领域描述所有业务流程,但是一旦涉及到编程,程序员和分析人员便会致力于将业务流程概念映射为概念框架和编程语言。 而且,如果没有一定数量的业务流程,并且对主题区域的定义足够严格,那么创建自己的DSL,实施大多数现有方案并添加新方案是有意义的。 在C ++世界中,“快速”实现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 [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(); } 

但是,还有SCXML规范-状态图XML:用于抽象控制的状态机符号,是一种类似XML标记的状态机的描述。 这不完全是DSL,而是一种无需编程即可自动执行过程的便捷机制。 Qt SCXML具有出色的实现。 还有其他实现,但是它们并不那么灵活。
这是SCXML表示法中FTP客户端的示例,该示例来自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="&quot;submit.&quot; + _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可视化器中看起来


ftp-client-scxml


资料存取与整合


这可能是++在世界上最“痛”的话题之一。 对于C ++开发人员而言,数据世界始终与能够在编程语言的本质上进行显示的需求联系在一起。 表中的行在对象或结构中,json在类中,依此类推。 在没有反思的情况下-这是一个巨大的问题,但是我们有了++-昵称就不会感到绝望并找到各种解决方法。 让我们从DBMS开始。


现在,我将变得司空见惯,但是ODBC是访问关系DBMS的唯一通用机制,还没有发明其他选择,但是C ++ –社区并没有停滞不前,并且今天有库和框架为多个DBMS提供了通用的访问接口。
首先,我将提到使用客户端库和SQL提供对DBMS的统一访问的库



所有这些都很好,但是它们使您记住将数据库中的数据显示到C ++对象和结构中的细微差别,再加上SQL查询的效率立即落在您的肩膀上。
以下示例是C ++中的ORM。 是的,有! 顺便说一下,SOCI通过专门化soci :: type_conversion支持ORM机制,但是我故意不包括它,因为这不是它的直接目的。


  • LiteSQL C ++ -ORM,允许您与DBMS SQLite3,PostgreSQL,MySQL进行交互。 该库要求程序员预先设计带有对象和关系描述的xml文件,以便使用litesql-gen生成其他源。
  • 来自Code Synthesis的ODB是一个非常有趣的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 ++是一个大型框架,一般而言,您可以写一篇有关它的单独文章,它还包含可以与DBMS Sqlite3,Firebird,MariaDB / MySQL,MSSQL Server,PostgreSQL和Oracle交互的ORM。
  • 我还想提到ORM,而不是sqlite sqlite_ormhiberlite 。 由于sqlite是嵌入式DBMS,并且ORM会在编译时检查查询以及与数据库的所有交互,因此该工具非常便于快速部署和原型制作。
  • QHibernate-具有Postgresql支持的Qt5的 ORM。 浸入了来自Java的休眠思想。

尽管通过DBMS进行的集成被视为“集成”,但我更喜欢将其放在括号之外,而继续通过协议和API进行集成。


  • RPC-远程处理调用,一种用于“客户端”与“服务器”交互的众所周知的技术。 与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项目的出现,您可以免费使用它。


  • 消息传递是一个广义的概念,但是我将从通过通用数据总线进行消息传递的角度来分析它,即,我将研究使用各种协议(例如AMQPSTOMP)实现Java消息服务的库和服务器。 通用数据总线,也称为企业服务总线(ESB),在企业级解决方案中非常常见,例如 使您可以使用点对点和发布-订阅模式将IT基础架构的各个元素快速集成在一起。 很少有用 C ++编写的工业消息代理,我知道两个: Apache QpidUPMQ ,第二个是我写的。 有Eclipse Mosquitto ,但是它是用si编写的。 JMS for Java的优点在于,您的代码不依赖于客户端和服务器使用的协议(作为ODBC的JMS声明函数和行为),因此您可以每天至少更改一次JMS提供程序,而不必重写代码,例如不幸的是,C ++不是。 您将必须为每个提供程序重写客户端部分。 我认为,对于不那么流行的消息代理,我将列出最受欢迎的C ++库:



    这些库提供常规功能的原理通常与JMS规范一致。 在这方面,希望组建一群志趣相投的人,并编写某种ODBC,但是对于消息代理,这样每个C ++程序员所受的苦难都比平时少。



网络连接


我故意将与网络交互直接连接的所有内容都留到最后,因为 在我看来,在这一领域,C ++开发人员的问题最少。 剩下的只是选择最接近您的决策的模式以及实现该决策的框架。 在列出最受欢迎的库之前,我想说明一下开发自己的网络应用程序时的重要细节。 如果决定通过TCP或UDP提出自己的协议,请准备好各种“智能”安全工具将阻止您的通信,因此请谨慎打包协议,例如在https中打包,否则可能会出现问题。 因此库:


  • Boost.AsioBoost.Beast-异步网络通信最流行的实现之一,它支持HTTP和WebSockets
  • Poco :: Net也是一个非常流行的解决方案,除了原始交互之外,您还可以使用现成的TCP Server Framework,Reactor Framework类以及HTTP,FTP和E-Mail的客户端和服务器类。 还支持WebSockets
  • ACE-从未使用过该库,但同事们说它也是一个有价值的库,具有用于实现网络应用程序等的集成方法。
  • Qt网络 -在Qt中,网络部分实现良好。 唯一有争议的点是服务器解决方案的信号和插槽,尽管服务器位于Qt!上。

合计


因此,我想说的是这些库的概述。 如果我成功了,那么您的印象是,这里有一个“企业版”,但是没有实现和使用的解决方案,只有一个动物园。 确实如此。 有或多或少完整的库可用于为企业部门开发应用程序,但是没有标准的解决方案。 我个人只能推荐pocoprojectmaccina.io作为研究后端解决方案的起点,并在任何情况下提高性能,当然,我正在寻找志趣相投的人来推广“ C ++企业版”的概念!

Source: https://habr.com/ru/post/zh-CN455726/


All Articles