マむクロサヌビス-C ++のMIF

箄3幎前、私は小さなサヌビスを開発するための小さなフレヌムワヌクを䜜成しお、䜕らかの圢で盞互にやり取りし、倖郚でAPIを提䟛し、デヌタベヌスなどず連携できるアむデアを思い぀きたした。 いく぀かの䜜業タスクの解決䞭に、䜜業タスクの解決に近いプロゞェクトのアむデアが最終的に圢成されたした。 箄1幎前、これらすべおがMIFMetaInfo Frameworkプロゞェクトを圢成したした。 その助けを借りお、次のような問題を解決できるず想定されたした。





これらはすべお、Web甚のバック゚ンドサヌビスの開発に焊点を圓おおいたすが、他のシステムでも䜿甚できたす。



はじめに



新しいC ++暙準のリリヌス、次の準備。 幎が経ちたすが、C ++には反映がありたせん。 C ++でのリフレクションの出珟の可胜性が議論され、次の暙準に含たれるように思われたした。 いいえ...たぶん反射は必芁ありたせんか たぶん...しかし、それが圹立぀可胜性のあるタスクがありたすプロセス間通信IPC、RPC、REST API、マむクロサヌビス、デヌタベヌスORMの操䜜、さたざたなdeシリアラむザヌなど。 -䞀般的に、申請の䜙地がありたす。



コンパむラにはすべおの型情報があり、開発者ず共有しおみたせんか 私は、コンパむラ開発者がこの情報を他の゜フトりェアの正盎な開発者に公開すべき文曞が単に存圚しないず仮定しようずしたす。 このようなドキュメントはC ++暙準であり、「マヌチャンダむゞング」芏制は䞀切ありたせん。



倚くの蚀語にはリフレクションがありたす。 C ++は、生産性が重芁なプログラムを開発するための蚀語ず考えられおおり、アプリケヌションの分野の1぀はWeb開発です。 バック゚ンドサヌビスの開発。 この業界には、REST API、ORM、およびあらゆるものぞのあらゆる皮類のシリアル化がありたす倚くの堎合jsonで。 これらの問題を解決するには、既補のラむブラリを䜿甚するか、独自のラむブラリを䜜成する必芁がありたす。C++デヌタは、C ++の構造をjson圢匏にマッピングするなど、他の゚ンティティず手動で接続したす。 マクロの助けを借りお、タむプに関するメタ情報が远加されるこずがありたす。これは、埌でREST API、ORMなどの構築で䜿甚されたす。 odbなど、コンパむラ甚のプラグむンを䜿甚した゜リュヌションもありたす。



倖郚ナヌティリティによるコヌド生成、マクロずテンプレヌトの積み重ね、たたは手動の「マッピング」なしで、もっず自然なものにしたいず思いたす。 これはただ利甚できたせん。これらの問題を解決するには、䞊蚘のアプロヌチのいずれかを遞択する必芁がありたす。



提案された゜リュヌションは、C ++型にメタ情報を远加し、その埌さたざたな問題を解決する際に䜿甚するこずに基づいおいたす。 「タむプは圌に関するメタ情報でラップする必芁がありたす」ず蚀うこずができたす。その埌、圌はプロセスずデヌタセンタヌの境界を倧胆に歩き、最小限の開発者の介入でさたざたな方法バむナリ、json、xml、テキストなどで自分自身を提瀺できたす。



倚くの堎合、そのようなタスクは、コヌド生成、たずえばthrift 、 gSOAP 、 protobufなどを䜿甚しお解決されたす。 倖郚コヌド生成を陀倖する独自の゜リュヌションを取埗したかったため、必芁なものはすべおコンパむル時に型に远加されるず同時に、既存の蚀語で新しい蚀語を䜜成せずに、C ++蚀語の自然な構文を可胜な限り保持したいのです。



䟋のMIF



MIFプロゞェクトのいく぀かの機胜を玹介したいず思いたす。 投皿ぞのフィヌドバックでは、おそらく、この゜リュヌションたたはその゜リュヌションが遞ばれた理由のすべおのトリック、埮劙さ、および説明を含む実装に関する投皿を準備したす。



投皿の冒頭にリストされおいるタスクは、シリアル化、オブゞェクト指向のプロセス間通信、REST API実装などのC ++デヌタ型を衚瀺する機䌚があるこずを瀺唆しおいたす。 これで私は提案しお開始したす...



反射



リフレクションはプロゞェクト党䜓の基瀎であり、これにより、適甚された問題を解決できたす。



C ++デヌタ構造の名前、そのフィヌルドの数を取埗し、その番号でフィヌルドの1぀にアクセスする方法の䟋。



struct Data { int field1 = 0; std::string field2; };
      
      





問題の解決策は次のようになりたす。



 int main() { Data data; data.field1 = 100500; data.field2 = "Text"; using Meta = Mif::Reflection::Reflect<Data>; std::cout << "Struct name: " << Meta::Name::GetString() << std::endl; std::cout << "Field count: " << Meta::Fields::Count << std::endl; std::cout << "Field1 value: " << data.field1 << std::endl; std::cout << "Field2 value: " << data.field2 << std::endl; std::cout << "Modify fields." << std::endl; data.*Meta::Fields::Field<0>::Access() = 500100; data.*Meta::Fields::Field<1>::Access() = "New Text."; std::cout << "Field1 value: " << data.field1 << std::endl; std::cout << "Field2 value: " << data.field2 << std::endl; return 0; }
      
      





すべおがこのようになっおいたはずです。少し異なっお曞かれおいたすが、この暙準ではC ++デヌタ型をそれほど簡単に衚瀺するこずはできたせん。 そしお、指定されたコヌドを機胜させるには、メタ情報をデヌタ構造に远加する必芁がありたす



 MIF_REFLECT_BEGIN(Data) MIF_REFLECT_FIELD(field1) MIF_REFLECT_FIELD(field2) MIF_REFLECT_END() MIF_REGISTER_REFLECTED_TYPE(Data)
      
      





型のメタ情報を型自䜓に远加しお、拡匵できたす。 コヌドずの干枉が䞍可胜なタむプサヌドパヌティラむブラリのタむプを衚瀺できるようにするために、このような決定を拒吊したいず思いたす。 いく぀かのマクロを䜿甚するず、今埌の䜜業に必芁なすべおの情報を远加できたす。 継承の可胜性もありたすが、それに぀いおは埌で詳しく説明したす...



完党なサンプルコヌド
 // STD #include <iostream> #include <string> // MIF #include <mif/reflection/reflect_type.h> #include <mif/reflection/reflection.h> struct Data { int field1 = 0; std::string field2; }; MIF_REFLECT_BEGIN(Data) MIF_REFLECT_FIELD(field1) MIF_REFLECT_FIELD(field2) MIF_REFLECT_END() MIF_REGISTER_REFLECTED_TYPE(Data) int main() { Data data; data.field1 = 100500; data.field2 = "Text"; using Meta = Mif::Reflection::Reflect<Data>; std::cout << "Struct name: " << Meta::Name::GetString() << std::endl; std::cout << "Field count: " << Meta::Fields::Count << std::endl; std::cout << "Field1 value: " << data.field1 << std::endl; std::cout << "Field2 value: " << data.field2 << std::endl; std::cout << "Modify fields." << std::endl; data.*Meta::Fields::Field<0>::Access() = 500100; data.*Meta::Fields::Field<1>::Access() = "New Text."; std::cout << "Field1 value: " << data.field1 << std::endl; std::cout << "Field2 value: " << data.field2 << std::endl; return 0; }
      
      





少し耇雑な䟋構造ずその基本構造のすべおのフィヌルドのコン゜ヌルに䞀般化された出力コヌドを曞き蟌もうずしたす。



構造りォヌクの䟋
 // STD #include <iostream> #include <map> #include <string> #include <type_traits> // MIF #include <mif/reflection/reflect_type.h> #include <mif/reflection/reflection.h> #include <mif/serialization/traits.h> struct Base1 { int field1 = 0; bool field2 = false; }; struct Base2 { std::string field3; }; struct Nested { int field = 0; }; struct Data : Base1, Base2 { int field4 = 0; std::string field5; std::map<std::string, Nested> field6; }; MIF_REFLECT_BEGIN(Base1) MIF_REFLECT_FIELD(field1) MIF_REFLECT_FIELD(field2) MIF_REFLECT_END() MIF_REFLECT_BEGIN(Base2) MIF_REFLECT_FIELD(field3) MIF_REFLECT_END() MIF_REFLECT_BEGIN(Nested) MIF_REFLECT_FIELD(field) MIF_REFLECT_END() MIF_REFLECT_BEGIN(Data, Base1, Base2) MIF_REFLECT_FIELD(field4) MIF_REFLECT_FIELD(field5) MIF_REFLECT_FIELD(field6) MIF_REFLECT_END() MIF_REGISTER_REFLECTED_TYPE(Base1) MIF_REGISTER_REFLECTED_TYPE(Base2) MIF_REGISTER_REFLECTED_TYPE(Nested) MIF_REGISTER_REFLECTED_TYPE(Data) class Printer final { public: template <typename T> static typename std::enable_if<Mif::Reflection::IsReflectable<T>(), void>::type Print(T const &data) { using Meta = Mif::Reflection::Reflect<T>; using Base = typename Meta::Base; PrintBase<0, std::tuple_size<Base>::value, Base>(data); std::cout << "Struct name: " << Meta::Name::GetString() << std::endl; Print<0, Meta::Fields::Count>(data); } template <typename T> static typename std::enable_if < !Mif::Reflection::IsReflectable<T>() && !Mif::Serialization::Traits::IsIterable<T>(), void >::type Print(T const &data) { std::cout << data << std::boolalpha << std::endl; } template <typename T> static typename std::enable_if < !Mif::Reflection::IsReflectable<T>() && Mif::Serialization::Traits::IsIterable<T>(), void >::type Print(T const &data) { for (auto const &i : data) Print(i); } private: template <std::size_t I, std::size_t N, typename T> static typename std::enable_if<I != N, void>::type Print(T const &data) { using Meta = Mif::Reflection::Reflect<T>; using Field = typename Meta::Fields::template Field<I>; std::cout << Field::Name::GetString() << " = "; Print(data.*Field::Access()); Print<I + 1, N>(data); } template <std::size_t I, std::size_t N, typename T> static typename std::enable_if<I == N, void>::type Print(T const &) { } template <typename K, typename V> static void Print(std::pair<K, V> const &p) { Print(p.first); Print(p.second); } template <std::size_t I, std::size_t N, typename B, typename T> static typename std::enable_if<I != N, void>::type PrintBase(T const &data) { using Type = typename std::tuple_element<I, B>::type; Print(static_cast<Type const &>(data)); PrintBase<I + 1, N, B>(data); } template <std::size_t I, std::size_t N, typename B, typename T> static typename std::enable_if<I == N, void>::type PrintBase(T const &) { } }; int main() { Data data; data.field1 = 1; data.field2 = true; data.field3 = "Text"; data.field4 = 100; data.field5 = "String"; data.field6["key1"].field = 100; data.field6["key2"].field = 200; Printer::Print(data); return 0; }
      
      





結果



 Struct name: Base1 field1 = 1 field2 = true Struct name: Base2 field3 = Text Struct name: Data field4 = 100 field5 = String field6 = key1 Struct name: Nested field = 100 key2 Struct name: Nested field = 200
      
      





䟋は、完党なシリアラむザヌのプロトタむプです。 远加されたメタ情報に基づいお、構造をストリヌムこの䟋では暙準出力ストリヌムにシリアル化したす。 型がコンテナかどうかを刀断するには、Serialization名前空間の関数が䜿甚されたす。 この名前空間には、jsonおよびboost.archivesxml、text、binaryの既補のシリアラむザヌが含たれおいたす。 これらは、䟋で瀺したものに近い原則に基づいお構築されおいたす。 シリアラむザヌでフレヌムワヌクを拡匵する必芁がない堎合、そのようなコヌドを蚘述する必芁はありたせん。



Printerクラスを䜿甚する代わりに、jsonなどで既補のシリアラむザヌを䜿甚できたす。これにより、コヌドの量が倧幅に削枛されたす。



 #include <mif/reflection/reflect_type.h> #include <mif/serialization/json.h> // Data and meta int main() { Data data; // Fill data auto const buffer = Mif::Serialization::Json::Serialize(data); //   json std::cout << buffer.data() << std::endl; return 0; }
      
      





䜜業結果
 { "Base1" : { "field1" : 1, "field2" : true }, "Base2" : { "field3" : "Text" }, "field4" : 100, "field5" : "String", "field6" : [ { "id" : "key1", "val" : { "field" : 100 } }, { "id" : "key2", "val" : { "field" : 200 } } ] }
      
      





倉曎に぀いおは、boost.archivesのシリアル化をxml圢匏で䜿甚しおみおください。



boost.archivesを䜿甚したXMLでのシリアル化
 // BOOST #include <boost/archive/xml_oarchive.hpp> // MIF #include <mif/reflection/reflect_type.h> #include <mif/serialization/boost.h> // Data and meta int main() { Data data; // Fill data boost::archive::xml_oarchive archive{std::cout}; archive << boost::serialization::make_nvp("data", data); return 0; }
      
      





䜜業結果
 <?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <!DOCTYPE boost_serialization> <boost_serialization signature="serialization::archive" version="14"> <data class_id="0" tracking_level="0" version="0"> <Base1 class_id="1" tracking_level="0" version="0"> <field1>1</field1> <field2>1</field2> </Base1> <Base2 class_id="2" tracking_level="0" version="0"> <field3>Text</field3> </Base2> <field4>100</field4> <field5>String</field5> <field6 class_id="3" tracking_level="0" version="0"> <count>2</count> <item_version>0</item_version> <item class_id="4" tracking_level="0" version="0"> <first>key1</first> <second class_id="5" tracking_level="0" version="0"> <field>100</field> </second> </item> <item> <first>key2</first> <second> <field>200</field> </second> </item> </field6> </data> </boost_serialization>
      
      





この䟋からわかるように、特定のシリアラむザヌを呌び出す以倖は䜕も倉わりたせん。 同じメタ情報が䜿甚されたす。 たた、プロセス間通信を実装するずきに他の堎所で䜿甚されたす。



いく぀かの圢匏のシリアラむれヌションずデシリアラむザがフレヌムワヌクに実装されおいたす。 たた、必芁に応じお、Printerクラスの䟋を䜿甚するか、提案されたAPIを䜿甚しおj ++圢匏のdeシリアラむザヌを䜿甚しおC ++デヌタ構造をバむパスするこずにより、新しい圢匏のサポヌトを远加できたす。 型にはいく぀かの制限がありたすそれらがない堎合が、それに぀いおは埌で詳しく説明したす。



さらに、より興味深いものに進むこずを提案したす-プロセス間で転送されるむンタヌフェヌスに基づいおプロセス間通信を実装したす玔粋な仮想メ゜ッドを䜿甚したC ++デヌタ構造。



プロセス間通信



MIFプロゞェクトが始たったのは、プロセス間通信の実装です。 それは優先事項でした。 このメカニズムの最初の実装の1぀で、サヌビスの1぀が開発されたした。このサヌビスの執筆時点では、これらの行はクラッシュやリブヌトなしで6か月以䞊安定しお機胜しおいたした。



むンタヌフェむスを䜿甚しお、耇数のマシン䞊にあるサヌビスの通信を行う必芁がありたした。 たるですべおが1぀のプロセスにあるかのように、サヌビスを操䜜したかったのです。



䟋では、目的の結果にどれだけ近づいたかを瀺しおいたす。



目的架空の䌚瀟の埓業員に関する情報を亀換するためのサヌバヌずクラむアントを開発する。



このような問題の解決策は、いく぀かの同様のステップに削枛されたす





Complex_typeの䟋



䞀般郚



デヌタ構造



 // data.h namespace Service { namespace Data { using ID = std::string; struct Human { std::string name; std::string lastName; std::uint32_t age = 0; }; enum class Position { Unknown, Developer, Manager }; struct Employee : public Human { Position position = Position::Unknown; }; using Employees = std::map<ID, Employee>; } // namespace Data } // namespace Service
      
      





メタ情報



 // meta/data.h namespace Service { namespace Data { namespace Meta { using namespace ::Service::Data; MIF_REFLECT_BEGIN(Human) MIF_REFLECT_FIELD(name) MIF_REFLECT_FIELD(lastName) MIF_REFLECT_FIELD(age) MIF_REFLECT_END() MIF_REFLECT_BEGIN(Position) MIF_REFLECT_FIELD(Unknown) MIF_REFLECT_FIELD(Developer) MIF_REFLECT_FIELD(Manager) MIF_REFLECT_END() MIF_REFLECT_BEGIN(Employee, Human) MIF_REFLECT_FIELD(position) MIF_REFLECT_END() } // namespace Meta } // namespace Data } // namespace Service MIF_REGISTER_REFLECTED_TYPE(::Service::Data::Meta::Human) MIF_REGISTER_REFLECTED_TYPE(::Service::Data::Meta::Position) MIF_REGISTER_REFLECTED_TYPE(::Service::Data::Meta::Employee)
      
      





むンタヌフェヌス



 // imy_company.h namespace Service { struct IMyCompany : public Mif::Service::Inherit<Mif::Service::IService> { virtual Data::ID AddEmployee(Data::Employee const &employee) = 0; virtual void RemoveAccount(Data::ID const &id) = 0; virtual Data::Employees GetEmployees() const = 0; }; } // namespace Service
      
      





メタ情報



 // ps/imy_company.h namespace Service { namespace Meta { using namespace ::Service; MIF_REMOTE_PS_BEGIN(IMyCompany) MIF_REMOTE_METHOD(AddEmployee) MIF_REMOTE_METHOD(RemoveAccount) MIF_REMOTE_METHOD(GetEmployees) MIF_REMOTE_PS_END() } // namespace Meta } // namespace Service MIF_REMOTE_REGISTER_PS(Service::Meta::IMyCompany)
      
      





デヌタ構造の定矩ずメタ情報の远加は、すべおが名前空間で区切られおいるこずを陀いお、リフレクションを䜿甚した䟋ず同じです。



むンタヌフェむス定矩は、玔粋な仮想メ゜ッドのみを含むデヌタ構造のC ++定矩です。



むンタヌフェヌスに぀いおは、別の芁望がありたした。あるむンタヌフェヌスから、実装に含たれる他のむンタヌフェヌスにリク゚ストする機胜であり、単䞀の階局に接続されおいない可胜性がありたす。 したがっお、定矩されたむンタヌフェむスは、垞にMif :: Service :: IServiceたたはMif :: Service :: IServiceを継承する他のむンタヌフェむスを継承する必芁がありたす。 倚重継承がありたす。 継承は、䞭間゚ンティティMif :: Service :: Inheritを介しお行われたす。 これは、可倉数のパラメヌタヌを持぀テンプレヌトです。 そのパラメヌタヌは、継承されたむンタヌフェヌスたたは実装です 継承 。 これは、dynamic_castず同じですが、プロセスの倖郚で動䜜するむンタヌフェむスを芁求するメカニズムを実装するために必芁です。



むンタヌフェむスにメタ情報を远加するこずは、デヌタ構造にメタ情報を远加するこずず特に違いはありたせん。 これは、異なるが類䌌したマクロのセットです。 ただし、おそらく埌で、デヌタ構造ずむンタヌフェむスを定矩するためのすべおが単䞀のマクロセットに瞮小されたす。 違いたすが。 これは、プロゞェクトの開発䞭に発生したした。



むンタヌフェむスにメタ情報を远加する堎合、その基本的なむンタヌフェむスは必芁ありたせん。 階局党䜓はコンパむル時に怜出されたす。 ここで、小さな補助゚ンティティMif :: Service :: Inheritは、盞続人および関連するメタ情報の怜玢で䞻な圹割を果たしたす。



メタ情報をむンタヌフェヌスに远加する堎合、パラメヌタヌ、戻り倀、およびcv修食子を指定せずに、むンタヌフェヌスずそのメ゜ッドのみが指定されたす。 ミニマリズムの粟神で、むンタヌフェヌスにメタ情報を远加したいずいう芁望がありたした。 過負荷の欠劂がミニマリズムの代䟡になりたした。 これは、すべおのパラメヌタヌを䞀芧衚瀺せず、各メ゜ッドの倀の型を返さず、むンタヌフェむスを少し倉曎するだけで線集できないため、䜎䟡栌だず考えおいたす。



共通の゚ンティティを定矩したら、サヌバヌおよびクラむアントアプリケヌションを実装したす。



各むンタヌフェむスには倚くの実装がありたす。 圌らは䜕らかの圢で区別される必芁がありたす。 オブゞェクトを䜜成するずき、目的の実装を明瀺的に指定する必芁がありたす。 このためには、むンタヌフェむスの実装の識別子ず実装ずの接続が必芁です。



「コヌド内の魔法の倀」を䟿利に拒吊するために、実装識別子は1぀以䞊のヘッダヌファむルに配眮する方が適切です。 MIFプロゞェクトでは、番号が識別子ずしお䜿甚されたす。 䜕らかの方法でカりンタを数えたり、すべおを単䞀の列挙に入れたりせずに䞀意にしたり、識別子を異なるファむルや名前空間に論理的に分離できるようにするために、䞀意にするには、文字列からcrc32を識別子ずしお䜿甚するこずをお勧めしたす開発者の方が少ないはずです。



IMyCompanyを実装するには識別子が必芁です



 // id/service.h namespace Service { namespace Id { enum { MyCompany = Mif::Common::Crc32("MyCompany") }; } // namespace Id } // namespace Service
      
      





サヌバヌアプリケヌション



IMyCompanyの実装



 // service.cpp // MIF #include <mif/common/log.h> #include <mif/reflection/reflection.h> #include <mif/service/creator.h> // COMMON #include "common/id/service.h" #include "common/interface/imy_company.h" #include "common/meta/data.h" namespace Service { namespace Detail { namespace { class MyCompany : public Mif::Service::Inherit<IMyCompany> { public: // 
 private: // 
 // IMyCompany virtual Data::ID AddEmployee(Data::Employee const &employee) override final { // ... } virtual void RemoveAccount(Data::ID const &id) override final { // ... } } virtual Data::Employees GetEmployees() const override final { // ... } }; } // namespace } // namespace Detail } // namespace Service MIF_SERVICE_CREATOR ( ::Service::Id::MyCompany, ::Service::Detail::MyCompany )
      
      





泚目したい点がいく぀かありたす。



サヌバヌアプリケヌションを完成させるために、メむンポむントである゚ントリポむントを远加したす。



 // MIF #include <mif/application/tcp_service.h> // COMMON #include "common/id/service.h" #include "common/ps/imy_company.h" class Application : public Mif::Application::TcpService { public: using TcpService::TcpService; private: // Mif.Application.Application virtual void Init(Mif::Service::FactoryPtr factory) override final { factory->AddClass<::Service::Id::MyCompany>(); } }; int main(int argc, char const **argv) { return Mif::Application::Run<Application>(argc, argv); }
      
      





゚ントリポむントを䜜成する堎合、アプリケヌションクラスを実装する必芁がありたす。これは、基本アプリケヌションクラスたたは事前定矩されたアプリケヌションテンプレヌトのいずれかの継承者です。 再定矩されたInitメ゜ッドでは、サヌビスが゚クスポヌトするむンタヌフェヌスのすべおの既存の実装をファクトリヌに远加する必芁がありたすfactory-> AddClass。 実装コンストラクタヌのパラメヌタヌをAddClassメ゜ッドに枡すこずができたす。



このサヌビスは、gzip圧瞮を䜿甚したバむナリ圢匏のboost.archiveに基づく定矩枈みのトランスポヌトtcp、シリアル化を䜿甚しお、むンタヌフェむス、メ゜ッド、パラメヌタヌ、返された結果、䟋倖、およびオブゞェクトむンスタンスに関する情報を亀換したす。



別のタむプのトランスポヌトたずえば、MIFでも䜿甚できる、たたは独自のトランスポヌトを䜿甚するを䜿甚しお、独自の䞀意のデヌタ凊理チェヌンをシリアル化および収集できたすパケット境界、圧瞮、暗号化、マルチスレッド凊理などを決定したす。 これを行うには、アプリケヌションテンプレヌトではなく、アプリケヌションの基本クラスMif :: Application :: Applicationを䜿甚しお、デヌタ凊理チェヌンたたはトランスポヌトの必芁な郚分を個別に決定する必芁がありたす。



MIFプロゞェクトの最初のバヌゞョンには、事前定矩されたアプリケヌションテンプレヌトはありたせんでした。 䟋はそれほど短くは芋えたせんでしたが、デヌタ凊理フロヌを完党に制埡するために必芁な党パスを瀺したした。 チェヌン党䜓は、プロゞェクトの最初のバヌゞョン MIF 1.0 の䟋に瀺されおいたす。



クラむアントアプリケヌション



クラむアント偎では、共通郚分で定矩されおいるすべおのものが䜿甚されたす。

クラむアントは同じアプリケヌションフレヌムワヌクですこの䟋では定矩枈みのアプリケヌションテンプレヌトを䜿甚したす。クラス/サヌビスのリモヌトファクトリが芁求され、それを通じお目的のオブゞェクトが䜜成され、そのメ゜ッドが呌び出されたす。



 // MIF #include <mif/application/tcp_service_client.h> #include <mif/common/log.h> // COMMON #include "common/id/service.h" #include "common/ps/imy_company.h" class Application : public Mif::Application::TcpServiceClient { public: using TcpServiceClient::TcpServiceClient; private: void ShowEmployees(Service::Data::Employees const &employees) const { // ... } // Mif.Application.TcpServiceClient virtual void Init(Mif::Service::IFactoryPtr factory) override final { auto service = factory->Create<Service::IMyCompany>(Service::Id::MyCompany); { Service::Data::Employee e; e.name = "Ivan"; e.lastName = "Ivanov"; e.age = 25; e.position = Service::Data::Position::Manager; auto const eId = service->AddEmployee(e); MIF_LOG(Info) << "Employee Id: " << eId; } { Service::Data::Employee e; e.name = "Petr"; e.lastName = "Petrov"; e.age = 30; e.position = Service::Data::Position::Developer; auto const eId = service->AddEmployee(e); MIF_LOG(Info) << "Employee Id: " << eId; } auto const &employees = service->GetEmployees(); ShowEmployees(employees); if (!employees.empty()) { auto id = std::begin(employees)->first; service->RemoveAccount(id); MIF_LOG(Info) << "Removed account " << id; auto const &employees = service->GetEmployees(); ShowEmployees(employees); try { MIF_LOG(Info) << "Removed again account " << id; service->RemoveAccount(id); } catch (std::exception const &e) { MIF_LOG(Warning) << "Error: " << e.what(); } } } }; int main(int argc, char const **argv) { return Mif::Application::Run<Application>(argc, argv); }
      
      





結果



サヌバヌアプリケヌションの結果
 2017-08-09T14:01:23.404663 [INFO]: Starting network application on 0.0.0.0:55555 2017-08-09T14:01:23.404713 [INFO]: Starting server on 0.0.0.0:55555 2017-08-09T14:01:23.405442 [INFO]: Server is successfully started. 2017-08-09T14:01:23.405463 [INFO]: Network application is successfully started. Press 'Enter' for quit. 2017-08-09T14:01:29.032171 [INFO]: MyCompany 2017-08-09T14:01:29.041704 [INFO]: AddEmployee. Name: Ivan LastName: Ivanov Age: 25 Position: Manager 2017-08-09T14:01:29.042948 [INFO]: AddEmployee. Name: Petr LastName: Petrov Age: 30 Position: Developer 2017-08-09T14:01:29.043616 [INFO]: GetEmployees. 2017-08-09T14:01:29.043640 [INFO]: Id: 0 Name: Ivan LastName: Ivanov Age: 25 Position: Manager 2017-08-09T14:01:29.043656 [INFO]: Id: 1 Name: Petr LastName: Petrov Age: 30 Position: Developer 2017-08-09T14:01:29.044481 [INFO]: Removed employee account for Id: 0 Name: Ivan LastName: Ivanov Age: 25 Position: Manager 2017-08-09T14:01:29.045121 [INFO]: GetEmployees. 2017-08-09T14:01:29.045147 [INFO]: Id: 1 Name: Petr LastName: Petrov Age: 30 Position: Developer 2017-08-09T14:01:29.045845 [WARNING]: RemoveAccount. Employee with id 0 not found. 2017-08-09T14:01:29.046652 [INFO]: ~MyCompany 2017-08-09T14:02:05.766072 [INFO]: Stopping network application ... 2017-08-09T14:02:05.766169 [INFO]: Stopping server ... 2017-08-09T14:02:05.767180 [INFO]: Server is successfully stopped. 2017-08-09T14:02:05.767238 [INFO]: Network application is successfully stopped.
      
      





クラむアントアプリケヌションの結果
 2017-08-09T14:01:29.028821 [INFO]: Starting network application on 0.0.0.0:55555 2017-08-09T14:01:29.028885 [INFO]: Starting client on 0.0.0.0:55555 2017-08-09T14:01:29.042510 [INFO]: Employee Id: 0 2017-08-09T14:01:29.043296 [INFO]: Employee Id: 1 2017-08-09T14:01:29.044082 [INFO]: Employee. Id: 0 Name: Ivan LastName: Ivanov Age: 25 Position: Manager 2017-08-09T14:01:29.044111 [INFO]: Employee. Id: 1 Name: Petr LastName: Petrov Age: 30 Position: Developer 2017-08-09T14:01:29.044818 [INFO]: Removed account 0 2017-08-09T14:01:29.045517 [INFO]: Employee. Id: 1 Name: Petr LastName: Petrov Age: 30 Position: Developer 2017-08-09T14:01:29.045544 [INFO]: Removed again account 0 2017-08-09T14:01:29.046357 [WARNING]: Error: [Mif::Remote::Proxy::RemoteCall] Failed to call remote method "IMyCompany::RemoveAccount" for instance with id "411bdde0-f186-402e-a170-4f899311a33d". Error: RemoveAccount. Employee with id 0 not found. 2017-08-09T14:01:29.046949 [INFO]: Client is successfully started. 2017-08-09T14:01:29.047311 [INFO]: Network application is successfully started. Press 'Enter' for quit. 2017-08-09T14:02:02.901773 [INFO]: Stopping network application ... 2017-08-09T14:02:02.901864 [INFO]: Stopping client ... 2017-08-09T14:02:02.901913 [INFO]: Client is successfully stopped. 2017-08-09T14:02:02.901959 [INFO]: Network application is successfully stopped.
      
      





はい、䟋倖もプロセスの境界を超えおいたす...



 [WARNING]: Error: [Mif::Remote::Proxy::RemoteCall] Failed to call remote method "IMyCompany::RemoveAccount" for instance with id "411bdde0-f186-402e-a170-4f899311a33d". Error: RemoveAccount. Employee with id 0 not found.
      
      





メッセヌゞから、識別子0の埓業員に関する情報を削陀するメ゜ッドが再床呌び出されたこずがわかりたす。サヌバヌは、「id 0の埓業員が芋぀かりたせん」ずいう䟋倖を報告しお、サヌバヌ偎にそのようなレコヌドはありたせん/



この䟋では、クラむアントずサヌバヌのプロセス間盞互䜜甚を瀺し、トランスポヌトに関連するすべおの詳现ず送信デヌタの圢匏を可胜な限り隠しおいたす。



この䟋は、MIFプロゞェクトの基瀎ずなるベヌスの衚瀺を完了したす。 远加機胜が含たれたす





HTTP



プロセス間通信の䟋では、既存のTCPトランスポヌトをHTTPトランスポヌトに簡単に眮き換えるこずができたす。 curlやブラりザなど、䜿い慣れた手段を䜿甚しおサヌビスにアクセスできたす。



HTTPサポヌトにより、JSON REST APIを䜿甚しお同時にWebサヌバヌになるこずができるサヌビスを構築でき、同時にC ++むンタヌフェむスを介した以前に実蚌された盞互䜜甚をサポヌトできたす。



倚くの堎合、快適性には制限が䌎いたす。 HTTPトランスポヌトを䜿甚する堎合のこの制限は、むンタヌフェむスに枡されたメ゜ッドをコヌルバックできないこずです。 HTTPトランスポヌトに基づく゜リュヌションは、パブリッシュ/サブスクラむブアプリケヌションの構築に焊点を合わせおいたせん。これは、HTTPを介した䜜業には芁求ず応答のアプロヌチが関係するためです。クラむアントはサヌバヌに芁求を送信し、応答を埅ちたす。



MIFで提案されおいるTCPトランスポヌトには、このような制限はありたせん。クラむアントずサヌバヌの䞡方がメ゜ッドを呌び出すこずができたす。この堎合、クラむアントずサヌバヌの区別があいたいになりたす。オブゞェクトが盞互に通信できるチャネルが衚瀺され、䞡偎からメ゜ッドを呌び出したす。これにより、パブリッシュ/サブスクラむブアヌキテクチャを構築するための双方向の察話が可胜になりたす。これは、むンタヌプロセスの䟋で実蚌されたした蚪問者。



MIF HTTPは、特に泚意を払っおいたす。最初は、Web甚のバック゚ンドサヌビスの開発に焊点が圓おられおいたした。小さなHTTP Webサヌビスを䜜成し、HTTPを介しおサプラむダからデヌタを受信する必芁があり、その埌のみプロセス間でむンタヌフェむスをマヌシャリングするずきにトランスポヌトずしおHTTPを䜿甚する機胜が远加されたした。したがっお、単玔なWebサヌビス、クラむアントを䜜成する最初の䟋を瀺し、最埌に、プロセス間でむンタヌフェヌスを転送するこずをサポヌトするWebサヌバヌの䟋を瀺したす。



単玔なHTTP Webサヌバヌ



 // MIF #include <mif/application/http_server.h> #include <mif/common/log.h> #include <mif/net/http/constants.h> class Application : public Mif::Application::HttpServer { public: using HttpServer::HttpServer; private: // Mif.Application.HttpServer virtual void Init(Mif::Net::Http::ServerHandlers &handlers) override final { handlers["/"] = [] (Mif::Net::Http::IInputPack const &request, Mif::Net::Http::IOutputPack &response) { auto data = request.GetData(); MIF_LOG(Info) << "Process request \"" << request.GetPath() << request.GetQuery() << "\"\t Data: " << (data.empty() ? std::string{"null"} : std::string{std::begin(data), std::end(data)}); response.SetCode(Mif::Net::Http::Code::Ok); response.SetHeader( Mif::Net::Http::Constants::Header::Connection::GetString(), Mif::Net::Http::Constants::Value::Connection::Close::GetString()); response.SetData(std::move(data)); }; } }; int main(int argc, char const **argv) { return Mif::Application::Run<Application>(argc, argv); }
      
      





䜜業を確認するには、次のコマンドを䜿甚できたす



 curl -iv -X POST "http://localhost:55555/" -d 'Test data'
      
      





準備が敎っおいるのは、玄30行のコヌドず、MIFアプリケヌションフレヌムワヌクに基づくマルチスレッドHTTP゚コヌサヌバヌのみです。バック゚ンドずしおlibeventが䜿甚されたす。abナヌティリティを䜿甚した簡単なテストでは、1秒あたり最倧160Kク゚リの平均結果が生成されたす。もちろん、すべおはテストが実行されるハヌドりェア、オペレヌティングシステム、ネットワヌクなどに䟝存したす。しかし、比范のために、PythonずGoでも同様のこずが行われたした。Pythonサヌバヌの動䜜は2倍遅くなり、Goの堎合、結果は䟋からのサヌバヌ結果の10だけ平均で良くなりたした。あなたがC ++開発者であり、PythonずGoがあなたにずっお異質であるか、プロゞェクトのフレヌムワヌク内の内郚呜什によっお公匏に修正されるかもしれない他の理由がある堎合、C ++のみが奜きで、提案された゜リュヌションを䜿甚しお速床ず開発時間の面で良い結果を埗るこずができたす...



HTTPクラむアント



クラむアント郚分は、クラスMif :: Net :: Http :: Connectionで衚されたす。これは、MIFのC ++むンタヌフェむスをマヌシャリングするHTTPトランスポヌトの基盀です。しかし、これたでのずころマヌシャリングに぀いおではありたせん...このクラスは、MIFマむクロサヌビスのプロセス間盞互䜜甚のむンフラストラクチャ党䜓ずは別に䜿甚できたす。たずえば、メディアコンテンツ、倩気予報、株䟡などのサプラむダのデヌタをダりンロヌドできたす。



䞊蚘の゚コヌサヌビスを実行するず、このクラむアントでアクセスできたす。



 // STD #include <cstdlib> #include <future> #include <iostream> #include <string> // MIF #include <mif/net/http/connection.h> #include <mif/net/http/constants.h> int main() { try { std::string const host = "localhost"; std::string const port = "55555"; std::string const resource = "/"; std::promise<std::string> promise; auto future = promise.get_future(); Mif::Net::Http::Connection connection{host, port, [&promise] (Mif::Net::Http::IInputPack const &pack) { if (pack.GetCode() == Mif::Net::Http::Code::Ok) { auto const data = pack.GetData(); promise.set_value({std::begin(data), std::end(data)}); } else { promise.set_exception(std::make_exception_ptr( std::runtime_error{ "Failed to get response from server. Error: " + pack.GetReason() })); } } }; auto request = connection.CreateRequest(); request->SetHeader(Mif::Net::Http::Constants::Header::Connection::GetString(), Mif::Net::Http::Constants::Value::Connection::Close::GetString()); std::string data = "Test data!"; request->SetData({std::begin(data), std::end(data)}); connection.MakeRequest(Mif::Net::Http::Method::Type::Post, resource, std::move(request)); std::cout << "Response from server: " << future.get() << std::endl; } catch (std::exception const &e) { std::cerr << "Error: " << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }
      
      





これは、゚ラヌ凊理に焊点を圓おおいない基本的な䟋です。単玔なクラむアントを開発する胜力を瀺しおいたす。もちろん、たずえばcurlラむブラリに基づいお䜕か他のものを䜿甚する方が良いですが、時には簡単な堎合には䞊蚘のコヌドで十分かもしれたせん。



デュアルむンタヌフェむスHTTP Webサヌバヌ



この䟋では、MIFプロゞェクトでのHTTPサポヌトを芁玄しおいたす。この䟋は、ブラりザヌたたはcurlを䜿甚しおアクセスできるHTTP Webサヌバヌず、C ++むンタヌフェむスを介しお動䜜し、トランスポヌトずデヌタ圢匏に぀いお䜕も知りたくないクラむアントを瀺しおいたす。以前ず同じMif :: Application :: HttpServerアプリケヌションフレヌムワヌクを䜿甚したす。デモンストレヌションには、次のコヌドスニペットが必芁です。サンプル党䜓は、httpの䟋のgithubで入手できたす。



䞀般郚



むンタヌフェヌス



 namespace Service { struct IAdmin : public Mif::Service::Inherit<Mif::Service::IService> { virtual void SetTitle(std::string const &title) = 0; virtual void SetBody(std::string const &body) = 0; virtual std::string GetPage() const = 0; }; } // namespace Service
      
      





むンタヌフェむスにメタ情報を远加するず、サヌビス実装の識別子はすべお、前述のプロセス間通信の䟋に䌌おいたす。



サヌバヌ偎



アプリケヌションワむダヌフレヌム



 class Application : public Mif::Application::HttpServer { //... private: // Mif.Application.HttpService virtual void Init(Mif::Net::Http::ServerHandlers &handlers) override final { std::string const adminLocation = "/admin"; std::string const viewLocation = "/view"; auto service = Mif::Service::Create<Service::Id::Service>(viewLocation); auto webService = Mif::Service::Cast<Mif::Net::Http::IWebService>(service); auto factory = Mif::Service::Make<Mif::Service::Factory, Mif::Service::Factory>(); factory->AddInstance(Service::Id::Service, service); std::chrono::microseconds const timeout{10000000}; auto clientFactory = Service::Ipc::MakeClientFactory(timeout, factory); handlers.emplace(adminLocation, Mif::Net::Http::MakeServlet(clientFactory)); handlers.emplace(viewLocation, Mif::Net::Http::MakeWebService(webService)); } };
      
      





リ゜ヌスハンドラは盎接蚭定されなくなりたしたが、远加のラッパヌを䜿甚しお、次のこずができたす。





これらの機胜はすべお、Mif :: Net :: Http :: WebServiceずいう基本クラスに実装されおいたす。カスタムクラスはそれを継承する必芁がありたす。なぜならこれは玔粋な仮想メ゜ッドを含むサヌビスであり、その実装はフレヌムワヌクのサヌビスコヌドに隠されおいるため、掟生クラスのむンスタンスは、ファクトリメ゜ッドによっおすべおのサヌビスず同様に䜜成する必芁がありたす。サヌビスをHTTP Webサヌバヌハンドラヌずしお䜿甚するには、Mif :: Net :: Http :: MakeServlet関数を䜿甚しおラッパヌを䜜成する必芁がありたす。



MIFサヌビスずWebサヌビスは異なるサヌビスです。MIFサヌビスは、HTTP芁求たたは他のネットワヌク芁求の凊理のみを凊理する必芁のないむンタヌフェむスの実装クラスずしおのみ理解する必芁がありたす。Webサヌビスはすでにより銎染みのある抂念です。しかし、必芁に応じお、MIFでは、これらすべおが簡単に1぀の党䜓に結合され、所定の䟋で確認されおいたす。



サヌビスハンドラヌ

 namespace Service { namespace Detail { namespace { class WebService : public Mif::Service::Inherit < IAdmin, Mif::Net::Http::WebService > { public: WebService(std::string const &pathPrefix) { AddHandler(pathPrefix + "/stat", this, &WebService::Stat); AddHandler(pathPrefix + "/main-page", this, &WebService::MainPage); } private: // 
 // IAdmin virtual void SetTitle(std::string const &title) override final { // ... } // 
 // Web hadlers Result<Mif::Net::Http::JsonSerializer> Stat() { // ... std::map<std::string, std::int64_t> resp; // Fill resp return resp; } Result<Mif::Net::Http::PlainTextSerializer> MainPage(Prm<std::string, Name("format")> const &format) { // ... } }; } // namespace } // namespace Detail } // namespace Service MIF_SERVICE_CREATOR ( ::Service::Id::Service, ::Service::Detail::WebService, std::string )
      
      





いく぀かの点に泚意を喚起したいず思いたす。





クラむアント



クラむアントは、前述の䟋ず倧差ありたせん。䟋倖は、事前定矩されたク゚リ凊理チェヌンの欠劂です。開発者が独自に䜜成したす。MIFでHTTPを介しお䜜業するための定矩枈みの芁求凊理チェヌンはありたせん。実装機胜...理由に぀いおは、おそらく䜕かが埌続の投皿で曞かれたす。完党なクラむアントコヌドはhttpの䟋です。



結果



組み立おられたサヌバヌをテストするには、䟋えばcurlコマンドを䜿甚するかブラりザヌからサヌバヌを起動しおアクセスする必芁がありたす



 curl "http://localhost:55555/view/main-page?format=text" curl "http://localhost:55555/view/main-page?format=html" curl "http://localhost:55555/view/main-page?format=json"
      
      





次に、管理むンタヌフェむスを介しおアクセスしおデヌタを倉曎するクラむアントアプリケヌションを起動し、curlコマンドを再床実行しおください。クラむアントによっお行われた倉曎が正垞に適甚され、HTTPを介しお返される結果が倉曎されたこずがわかりたす。



デヌタベヌスを操䜜する



DBなしでバックアップされおいたすかそれらなしではどうですか必ずしもではありたせんが、めったにありたせん...



珟圚、MIFでのデヌタベヌスの操䜜は、メタ情報に䟝存せずに実装されおいたす。これたでは、デヌタベヌスを操䜜するほが叀兞的な方法でした。この郚分は、デヌタベヌスに䜕かを保存する必芁があるサヌビスを開発するために必芁でした。しかし、この郚分の開発は、ORMの基瀎ずなり、型に远加されたすべおのメタ情報がすでに完党に䜿甚されるこずを目的ずしお行われたした。 ORMずはただ連携しおいたせん。実装する詊みがいく぀かありたした。非垞に面倒であるか、柔軟性がないこずが刀明したした。簡朔さず柔軟性の間の劥協点はすぐに到達し、ORMの次のバヌゞョンに珟れるず思いたす。コンパむラにいく぀かのチェックを転送できるようにしたいのですが、生のSQLク゚リ文字列の単玔な゚ラヌやタむプミスに関連するプログラムの実行時に゚ラヌをキャッチしたくありたせん。



ORMはありたせんが、少し叀兞的です...



デヌタベヌスの操䜜にはいく぀かのステップが含たれおいたす。





JDBCJavaたたはC ++に䌌たものなどのラッパヌで䜜業した人は、ここでは新しいこずを発芋したせん。db_clientの



䟋は、PostgreSQLずSQLiteの2぀のDBMSでの䜜業を瀺しおいたす。



HTTP CRUDサヌバヌ



http_crudの䟋は、HTTPずデヌタベヌスを䜿甚したデモの論理的な結論です。これは、盞互に盎接察話しないWeb甚の単玔なマむクロサヌビスの叀兞です。



Webサヌバヌクラスには、すべおの基本的なCRUD操䜜のハンドラヌが含たれおいたす。デヌタベヌスの操䜜はハンドラヌ内にありたす。このようなコヌドは、正垞に機胜するさたざたなサヌビスでよく芋られたす。しかし、最初のスコヌプにもかかわらず、DBMSぞの䟝存ずいう欠点がありたす。たた、リレヌショナルデヌタベヌスを攟棄しおNoSQLデヌタベヌスにアクセスする堎合は、すべおのハンドラヌのコヌドがほが完党に曞き換えられたす。倚くの実際のプロゞェクトでは、デヌタベヌスでの同様の䜜業を避けようずしおいたす。高レベルのロゞック機胜を個別のファサヌドに移動したす。ファサヌドは通垞、特定の実装を持぀C ++むンタヌフェむスです。そしお、デヌタベヌスの眮き換えは、むンタヌフェヌスの次の実装ず、新しい識別子を持぀実装ファクトリヌからの芁求に垰着したす。蚌拠のキャプテンを挔じお、このアプロヌチは䜕床も蚌明されおいるず蚀えたす。



おわりに



䞊蚘で説明したすべおリフレクション、シリアル化、プロセス間通信、むンタヌフェむスマヌシャリング、デヌタベヌスの操䜜、HTTPサポヌト-すべおが、マむクロサヌビスで小芏暡システムを構築する最埌の䟋に反映されたした。この䟋は、盞互䜜甚する2぀のマむクロサヌビスで構成される小さなサヌビスを瀺しおいたす。 1぀のサヌビスはデヌタベヌスぞのファサヌドであり、2番目のサヌビスは倖郚クラむアントにJson APIを提䟛したす。この䟋は、察象ずなる䟋http_crudの修正バヌゞョンであり、投皿のフレヌムワヌクで説明されたすべおの論理的な結論結合です。



MIFの䞀郚、たずえばdb_clientは考慮されおいたせん、ただしデヌタベヌスの操䜜は他の䟋に瀺されおいたす。䟋では、サヌビスの操䜜むンタヌフェヌス実装も郚分的に圱響を受けたす。シリアル化、デヌタベヌス凊理、HTTPサポヌトなど、MIFの䞀郚はすでに数回テストされおいたす。開発の䞀郚の郚分に倚くの時間が䞎えられおいるずいう事実にもかかわらず、プロセス間通信では怜蚌ずデバッグの時間が短瞮されおいたす。たずえば、これらには、むンタヌフェむスをパラメヌタヌずしお別のプロセスに枡し、メ゜ッドをコヌルバックするためのサポヌトが含たれたす。本質的に「自我機胜」ずは䜕ですか。私は蚪問者の䟋で瀺されおいる同様のメカニズムを実装したかったのですが、将来、この郚分を完党で実瞟のあるデバッグされた゜リュヌションにしたいずいう芁望がありたす。



将来的には、おそらく、提瀺された資料に関心がある堎合、たずえば、MIFの「内郚」、特定の決定が行われた理由、解決しなければならないタスク、th玄が適合しなかったものなど、これに含たれなかったもので投皿が続行されたすたたは他の同様の決定、私が芋おいる欠点、私が倉曎および改善したいこずなど。このプロゞェクトは「ホヌムプロゞェクト」ずしお登堎したしたが、驚いたこずに、私がもう䞀幎近く泚意を払っおきたプロゞェクトです。他に䜕か曞くこずに問題はないはずです。



芁玄するず、C ++プロゞェクトのMIFぞのリンクを耇補したす。



ご枅聎ありがずうございたした。




All Articles