Saya telah mencari cara mengelola versi REST API menggunakan Spring 3.2.x, tetapi saya belum menemukan apa pun yang mudah dipelihara. Saya akan menjelaskan terlebih dahulu masalah yang saya miliki, dan kemudian solusinya ... tetapi saya bertanya-tanya apakah saya menemukan kembali roda di sini.
Saya ingin mengelola versi berdasarkan header Terima, dan misalnya jika permintaan memiliki header Terima application/vnd.company.app-1.1+json
, saya ingin MVC musim semi untuk meneruskan ini ke metode yang menangani versi ini. Dan karena tidak semua metode dalam API berubah dalam rilis yang sama, saya tidak ingin membuka setiap pengontrol saya dan mengubah apa pun untuk penangan yang tidak berubah antar versi. Saya juga tidak ingin memiliki logika untuk mencari tahu versi mana yang akan digunakan dalam pengontrol itu sendiri (menggunakan pelacak layanan) karena Spring sudah menemukan metode mana yang harus dipanggil.
Jadi mengambil API dengan versi 1.0, ke 1.8 di mana penangan diperkenalkan di versi 1.0 dan dimodifikasi di v1.7, saya ingin menangani ini dengan cara berikut. Bayangkan kode tersebut ada di dalam pengontrol, dan ada beberapa kode yang dapat mengekstrak versi dari header. (Berikut ini tidak valid di Spring)
@RequestMapping(...)
@VersionRange(1.0,1.6)
@ResponseBody
public Object method1() {
// so something
return object;
}
@RequestMapping(...) //same Request mapping annotation
@VersionRange(1.7)
@ResponseBody
public Object method2() {
// so something
return object;
}
Ini tidak mungkin dilakukan pada musim semi karena 2 metode memiliki RequestMapping
anotasi yang sama dan Spring gagal dimuat. Idenya adalah bahwa VersionRange
anotasi dapat menentukan rentang versi terbuka atau tertutup. Metode pertama berlaku dari versi 1.0 hingga 1.6, sedangkan yang kedua untuk versi 1.7 dan seterusnya (termasuk versi terbaru 1.8). Saya tahu bahwa pendekatan ini rusak jika seseorang memutuskan untuk mengoper versi 99,99, tetapi itu adalah sesuatu yang tidak masalah bagi saya.
Sekarang, karena hal di atas tidak mungkin dilakukan tanpa pengerjaan ulang yang serius tentang cara kerja pegas, saya berpikir untuk mengutak-atik cara penangan yang cocok dengan permintaan, khususnya untuk menulis sendiri ProducesRequestCondition
, dan memiliki rentang versi di sana. Sebagai contoh
Kode:
@RequestMapping(..., produces = "application/vnd.company.app-[1.0-1.6]+json)
@ResponseBody
public Object method1() {
// so something
return object;
}
@RequestMapping(..., produces = "application/vnd.company.app-[1.7-]+json)
@ResponseBody
public Object method2() {
// so something
return object;
}
Dengan cara ini, saya dapat memiliki rentang versi tertutup atau terbuka yang ditentukan di bagian produksi anotasi. Saya sedang mengerjakan solusi ini sekarang, dengan masalah bahwa saya masih harus mengganti beberapa kelas inti Spring MVC ( RequestMappingInfoHandlerMapping
, RequestMappingHandlerMapping
dan RequestMappingInfo
), yang tidak saya sukai, karena itu berarti kerja ekstra setiap kali saya memutuskan untuk meningkatkan ke versi yang lebih baru dari musim semi.
Saya akan menghargai pemikiran apa pun ... dan terutama, saran apa pun untuk melakukan ini dengan cara yang lebih sederhana dan lebih mudah dipertahankan.
Sunting
Menambahkan hadiah. Untuk mendapatkan bounty, harap jawab pertanyaan di atas tanpa menyarankan untuk memiliki logika ini sendiri di controller. Spring sudah memiliki banyak logika untuk memilih metode pengontrol mana yang akan dipanggil, dan saya ingin mendukungnya.
Edit 2
Saya telah membagikan POC asli (dengan beberapa peningkatan) di github: https://github.com/augusto/restVersioning
sumber
produces={"application/json-1.0", "application/json-1.1"}
dllJawaban:
Terlepas dari apakah pembuatan versi dapat dihindari dengan melakukan perubahan yang kompatibel ke belakang (yang mungkin tidak selalu memungkinkan ketika Anda terikat oleh beberapa pedoman perusahaan atau klien API Anda diimplementasikan dengan cara buggy dan akan mencapai titik impas jika seharusnya tidak) persyaratan yang diabstraksi adalah hal yang menarik satu:
Bagaimana saya bisa melakukan pemetaan permintaan kustom yang melakukan evaluasi arbitrer nilai header dari permintaan tanpa melakukan evaluasi di badan metode?
Seperti yang dijelaskan dalam jawaban SO ini Anda sebenarnya dapat memiliki yang sama
@RequestMapping
dan menggunakan penjelasan yang berbeda untuk membedakan selama perutean sebenarnya yang terjadi selama runtime. Untuk melakukannya, Anda harus:VersionRange
.RequestCondition<VersionRange>
. Karena Anda akan memiliki sesuatu seperti algoritme yang paling cocok, Anda harus memeriksa apakah metode yang dianotasi denganVersionRange
nilai lain memberikan kecocokan yang lebih baik untuk permintaan saat ini.VersionRangeRequestMappingHandlerMapping
berdasarkan pada anotasi dan kondisi permintaan (seperti yang dijelaskan dalam posting Cara mengimplementasikan properti khusus @RequestMapping ).VersionRangeRequestMappingHandlerMapping
sebelum menggunakan defaultRequestMappingHandlerMapping
(misalnya dengan mengatur urutannya ke 0).Ini tidak memerlukan penggantian komponen Spring yang diretas tetapi menggunakan konfigurasi Spring dan mekanisme ekstensi sehingga harus berfungsi meskipun Anda memperbarui versi Spring (selama versi baru mendukung mekanisme ini).
sumber
mvc:annotation-driven
. Mudah-mudahan Spring akan menyediakan versimvc:annotation-driven
di mana seseorang dapat menentukan kondisi khusus.Saya baru saja membuat solusi khusus. Saya menggunakan
@ApiVersion
anotasi yang dikombinasikan dengan@RequestMapping
anotasi di dalam@Controller
kelas.Contoh:
Penerapan:
Anotasi ApiVersion.java :
ApiVersionRequestMappingHandlerMapping.java (ini sebagian besar adalah salin dan tempel dari
RequestMappingHandlerMapping
):Injeksi ke WebMvcConfigurationSupport:
sumber
/v1/aResource
dan/v2/aResource
terlihat seperti sumber daya yang berbeda, tetapi itu hanya representasi yang berbeda dari sumber daya yang sama! 2. Menggunakan header HTTP terlihat lebih baik, tetapi Anda tidak dapat memberikan URL kepada seseorang, karena URL tersebut tidak berisi header. 3. Menggunakan parameter URL, yaitu/aResource?v=2.1
(btw: itulah cara Google membuat versi)....
Saya masih tidak yakin apakah saya akan menggunakan opsi 2 atau 3 , tetapi saya tidak akan pernah menggunakan 1 lagi karena alasan yang disebutkan di atas.RequestMappingHandlerMapping
ke dalamWebMvcConfiguration
, Anda harus menimpacreateRequestMappingHandlerMapping
bukanrequestMappingHandlerMapping
! Jika tidak, Anda akan mengalami masalah aneh (tiba-tiba saya punya masalah dengan Hibernates inisialisasi malas karena sesi tertutup)WebMvcConfigurationSupport
tapi memperpanjangDelegatingWebMvcConfiguration
. Ini berhasil untuk saya (lihat stackoverflow.com/questions/22267191/… )Saya tetap merekomendasikan menggunakan URL untuk pembuatan versi karena di URL @RequestMapping mendukung pola dan parameter jalur, yang formatnya dapat ditentukan dengan regexp.
Dan untuk menangani peningkatan versi klien (yang Anda sebutkan dalam komentar), Anda dapat menggunakan alias seperti 'terbaru'. Atau memiliki versi api tidak berversi yang menggunakan versi terbaru (ya).
Juga menggunakan parameter jalur, Anda dapat menerapkan logika penanganan versi yang kompleks, dan jika Anda sudah ingin memiliki rentang, Anda mungkin menginginkan sesuatu yang lebih cepat.
Berikut ini beberapa contohnya:
Berdasarkan pendekatan terakhir, Anda sebenarnya dapat menerapkan sesuatu seperti yang Anda inginkan.
Misalnya Anda dapat memiliki pengontrol yang hanya berisi metode tusukan dengan penanganan versi.
Dalam penanganan itu Anda melihat (menggunakan pustaka pembuatan refleksi / AOP / kode) di beberapa layanan / komponen pegas atau di kelas yang sama untuk metode dengan nama / tanda tangan yang sama dan memerlukan @VersionRange dan memanggilnya dengan meneruskan semua parameter.
sumber
Saya telah menerapkan solusi yang menangani masalah dengan SEMPURNA dengan versi lainnya.
Berbicara Umum, ada 3 pendekatan utama untuk versi istirahat:
Pendekatan berbasis jalur , di mana klien menentukan versi di URL:
Header Tipe Konten , di mana klien menentukan versi di header Terima :
Header Kustom , di mana klien menentukan versi di header kustom.
The masalah dengan pertama pendekatan adalah bahwa jika Anda mengubah versi katakanlah dari v1 -> v2, mungkin Anda perlu copy-paste sumber v1 yang belum berubah ke jalan v2
The masalah dengan kedua pendekatan adalah bahwa beberapa alat seperti http://swagger.io/ tidak bisa berbeda antara operasi dengan jalan yang sama tetapi berbeda Content-Type (masalah cek https://github.com/OAI/OpenAPI-Specification/issues/ 146 )
Solusinya
Karena saya banyak bekerja dengan alat dokumentasi istirahat, saya lebih suka menggunakan pendekatan pertama. Solusi saya menangani masalah dengan pendekatan pertama, jadi Anda tidak perlu menyalin-tempel titik akhir ke versi baru.
Katakanlah kita memiliki versi v1 dan v2 untuk User controller:
The persyaratan adalah jika saya meminta v1 bagi pengguna sumber daya saya harus mengambil "V1 Pengguna" repsonse, sebaliknya jika saya meminta v2 , v3 dan seterusnya saya harus mengambil "User V2" respon.
Untuk mengimplementasikan ini di musim semi, kita perlu mengganti perilaku RequestMappingHandlerMapping default :
}
Implementasi membaca versi di URL dan meminta dari spring untuk menyelesaikan URL. Jika URL ini tidak ada (misalnya klien meminta v3 ) maka kami mencoba dengan v2 dan seterusnya sampai kami menemukan versi terbaru untuk resource .
Untuk melihat manfaat dari penerapan ini, katakanlah kita memiliki dua sumber daya: Pengguna dan Perusahaan:
Katakanlah kita membuat perubahan dalam "kontrak" perusahaan yang melanggar klien. Jadi kami menerapkan
http://localhost:9001/api/v2/company
dan kami meminta dari klien untuk mengubah ke v2 sebagai gantinya di v1.Jadi permintaan baru dari klien adalah:
dari pada:
Bagian terbaiknya di sini adalah dengan solusi ini klien akan mendapatkan informasi pengguna dari v1 dan informasi perusahaan dari v2 tanpa perlu membuat titik akhir baru (sama) dari pengguna v2!
Dokumentasi Istirahat Seperti yang saya katakan sebelumnya alasan saya memilih pendekatan versi berbasis URL adalah bahwa beberapa alat seperti kesombongan tidak mendokumentasikan secara berbeda titik akhir dengan URL yang sama tetapi jenis konten yang berbeda. Dengan solusi ini, kedua titik akhir ditampilkan karena memiliki URL yang berbeda:
GIT
Implementasi solusi di: https://github.com/mspapant/restVersioningExample/
sumber
The
@RequestMapping
penjelasan mendukungheaders
elemen yang memungkinkan Anda untuk mempersempit permintaan cocok. Secara khusus, Anda dapat menggunakanAccept
tajuk di sini.Ini tidak persis seperti yang Anda gambarkan, karena tidak secara langsung menangani rentang, tetapi elemennya mendukung * karakter pengganti serta! =. Jadi setidaknya Anda bisa lolos dengan menggunakan wildcard untuk kasus di mana semua versi mendukung titik akhir yang dimaksud, atau bahkan semua versi minor dari versi mayor tertentu (misalnya 1. *).
Saya tidak berpikir saya benar-benar menggunakan elemen ini sebelumnya (jika saya punya saya tidak ingat), jadi saya hanya akan mendokumentasikannya di
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html
sumber
application/*
dan bukan bagian dari tipe. Misalnya, berikut ini tidak valid di Spring"Accept=application/vnd.company.app-1.*+json"
. Ini terkait dengan caraMediaType
kerja kelas musim semiBagaimana dengan hanya menggunakan pewarisan ke versi model? Itulah yang saya gunakan dalam proyek saya dan tidak memerlukan konfigurasi pegas khusus dan memberi saya apa yang saya inginkan.
Pengaturan ini memungkinkan duplikasi kecil kode dan kemampuan untuk menimpa metode ke versi baru api dengan sedikit kerja. Ini juga menghemat kebutuhan untuk memperumit kode sumber Anda dengan logika peralihan versi. Jika Anda tidak membuat kode titik akhir dalam suatu versi, itu akan mengambil versi sebelumnya secara default.
Dibandingkan dengan apa yang dilakukan orang lain, ini tampaknya jauh lebih mudah. Apakah ada sesuatu yang saya lewatkan?
sumber
Saya sudah mencoba membuat versi API saya menggunakan URI Versioning , seperti:
Tetapi ada beberapa tantangan ketika mencoba membuat ini berfungsi: bagaimana mengatur kode Anda dengan versi yang berbeda? Bagaimana mengelola dua (atau lebih) versi secara bersamaan? Apa dampaknya saat menghapus beberapa versi?
Alternatif terbaik yang saya temukan bukanlah versi seluruh API, tetapi mengontrol versi pada setiap titik akhir . Pola ini disebut Versioning using Accept header atau Versioning through content negotiation :
Implementasi di Spring
Pertama, Anda membuat Controller dengan atribut produksi dasar, yang akan diterapkan secara default untuk setiap titik akhir di dalam kelas.
Setelah itu, buat skenario yang memungkinkan Anda memiliki dua versi titik akhir untuk membuat pesanan:
Selesai! Cukup panggil setiap titik akhir menggunakan versi Header Http yang diinginkan :
Atau, untuk memanggil versi dua:
Tentang kekhawatiran Anda:
Seperti dijelaskan, strategi ini mempertahankan setiap Pengontrol dan titik akhir dengan versi aslinya. Anda hanya mengubah titik akhir yang memiliki modifikasi dan membutuhkan versi baru.
Dan kesombongan?
Menyiapkan Swagger dengan versi yang berbeda juga sangat mudah menggunakan strategi ini. Lihat jawaban ini untuk lebih jelasnya.
sumber
Dalam menghasilkan Anda bisa mengalami negasi. Jadi untuk metode1 katakan
produces="!...1.7"
dan dalam metode2 memiliki positif.Menghasilkan juga sebuah array sehingga Anda untuk metode1 Anda dapat mengatakan
produces={"...1.6","!...1.7","...1.8"}
dll (menerima semua kecuali 1.7)Tentu saja tidak ideal seperti rentang yang Anda pikirkan tetapi menurut saya lebih mudah untuk dipertahankan daripada barang khusus lainnya jika ini adalah sesuatu yang tidak biasa di sistem Anda. Semoga berhasil!
sumber
Anda dapat menggunakan AOP, di sekitar intersepsi
Pertimbangkan untuk memiliki pemetaan permintaan yang menerima semua
/**/public_api/*
dan dalam metode ini tidak melakukan apa-apa;Setelah
Satu-satunya kendala adalah semua harus berada dalam pengontrol yang sama.
Untuk konfigurasi AOP, lihat http://www.mkyong.com/spring/spring-aop-examples-advice/
sumber