SObjectizer-5.6.0: potong hidup untuk tumbuh lebih lanjut


Pada hari ketiga, versi baru SObjectizer tersedia : 5.6.0 . Fitur utamanya adalah penolakan kompatibilitas dengan cabang stabil sebelumnya 5,5, yang telah berkembang terus selama empat setengah tahun.


Prinsip dasar pengoperasian SObjectizer-5 tetap sama. Komunikasi, agen, kerja sama dan operator masih bersama kami. Tetapi sesuatu berubah dengan serius, sesuatu yang umumnya dibuang. Karena itu, hanya mengambil SO-5.6.0 dan mengkompilasi ulang kode Anda akan gagal. Sesuatu perlu ditulis ulang. Sesuatu mungkin harus didesain ulang.


Mengapa kami menjaga kompatibilitas selama beberapa tahun, dan kemudian memutuskan untuk mengambil dan menghancurkan semuanya? Dan apa yang paling parah?


Saya akan mencoba membicarakan ini di artikel ini.


Mengapa Anda perlu memecahkan sesuatu?


Sederhana saja.


SObjectizer-5.5 selama pengembangannya telah menyerap begitu banyak hal yang berbeda dan beragam yang pada awalnya tidak direncanakan, sehingga sebagai hasilnya, ia telah membentuk terlalu banyak kruk dan alat peraga di dalamnya. Dengan setiap versi baru, menambahkan sesuatu yang baru ke SO-5.5 menjadi semakin sulit. Dan akhirnya, untuk pertanyaan "Mengapa kita membutuhkan semua ini?" tidak ada jawaban yang cocok ditemukan.


Jadi alasan pertama adalah komplikasi ulang jeroan ayam itik dari SObjectizer.


Alasan kedua adalah bahwa kita bodoh bosan berfokus pada kompiler C ++ lama. Cabang 5.5 dimulai pada 2014, ketika kami memiliki, jika saya tidak salah, gcc-4.8 dan MSVS2013. Dan pada level ini, kami masih terus mempertahankan standar persyaratan untuk level dukungan untuk standar C ++.


Awalnya, kami memiliki "kepentingan egois" dalam hal ini. Selain itu, untuk beberapa waktu kami menganggap persyaratan rendah untuk kualitas dukungan untuk standar C ++ sebagai "keunggulan kompetitif" kami.


Tetapi waktu terus berjalan, "minat egois" sudah berakhir. Beberapa manfaat dari "keunggulan kompetitif" seperti itu tidak terlihat. Mungkin mereka akan, jika kita bekerja dengan C ++ 98 sama sekali, maka kita akan tertarik pada perusahaan berdarah. Tetapi perusahaan berdarah pada orang-orang seperti kita, pada prinsipnya, tidak tertarik. Karena itu, diputuskan untuk berhenti membatasi diri dan mengambil sesuatu yang lebih segar. Jadi kami mengambil yang terbaru dari stabil saat ini: C ++ 17.


Tentunya, tidak semua orang akan menyukai solusi ini, untuk banyak C ++ 17, ini sekarang merupakan keunggulan terdepan yang tidak dapat dicapai, yang masih sangat, sangat jauh.


Namun demikian, kami memutuskan risiko seperti itu. Semua sama, proses mempopulerkan SObjectizer tidak berjalan cepat, jadi ketika SObjectizer menjadi lebih atau kurang banyak diminati, C ++ 17 tidak akan lagi menjadi "ujung tombak". Sebaliknya, itu akan diperlakukan sama seperti sekarang di C ++ 11.


Secara umum, alih-alih terus membangun kruk menggunakan subset C ++ 11, kami memutuskan untuk serius membuat ulang internal SObjectizer menggunakan C ++ 17. Untuk membangun basis di mana SObjectizer dapat semakin berkembang selama empat atau lima tahun ke depan.


Apa yang berubah serius di SObjectizer-5.6?


Sekarang, mari kita secara singkat membahas beberapa perubahan yang paling mencolok.


Kerjasama agen tidak lagi memiliki nama string


Masalah


Sejak awal, SObjectizer-5 menuntut agar setiap kerjasama memiliki nama string yang unik. Fitur ini diwarisi dari SObjectizer kelima dari sebelumnya, SObjectizer keempat.


Karenanya, SObjectizer perlu menyimpan nama-nama koperasi terdaftar. Periksa keunikan mereka saat mendaftar. Cari kerja sama dengan nama selama deregistrasi, dll., Dll.


Sejak versi pertama, skema sederhana telah digunakan dalam SObjectzer-5: kamus tunggal dari kerjasama terdaftar yang dilindungi oleh mutex. Saat mendaftarkan kerja sama, mutex ditangkap, keunikan nama kerja sama, kehadiran orang tua, dll. Diperiksa. Setelah memeriksa, kamus diubah, setelah itu mutex dirilis. Ini berarti bahwa jika pada saat yang sama pendaftaran / deregistrasi beberapa koperasi sekaligus dimulai, maka di beberapa titik mereka akan berhenti dan menunggu sampai salah satu operasi selesai bekerja dengan kamus koperasi. Karena itu, operasi koperasi tidak skala dengan baik.


Itulah yang ingin saya singkirkan untuk memperbaiki situasi dengan kecepatan pendaftaran kerjasama.


Solusi


Dua cara utama untuk memecahkan masalah ini dipertimbangkan.


Pertama, menyimpan nama string, tetapi mengubah cara kamus disimpan sehingga operasi pendaftaran kerjasama dapat ditingkatkan. Misalnya, kamus sharding, mis. memecahnya menjadi beberapa bagian, yang masing-masing akan dilindungi oleh mutex-nya.


Kedua, penolakan lengkap atas nama string dan penggunaan beberapa pengidentifikasi yang ditugaskan oleh SObjectizer.


Akibatnya, kami memilih metode kedua dan benar-benar meninggalkan penamaan koperasi. Sekarang di SObjectizer ada yang namanya coop_handle , mis. pegangan yang isinya disembunyikan dari pengguna, tetapi yang dapat dibandingkan dengan std::weak_ptr .


SObjectizer mengembalikan coop_handle saat mendaftarkan kolaborasi:


 auto coop = env.make_coop(); ... //    . auto coop_id = env.register_coop(std::move(coop)); // . //   coop_id    . 

Pegangan ini harus digunakan untuk deregistrasi kerja sama:


 auto coop = env.make_coop(); ... //    . auto coop_id = env.register_coop(std::move(coop)); // . //   coop_id    . ... // - . // ,     . //       . env.deregister_coop(coop_id, ...); 

Juga, pegangan ini harus digunakan ketika membangun hubungan orangtua-anak:


 //   . auto parent = env.make_coop(); ... //  parent . auto parent_id = env.register_coop(std::move(parent)); //  . ... //      ,    . auto child = env.make_coop(parent_id); ... 

Struktur repositori untuk kerjasama dalam Lingkungan SObjectizer juga telah berubah secara dramatis. Jika sebelum versi 5.5 termasuk itu adalah satu kamus umum, sekarang setiap kerjasama adalah repositori tautan ke kerjasama anak. Yaitu koperasi membentuk pohon dengan akar di koperasi akar khusus yang disembunyikan dari pengguna.


Struktur seperti itu memungkinkan untuk skala deregister_coop register_coop dan deregister_coop jauh lebih baik: pemblokiran mutual operasi paralel terjadi hanya jika keduanya milik kerjasama induk yang sama. Untuk kejelasan, berikut ini adalah hasil dari meluncurkan patokan khusus yang mengukur kinerja operasi dengan kerja sama pada laptop lama saya dengan Ubuntu 16.04 dan GCC-7.3:


 _test.bench.so_5.parallel_parent_child -r 4 -l 7 -s 5 Configuration: roots: 4, levels: 7, level-size: 5 parallel_parent_child: 15.69s 488280 488280 488280 488280 Total: 1953120 

Yaitu versi 5.6.0 diatasi dengan hampir 2 juta kerjasama dalam ~ 15,5 detik.


Dan di sini adalah versi 5.5.24.4, yang terakhir dari cabang 5.5 saat ini:


 _test.bench.so_5.parallel_parent_child -r 4 -l 7 -s 5 Configuration: roots: 4, levels: 7, level-size: 5 parallel_parent_child: 46.856s 488280 488280 488280 488280 Total: 1953120 

Skenario yang sama, tetapi hasilnya tiga kali lebih buruk.


Hanya ada satu jenis dispatcher yang tersisa


Dispatcher adalah salah satu pilar SObjectizer. Operator yang menentukan di mana dan bagaimana agen akan memproses pesan mereka. Jadi, tanpa ide dispatcher, mungkin tidak akan ada SObjectizer.


Namun, operator itu sendiri berevolusi, berevolusi dan berevolusi ke titik di mana bahkan tidak sulit bagi kami untuk membuat operator baru untuk SObjectizer-5.5. Tapi sangat merepotkan. Namun, mari kita mulai.


Pada awalnya, semua dispatcher yang diperlukan aplikasi hanya dapat dibuat pada awal SObjectizer:


 so_5::launch( []( so_5::environment_t & env ) { /* -   */ }, //    SObjectizer-. []( so_5::environment_params_t & params ) { p.add_named_dispatcher("active_obj", so_5::disp::active_obj::create_disp()); p.add_named_dispatcher("shutdowner", so_5::disp::active_obj::create_disp()); p.add_named_dispatcher("groups", so_5::disp::active_group::create_disp()); ... } ); 

Saya tidak membuat operator yang diperlukan sebelum memulai - semuanya, ini salah saya, Anda tidak dapat mengubah apa pun.


Jelas bahwa ini merepotkan dan karena skenario penggunaan untuk SObjectizer diperluas, maka perlu untuk menyelesaikan masalah ini. Oleh karena itu, metode add_dispatcher_if_not_exists , yang memeriksa keberadaan dispatcher dan, jika tidak ada, diizinkan untuk membuat instance baru:


 so_5::launch( []( so_5::environment_t & env ) { ... // - . //     . env.add_dispatcher_if_not_exists( "extra_dispatcher", []{ return so_5::disp::active_obj::create_disp(); } ); }, //    SObjectizer-. []( so_5::environment_params_t & params ) {...} ); 

Dispatcher seperti itu disebut publik. Dispatcher publik memiliki nama unik. Dan menggunakan nama-nama ini, agen terikat ke operator:


 so_5::launch( []( so_5::environment_t & env ) { ... // - . //     . env.add_dispatcher_if_not_exists( "extra_dispatcher", []{ return so_5::disp::active_obj::create_disp(); } ); //         //    . auto coop = env.create_coop( "ping_pong", //     extra_dispatcher. so_5::disp::active_obj::create_disp_binder( "extra_dispatcher" ) ); coop->make_agent< a_pinger_t >(...); coop->make_agent< a_ponger_t >(...); ... }, //    SObjectizer-. []( so_5::environment_params_t & params ) {...} ); 

Tetapi operator publik memiliki satu fitur yang tidak menyenangkan. Mereka mulai bekerja segera setelah ditambahkan ke Lingkungan SObjectizer dan terus bekerja sampai Lingkungan SObjectizer menyelesaikan pekerjaannya.


Lagi-lagi, seiring waktu, itu mulai mengganggu. Itu perlu untuk memastikan bahwa dispatcher dapat ditambahkan sesuai kebutuhan dan dispatcher yang menjadi tidak perlu secara otomatis dihapus.


Jadi ada dispatcher "pribadi". Dispatcher ini tidak memiliki nama dan hidup selama ada referensi kepada mereka. Dispatcher pribadi dapat dibuat kapan saja setelah memulai Lingkungan SObjectizer, mereka dihancurkan secara otomatis.


Secara umum, dispatcher pribadi ternyata menjadi tautan yang sangat sukses dalam evolusi dispatcher, tetapi bekerja dengan mereka sangat berbeda dengan bekerja dengan dispatcher publik:


 so_5::launch( []( so_5::environment_t & env ) { ... // - . //     . auto disp = so_5::disp::active_obj::create_private_disp(env); //         //    . auto coop = env.create_coop( "ping_pong", //      . disp->binder() ); coop->make_agent< a_pinger_t >(...); coop->make_agent< a_ponger_t >(...); ... }, //    SObjectizer-. []( so_5::environment_params_t & params ) {...} ); 

Bahkan lebih banyak operator swasta dan publik berbeda dalam implementasi. Oleh karena itu, agar tidak menduplikasi kode dan menulis secara terpisah dispatcher publik dan pribadi dari jenis yang sama, saya harus menggunakan konstruksi yang agak rumit dengan templat dan warisan.


Akibatnya, saya bosan menemani semua varietas ini, dan di SObjectizer-5.6 hanya ada satu jenis dispatcher yang tersisa. Bahkan, ini adalah analog dari operator swasta. Tetapi hanya tanpa menyebutkan kata "pribadi" secara eksplisit. Jadi sekarang fragmen yang ditunjukkan di atas akan ditulis sebagai:


 so_5::launch( []( so_5::environment_t & env ) { ... // - . //     . auto disp = so_5::disp::active_obj::make_dispatcher(env); //         //    . auto coop = env.create_coop( "ping_pong", //      . disp.binder() ); coop->make_agent< a_pinger_t >(...); coop->make_agent< a_ponger_t >(...); ... }, //    SObjectizer-. []( so_5::environment_params_t & params ) {...} ); 

Hanya ada fungsi bebas kirim, send_delayed dan send_ periodic tersisa


Pengembangan API untuk mengirim pesan ke SObjectizer mungkin merupakan contoh paling mencolok tentang bagaimana SObjectizer telah berubah karena dukungan untuk C ++ 11 telah meningkat dalam kompiler yang tersedia untuk kami.


Pertama, pesan dikirim seperti ini:


 mbox->deliver_message(new my_message(...)); 

Atau, jika Anda mengikuti "rekomendasi dari peternak anjing terbaik" (c):


 std::unique_ptr<my_message> msg(new my_message(...)); mbox->deliver_message(std::move(msg)); 

Namun, kemudian kami tiba di kompiler pembuangan kami dengan dukungan untuk templat variadic dan fungsi pengiriman yang muncul. Menjadi mungkin untuk menulis seperti ini:


 send<my_message>(target, ...); 

Benar, butuh lebih banyak waktu bagi seluruh keluarga untuk send_to_agent dari send sederhana, termasuk send_to_agent , send_delayed_to_agent , dll. Dan kemudian, untuk membuat keluarga ini lebih sempit ke set send send_delayed , send_delayed dan send_periodic .


Namun, terlepas dari kenyataan bahwa keluarga fungsi pengiriman sudah terbentuk sejak lama dan telah direkomendasikan untuk mengirim pesan selama beberapa tahun, metode lama seperti deliver_message , schedule_timer , dan single_timer masih tersedia bagi pengguna.


Tetapi dalam versi 5.6.0, hanya fungsi send , send_delayed , dan send_periodic yang disimpan dalam API SObjectizer publik. Semua yang lain dihapus sekaligus atau dipindahkan ke ruang nama SObjectizer internal.


Jadi dalam SObjectizer-5.6, antarmuka untuk mengirim pesan akhirnya menjadi seperti semula jika kita memiliki kompiler dengan dukungan C ++ 11 yang normal sejak awal. Nah, ditambah dengan itu, jika kita memiliki pengalaman menggunakan C ++ 11 yang sangat normal ini.


Format tunggal send_delayed dan send_ periodic


Dengan fungsi send_delayed dan send_periodic dalam versi SObjectizer sebelumnya, ada insiden lain.


Untuk menggunakan timer, Anda harus memiliki akses ke Lingkungan SObjectizer. Di dalam agen ada tautan ke Lingkungan SObjectizer. Dan di dalam mchain ada tautan seperti itu. Tetapi di dalam kotak itu dia tidak ada di sana. Oleh karena itu, jika pesan yang tertunda dikirim ke agen atau ke mchain, maka panggilan send_delayed seperti:


 send_delayed<my_message>(target_agent, pause, ...); send_delayed<my_message>(target_mchain, pause, ...); 

Untuk mbox, kami harus mengambil tautan ke Lingkungan SObjectizer dari tempat lain:


 send_delayed<my_message>(this->so_environment(), target_mbox, pause, ...); 

Fitur send_delayed dan send_periodic ini adalah pecahan kecil. Yang tidak terlalu mengganggu, tapi cukup mengganggu. Dan semua karena pada awalnya kami tidak mulai menyimpan tautan ke Lingkungan SObjectizer di mbox-ahs.


Pelanggaran kompatibilitas dengan versi sebelumnya adalah alasan yang baik untuk menyingkirkan sempalan ini.


Sekarang Anda dapat mengetahui dari mbox untuk apa SObjectizer Environment diciptakan. Dan ini memungkinkan untuk menggunakan send_periodic dan send_periodic send_delayed tunggal untuk semua jenis penerima pesan penghitung waktu:


 send_delayed<my_message>(target_agent, pause, ...); send_delayed<my_message>(target_mchain, pause, ...); send_delayed<my_message>(target_mbox, pause, ...); 

Dalam arti harfiah, "agak sepele, tapi bagus."


Tidak ada lagi agen ad-hoc


Seperti kata pepatah, "Setiap kecelakaan memiliki nama depan, nama tengah dan nama belakang." Dalam hal agen ad-hoc, ini adalah nama depan, nama tengah dan nama belakang saya :(


Intinya adalah ini. Ketika kami mulai berbicara tentang SObjectizer-5 di depan umum, kami mendengar banyak celaan untuk verbositas kode untuk contoh-contoh SObjectizer. Dan secara pribadi, verbositas ini bagi saya merupakan masalah serius yang harus saya tangani dengan serius.


Salah satu sumber verbositas adalah kebutuhan agen untuk mewarisi dari agent_t type base khusus. Dan dari sini, tampaknya, tidak ada jalan keluar. Atau tidak?


Jadi ada agen ad-hoc, yaitu agen, untuk penentuan yang tidak perlu untuk menulis kelas yang terpisah, itu hanya cukup untuk mengatur reaksi terhadap pesan dalam bentuk fungsi lambda. Misalnya, contoh ping-pong klasik pada agen ad-hoc dapat ditulis seperti ini:


 auto pinger = coop->define_agent(); auto ponger = coop->define_agent(); pinger .on_start( [ponger]{ so_5::send< msg_ping >( ponger ); } ) .event< msg_pong >( pinger, [ponger]{ so_5::send< msg_ping >( ponger ); } ); ponger .event< msg_ping >( ponger, [pinger]{ so_5::send< msg_pong >( pinger ); } ); 

Yaitu tidak ada kelas mereka sendiri. Kami hanya memanggil define_agent() pada kerjasama dan mendapatkan beberapa jenis objek agen, yang Anda dapat berlangganan pesan yang masuk.


Jadi di SObjectizer-5 ada pemisahan menjadi agen reguler dan ad-hoc.


Yang tidak membawa bonus yang terlihat, hanya biaya tenaga kerja tambahan untuk menyertai pemisahan seperti itu. Dan seiring waktu, menjadi jelas bahwa agen ad-hoc seperti koper tanpa pegangan: sulit untuk dibawa dan sangat disayangkan untuk pergi. Tetapi ketika bekerja pada SObjectizer-5.6, diputuskan untuk berhenti.


Pada saat yang sama, pelajaran lain juga dipelajari, mungkin bahkan lebih penting: dalam setiap diskusi publik tentang alat di Internet, sejumlah besar orang akan mengambil bagian yang acuh tak acuh terhadap apa alat itu, mengapa diperlukan, mengapa itu seperti yang seharusnya digunakan, dll. Sangat penting bagi mereka untuk mengekspresikan pendapat mereka yang kuat. Di segmen Internet berbahasa Rusia, selain itu, masih sangat penting untuk menyampaikan kepada pengembang alat bagaimana mereka bodoh dan tidak berpendidikan, dan seberapa banyak hasil pekerjaan mereka tidak diperlukan.


Karena itu, Anda harus sangat berhati-hati dengan apa yang diperintahkan. Dan Anda dapat mendengarkan (dan kemudian dengan hati-hati) hanya dengan apa yang dikatakan di sini dalam nada ini: "Saya mencoba melakukan ini pada instrumen Anda dan saya tidak suka berapa banyak kode yang didapat di sini." Bahkan keinginan seperti itu harus diperlakukan dengan sangat hati-hati: "Saya akan mengambil pengembangan Anda jika akan lebih mudah di sini dan di sini."


Sayangnya, keterampilan "menyaring" kata "simpatisan baik" di Internet sekitar lima tahun lalu jauh lebih sedikit daripada sekarang. Oleh karena itu, percobaan khusus seperti agen ad-hoc di SObjectizer.


SObjectizer-5.6 tidak lagi mendukung interaksi agen yang disinkronkan


Topik interaksi yang disinkronkan antara agen sangat tua dan sakit.


Itu dimulai pada zaman SObjectizer-4. Dan di SObjectizer-5 berlanjut. Sejauh ini, akhirnya, yang disebut permintaan layanan . Yang awalnya, memang, menakutkan seperti kematian. Tapi kemudian saya berhasil memberi mereka tampilan yang lebih atau kurang layak .


Tapi ini ternyata menjadi kasus ketika pancake pertama keluar kental :(


Di dalam SObjectizer, saya harus mengimplementasikan pengiriman dan pemrosesan pesan reguler dengan satu cara, dan pengiriman dan pemrosesan permintaan sinkron di lain. Sangat menyedihkan bahwa fitur-fitur ini harus diperhitungkan, termasuk ketika mengimplementasikan mbox-s Anda sendiri.


Dan setelah fungsionalitas pesan amplop ditambahkan ke SObjectizer, menjadi perlu untuk melihat lebih sering dan lebih teliti perbedaan antara pesan biasa dan permintaan sinkron.


Secara umum, dengan permintaan sinkron selama pemeliharaan / pengembangan SObjectizer, ada terlalu banyak sakit kepala. Sedemikian rupa sehingga pada awalnya ada keinginan konkret untuk menyingkirkan permintaan yang sangat sinkron ini . Dan kemudian keinginan ini terwujud.


Maka pada SObjectizer-5.6, agen dapat berinteraksi kembali hanya melalui pesan asinkron.


Dan karena kadang-kadang sesuatu seperti interaksi sinkron masih diperlukan, dukungan untuk jenis interaksi ini telah diserahkan kepada proyek so5extra yang menyertainya :


 //    "-". using my_request_reply = so_5::extra::sync::request_reply_t<my_request, my_reply>; ... //  ,    . class request_handler final : public so_5::agent_t { ... //  .      . void on_request(typename my_request_reply::request_mhood_t cmd) { ... //  . //      cmd->request(). //   . cmd->make_reply(...); //      my_reply. } ... void so_define_agent() override { //       . so_subscribe_self().event(&request_handler::on_request); } }; ... //     . so_5::mbox_t handler_mbox = ...; //        15s. //    ,    . my_reply reply = my_request_reply::ask_value(handler_mbox, 15s, ...); //       my_request. 

Yaitu Sekarang bekerja dengan permintaan sinkron secara fundamental berbeda karena penangan permintaan tidak mengembalikan nilai dari metode penangan, seperti sebelumnya. Sebagai gantinya, metode make_reply .


Implementasi baru baik dalam hal permintaan dan respons dikirim dalam SObjectizer seperti pesan asinkron biasa. Sebenarnya, make_reply adalah implementasi send sedikit lebih spesifik.


Dan, yang penting, implementasi baru memungkinkan kami untuk mendapatkan fungsionalitas yang sebelumnya tidak dapat dicapai:


  • permintaan sinkron (mis. request_reply_t<Request, Reply> objek) sekarang dapat disimpan dan / atau diteruskan ke penangan lain. Apa yang memungkinkan untuk mengimplementasikan berbagai skema penyeimbangan beban;
  • Anda dapat membuat respons terhadap permintaan datang dalam kotak mbox reguler dari agen yang mengajukan permintaan. Dan agen pemula akan memproses respons dengan cara biasa, seperti pesan lainnya;
  • Anda dapat mengirim beberapa permintaan ke penerima yang berbeda sekaligus, dan kemudian mem-parsing tanggapan dari mereka sesuai urutan penerimaannya:

 using first_dialog = so_5::extra::sync::request_reply_t<first_request, first_reply>; using second_dialog = so_5::extra::sync::request_reply_t<second_request, second_reply>; //         . auto reply_ch = create_mchain(env); //     . first_dialog::initiate_with_custom_reply_to( one_service, reply_ch, so_5::extra::sync::do_not_close_reply_chain, ...); second_dialog::initiate_with_custom_reply_to( another_service, reply_ch, so_5::extra::sync::do_not_close_reply_chain, ...); //    . receive(from(reply_ch).handle_n(2).empty_timeout(15s), [](typename first_dialog::reply_mhood_t cmd) {...}, [](typename second_dialog::reply_mhood_t cmd) {...}); 

Jadi, kita dapat mengatakan bahwa dengan interaksi sinkron di SObjectizer terjadi hal berikut:


  • untuk waktu yang lama dia pergi karena alasan ideologis;
  • kemudian ditambahkan dan ternyata interaksi seperti itu bermanfaat;
  • tetapi pengalaman menunjukkan bahwa implementasi pertama tidak terlalu berhasil;
  • implementasi lama benar-benar dibuang, dan implementasi baru diusulkan sebagai imbalan.

Mereka melakukan kesalahan mereka sendiri, secara umum.


Kesimpulan


Artikel ini, secara singkat, berbicara tentang beberapa perubahan pada SObjectizer-5.6.0 dan alasan di balik perubahan ini.


Daftar perubahan yang lebih lengkap dapat ditemukan di sini .


Kesimpulannya, saya ingin menawarkan mereka yang belum mencoba SObjectizer, ambil dan coba. Dan bagikan perasaan Anda kepada kami: apa yang Anda sukai, apa yang tidak Anda sukai, apa yang hilang.


Kami dengan cermat mendengarkan semua komentar / saran yang membangun. Selain itu, dalam beberapa tahun terakhir, hanya yang dibutuhkan seseorang yang termasuk dalam SObjectizer. Jadi jika Anda tidak memberi tahu kami apa yang ingin Anda miliki di SObjectizer, maka ini tidak akan muncul. Dan jika Anda memberi tahu saya, lalu siapa yang tahu ...;)


Proyek ini sekarang hidup dan berkembang di sini . Dan bagi mereka yang terbiasa menggunakan hanya GitHub, ada cermin GitHub . Cermin ini benar-benar baru, sehingga Anda dapat mengabaikan kekurangan bintang.


PS. Anda dapat mengikuti berita terkait SObjectizer di grup Google ini . Di sana Anda dapat mengangkat masalah yang terkait dengan SObjectizer.

Source: https://habr.com/ru/post/id453256/


All Articles