Ketika standar HTTP tidak cukup. Micronaut melakukan

Halo semuanya, nama saya Dmitry, dan hari ini saya akan berbicara tentang bagaimana kebutuhan produksi membuat saya menjadi kontributor untuk kerangka kerja Micronaut. Tentunya banyak yang sudah mendengar tentang dia. Singkatnya, ini adalah alternatif yang ringan untuk Spring Boot, di mana penekanan utamanya bukan pada refleksi, tetapi pada kompilasi awal dari semua dependensi yang diperlukan. Seorang kenalan yang lebih terperinci dapat mulai dengan dokumentasi resmi.

Kerangka kerja Micronaut digunakan dalam beberapa proyek Yandex internal dan telah memantapkan dirinya dengan cukup baik. Jadi, apa yang kita lewatkan? Saya dapat langsung mengatakan: out of the box, kerangka kerja mendukung, pada prinsipnya, semua fitur yang mungkin diperlukan seorang programmer untuk mengembangkan backend. Namun, ada beberapa kasus langka yang tidak didukung di luar kotak. Salah satunya adalah ketika Anda harus bekerja bukan melalui HTTP, tetapi dengan ekstensi HTTP. Misalnya dengan metode tambahan. Faktanya, kasus-kasus seperti itu jauh lebih banyak daripada yang terlihat. Selain itu, beberapa protokol ini adalah standar:

  • Webdav adalah ekstensi untuk mengakses sumber daya. Selain metode standar, HTTP memerlukan dukungan untuk metode tambahan seperti LOCK, PROPPATCH, dll.
  • Caldav adalah ekstensi Webdav untuk bekerja dengan acara jenis kalender. Protokol ini dengan tingkat probabilitas tinggi ada di aplikasi pada ponsel cerdas Anda: untuk menyinkronkan kalender, janji temu, dll.

Dan daftarnya tidak terbatas pada ini. Jika Anda melihat registri metode HTTP , Anda akan melihat bahwa metode HTTP hanya dijelaskan oleh standar RFC saat ini 39. Dan berapa banyak lagi kasus ada protokol yang ditulis sendiri melalui HTTP. Jadi dukungan untuk metode HTTP non-standar cukup umum. Sering juga terjadi bahwa kerangka kerja yang Anda gunakan tidak mendukung metode tersebut. Berikut ini adalah diskusi tentang Stack Overflow untuk ExpressJS . Dan di sini adalah permintaan tarik di github untuk Tornado . Nah, karena Micronaut sering diposisikan sebagai alternatif yang ringan untuk Spring - ini adalah masalah yang sama untuk Spring .

Tidak mengherankan bahwa ketika di salah satu proyek kami membutuhkan dukungan untuk protokol yang memperluas HTTP dalam hal metode, kami menghadapi masalah yang sama untuk Micronaut yang telah kami gunakan untuk proyek ini untuk waktu yang lama. Ternyata mendapatkan Micronaut untuk memproses metode non-standar cukup sulit.

Mengapa Karena jika Anda melihat definisi metode HTTP di Micronaut saat ini, Anda akan menemukan bahwa mereka diatur menggunakan Enum, dan bukan kelas, seperti yang dilakukan, misalnya, di Netty (saya tidak sengaja menyebutkan Netty, nanti akan muncul lebih dari sekali). Untuk membuat keadaan menjadi lebih buruk, semua pencocokan panggilan server dilakukan dengan memfilter dengan enum, bukan dengan nama string metode. Ini berarti bahwa jika Anda memerlukan metode HTTP non-standar, Anda harus menuliskannya dalam Enum, dan ini sebenarnya bukan solusi yang baik untuk masalah tersebut. Pertama, itu akan memerlukan komit ke repositori setiap kali Anda memerlukan metode baru. Kedua, metode HTTP tidak distandarisasi secara default dan daftar mereka tidak diperbaiki di mana saja, sehingga tidak realistis untuk meramalkan semua situasi yang mungkin terjadi. Penting untuk memaksa Micronaut untuk memproses metode yang sebelumnya tidak disediakan oleh pengembang.

Solusi satu: dahi


gambar

Solusi pertama dan paling jelas adalah tidak menyentuh Micronaut sama sekali dan tidak menulis ulang apa pun di dalamnya. Mengapa, karena Anda dapat meletakkan nronx di depan Micronaut, seperti yang kami lakukan, mulai dari contoh :

http { upstream other_PROPPATCH { server ...; } upstream other_REPORT { server ...; } server { location /service { proxy_method POST; proxy_pass http://other_$request_method; } } } 

Apa gunanya Kita dapat memaksa nginx untuk metode non-standar untuk mengakses proxy yang kita butuhkan, sambil menggunakan kemampuan nginx untuk mengubah metode: yaitu, kita akan mengakses melalui metode POST, dan Micronaut dapat menanganinya.

Apa yang buruk Untuk memulainya, kami benar-benar membuat semua permintaan dari sudut pandang Micronaut non-idempoten. Jangan lupa bahwa untuk metode non-standar juga ada pemisahan seperti itu. Misalnya, LAPORAN adalah idempoten, sementara PROPPATCH tidak. Akibatnya, kerangka kerja tidak tahu tentang jenis permintaan, dan programmer yang melihat kode penangan ini juga tidak akan dapat menentukan ini. Namun, ini tidak terjadi. Kami sudah memiliki serangkaian tes yang secara otomatis memeriksa aplikasi untuk kepatuhan dengan protokol yang diinginkan. Agar tes ini bekerja dengan solusi seperti itu dalam suatu proyek, Anda harus memilih salah satu dari dua opsi:

  • Naikkan gambar nginx dengan pengaturan yang diperlukan, selain aplikasi itu sendiri, sehingga tes mengakses nginx, dan bukan Micronaut itu sendiri. Meskipun infrastruktur Yandex tentu memungkinkan Anda untuk meningkatkan komponen tambahan, dalam hal ini sepertinya overengineering murni untuk pengujian.
  • Menulis ulang tes sehingga mereka tidak menguji protokol yang diinginkan, tetapi merujuk ke jalur yang diarahkan nginx. Faktanya, kami menguji bukan protokolnya, tetapi nyali dari implementasi kruk spesifiknya.

Kedua opsi tersebut tidak terlalu indah, sehingga muncul ide: mengapa tidak memperbaiki Micronaut untuk tujuan yang benar, terlebih lagi pasti pengeditan seperti itu akan bermanfaat tidak hanya bagi kita. Yaitu, saya menginginkan sesuatu seperti ini:

 @CustomMethod("PROPFIND") public String process( // Provide here HttpRequest or something else, as standard micronaut methods ) { } 

Dan saya dengan riang mengambil tugas ini, tetapi apa yang terjadi pada akhirnya?

Solusi dua: mari kita menulis ulang semuanya!




Bahkan, ini jauh lebih mudah daripada yang terlihat pada pandangan pertama. Komit hanya mengubah HttpMethod dari enum ke kelas. Selanjutnya, kami membuat metode statis (terutama valueOf) di dalam kelas yang dipanggil untuk enum. Dan IDEA ditambah dengan Gradle memastikan tidak ada yang rusak.

Yang paling sulit di sini adalah dengan DefaultUriRouter, karena diasumsikan bahwa himpunan itu diperbaiki dan membuat array daftar jalur untuk metode yang mungkin. Ini harus ditinggalkan untuk implementasi baru. Namun secara umum, semuanya ternyata cukup sederhana. Perhatikan bahwa Anda harus menambahkan 240 baris dan menghapus 116.

Masalahnya adalah ini adalah perubahan besar. Ya, dalam praktiknya, dalam proyek reguler menggunakan Micronaut, Anda - kemungkinan besar - tidak menggunakan HttpMethod secara langsung dalam kode, dan jika Anda menggunakannya, Anda tidak mungkin menggunakan metode ordinal dan metode enum spesifik lainnya di sana. Namun, ini masih tidak membuat perubahan dalam versi 1.x diizinkan, terutama mengingat fakta bahwa semua ini dimulai untuk mendukung kasus yang agak jarang. Tetapi untuk 2.x ini adalah pengeditan normal, tetapi Anda masih harus menjalankan hingga 2.x. Karena itu, saya harus menulis lebih banyak kode ...

Solusi tiga: bertindak secara evolusioner


gambar

Sebenarnya, Anda dapat melihat permintaan tarikan yang sesuai untuk versi 1.3. Seperti yang Anda lihat, saya harus menulis sekitar lima kali lebih banyak kode daripada untuk perubahan besar, dan ini bukan kebetulan. Di sini saya ingin memuji metode default di antarmuka yang diperkenalkan di Jawa kedelapan. Untuk refactoring yang tidak merusak kompatibilitas ke belakang, hal ini tidak tergantikan, dan saya tidak bisa membayangkan bagaimana saya akan melakukan pengeditan ini untuk Java sampai versi kedelapan (walaupun, anehnya, perubahan besar bisa dilakukan sebelum kedelapan).

Pengeditan dasar didasarkan pada fakta bahwa antarmuka HttpRequest memiliki metode getMethod, yang digunakan untuk memfilter. Dia kembali, seperti yang Anda duga, enum. Oleh karena itu, metode default getHttpMethodName ditambahkan ke antarmuka, yang secara default mengembalikan nama nilai enum. Kemudian mereka menemukan di mana metode asli digunakan dalam pencocokan jalur, dan di sana itu diganti oleh panggilan ke metode baru. Dan kemudian, dalam implementasi antarmuka untuk server Netty, metode antarmuka didefinisikan ulang untuk menggunakan nilai nyata dari metode HTTP.

Itu berisi satu jebakan yang bisa dilihat dalam diskusi , dan itu menyangkut klien deklaratif Micronaut. Mereka menggunakan konversi nama nilai enum ke turunan dari kelas HttpMethod untuk Netty. Jika Anda melihat dokumentasi untuk metode valueOf di kelas ini, Anda akan melihat bahwa nilai yang di-cache akan dikembalikan untuk metode standar, dan untuk metode non-standar, instance baru kelas akan dikembalikan setiap kali. Artinya, jika Anda memiliki beban tinggi dan Anda beralih ke server dengan metode HTTP non-standar jutaan kali, Anda secara bersamaan akan membuat sejuta objek baru. Tentu saja, GC modern harus mengatasi ini, tetapi saya masih tidak ingin membuat objek tambahan begitu saja. Kemudian muncul ide untuk menggunakan ConcurrentHashMap.computeIfAbsent untuk caching, tetapi di sini juga tidak begitu mudah: masalahnya ada di cacat untuk Java 8 , yang akan menyebabkan pemblokiran utas bahkan untuk kasus ketika tidak ada rekaman yang dilakukan. Sebagai hasilnya, kami membuat keputusan sementara:

  • Untuk metode standar, kami menggunakan contoh caching, yang disediakan Netty (pada kenyataannya, seperti sebelumnya).
  • Untuk metode non-standar, biarkan instance baru dibuat. Siapa pun yang memilih metode non-standar harus memastikan bahwa pengumpul sampah dapat mencerna pembuatan objek (kami, misalnya, menggunakan Shenandoah).

Kesimpulan


gambar

Apa yang bisa dikatakan pada akhirnya?

  • Kurva biaya koreksi kesalahan yang terkenal di berbagai tahap pengembangan perangkat lunak dimanifestasikan dengan sangat jelas di sini. Secara khusus, kita berbicara tentang kesalahan perhitungan pada tahap awal pengembangan Micronaut, ketika diputuskan untuk menggunakan enum untuk metode HTTP. Sulit untuk mengatakan bagaimana keputusan ini dibenarkan, mengingat bahwa Micronaut berputar di Netty, di mana kelasnya digunakan untuk hal yang sama. Pada dasarnya, mempertahankan kelas bukannya enum tidak akan sepadan dengan pekerjaan ekstra. Itu sebabnya ternyata lebih mudah untuk membuat perubahan besar dalam rencana ini daripada memperbaikinya dengan dukungan untuk kompatibilitas mundur.
  • Tumit Achilles yang terkenal dari proyek-proyek sumber terbuka (namun, ini juga dapat diamati dalam proyek-proyek industri dengan kode tertutup) - mereka tidak memiliki dokumentasi proyek. Pada saat yang sama, Micronaut sebenarnya memiliki dokumentasi yang sangat bagus: apa saja pilihan untuk penggunaan dan sejenisnya. Namun, di sini kita berbicara tentang mendokumentasikan bagaimana keputusan desain dibuat. Akibatnya, sangat sulit bagi programmer dari luar untuk terlibat dalam pengembangan proyek, bahkan jika sedikit perbaikan diperlukan.
  • Jangan lupa untuk mempertimbangkan fakta bahwa satu atau proyek open source lain digunakan di lingkungan yang banyak dan multi-threaded. Di sini perlu diperhitungkan bahkan untuk perbaikan kecil.

PS


Sementara artikel ini sedang disiapkan untuk publikasi, permintaan tarik diterima ke cabang penyihir Micronaut dan akan dirilis dalam versi 1.3.

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


All Articles