Saya telah membaca tentang strategi pembuatan versi untuk ReST API, dan sesuatu yang tampaknya tidak ditangani oleh mereka adalah bagaimana Anda mengelola basis kode yang mendasarinya.
Katakanlah kita membuat banyak perubahan yang merusak pada API - misalnya, mengubah sumber daya Pelanggan kita sehingga mengembalikan bidang forename
dan surname
bukan satu name
bidang. (Untuk contoh ini, saya akan menggunakan solusi pembuatan versi URL karena mudah untuk memahami konsep yang terlibat, tetapi pertanyaannya juga berlaku untuk negosiasi konten atau header HTTP kustom)
Kami sekarang memiliki titik akhir di http://api.mycompany.com/v1/customers/{id}
, dan titik akhir lain yang tidak kompatibel di http://api.mycompany.com/v2/customers/{id}
. Kami masih merilis perbaikan bug dan pembaruan keamanan untuk v1 API, tetapi pengembangan fitur baru sekarang semuanya berfokus pada v2. Bagaimana kita menulis, menguji dan menerapkan perubahan ke server API kita? Saya dapat melihat setidaknya dua solusi:
Gunakan cabang / tag kontrol sumber untuk basis kode v1. v1 dan v2 dikembangkan, dan diterapkan secara independen, dengan gabungan kontrol revisi yang digunakan seperlunya untuk menerapkan perbaikan bug yang sama ke kedua versi - serupa dengan cara Anda mengelola basis kode untuk aplikasi asli saat mengembangkan versi baru utama sambil tetap mendukung versi sebelumnya.
Buat basis kode itu sendiri mengetahui versi API, sehingga Anda mendapatkan satu basis kode yang menyertakan representasi pelanggan v1 dan representasi pelanggan v2. Perlakukan pembuatan versi sebagai bagian dari arsitektur solusi Anda, bukan masalah penerapan - mungkin menggunakan beberapa kombinasi namespace dan perutean untuk memastikan permintaan ditangani oleh versi yang benar.
Keuntungan nyata dari model cabang adalah bahwa mudah untuk menghapus versi API lama - cukup hentikan penerapan cabang / tag yang sesuai - tetapi jika Anda menjalankan beberapa versi, Anda bisa berakhir dengan struktur cabang dan pipeline penerapan yang sangat berbelit-belit. Model "basis kode terpadu" menghindari masalah ini, tetapi (menurut saya?) Akan jauh lebih sulit untuk menghapus sumber daya dan titik akhir yang tidak digunakan lagi dari basis kode saat tidak lagi diperlukan. Saya tahu ini mungkin subjektif karena tidak mungkin ada jawaban sederhana yang benar, tetapi saya ingin tahu bagaimana organisasi yang mengelola API kompleks di berbagai versi memecahkan masalah ini.
sumber
Jawaban:
Saya telah menggunakan kedua strategi yang Anda sebutkan. Dari keduanya, saya menyukai pendekatan kedua, yang lebih sederhana, dalam kasus penggunaan yang mendukungnya. Artinya, jika kebutuhan pembuatan versi sederhana, gunakan desain perangkat lunak yang lebih sederhana:
Saya tidak merasa terlalu sulit untuk menghapus versi yang tidak digunakan lagi menggunakan model ini:
Pendekatan pertama tentu lebih sederhana dari sudut pandang mengurangi konflik antara versi yang sudah ada bersama, tetapi biaya pemeliharaan sistem terpisah cenderung lebih besar daripada manfaat mengurangi konflik versi. Meskipun demikian, sangat mudah untuk membuat tumpukan API publik baru dan mulai melakukan iterasi pada cabang API terpisah. Tentu saja, hilangnya generasi segera terjadi, dan cabang-cabang berubah menjadi kekacauan penggabungan, resolusi konflik gabungan, dan kesenangan lainnya.
Pendekatan ketiga ada pada lapisan arsitektural: mengadopsi varian dari pola Fasad, dan mengabstraksi API Anda menjadi lapisan berversi yang dihadapi publik yang berbicara ke instance Fasad yang sesuai, yang pada gilirannya berbicara ke backend melalui set API-nya sendiri. Fasad Anda (saya menggunakan Adaptor di proyek saya sebelumnya) menjadi paketnya sendiri, mandiri dan dapat diuji, dan memungkinkan Anda untuk memigrasi API frontend secara independen dari backend, dan satu sama lain.
Ini akan berfungsi jika versi API Anda cenderung menampilkan jenis sumber daya yang sama, tetapi dengan representasi struktural yang berbeda, seperti dalam contoh nama lengkap / nama depan / nama belakang Anda. Akan sedikit lebih sulit jika mereka mulai mengandalkan komputasi backend yang berbeda, seperti dalam, "Layanan backend saya mengembalikan bunga majemuk yang dihitung dengan tidak benar yang telah diekspos di API v1 publik. Pelanggan kami telah memperbaiki perilaku yang salah ini. Oleh karena itu, saya tidak dapat memperbaruinya komputasi di backend dan menerapkannya hingga v2. Oleh karena itu, kita sekarang perlu membagi kode perhitungan bunga kita. " Untungnya, itu cenderung jarang: secara praktis, konsumen RESTful API lebih menyukai representasi sumber daya yang akurat daripada kompatibilitas mundur bug-untuk-bug, bahkan di antara perubahan yang tidak melanggar pada
GET
sumber daya yang secara teoritis tidak berdaya .Saya akan tertarik untuk mendengar keputusan akhir Anda.
sumber
Bagi saya pendekatan kedua lebih baik. Saya telah menggunakannya untuk layanan web SOAP dan berencana menggunakannya untuk REST juga.
Saat Anda menulis, basis kode harus memperhatikan versi, tetapi lapisan kompatibilitas dapat digunakan sebagai lapisan terpisah. Dalam contoh Anda, basis kode dapat menghasilkan representasi sumber daya (JSON atau XML) dengan nama depan dan belakang, tetapi lapisan kompatibilitas akan mengubahnya menjadi hanya nama saja.
Basis kode harus menerapkan hanya versi terbaru, katakanlah v3. Lapisan kompatibilitas harus mengubah permintaan dan respons antara versi terbaru v3 dan versi yang didukung, misalnya v1 dan v2. Lapisan kompatibilitas dapat memiliki adaptor terpisah untuk setiap versi yang didukung yang dapat dihubungkan sebagai rantai.
Sebagai contoh:
Permintaan klien v1: v1 beradaptasi dengan v2 ---> v2 beradaptasi dengan v3 ----> basis kode
Permintaan klien v2: v1 beradaptasi dengan v2 (lewati) ---> v2 beradaptasi dengan v3 ----> basis kode
Untuk respons, adaptor berfungsi hanya dalam arah yang berlawanan. Jika Anda menggunakan Java EE, Anda dapat menggunakan rantai filter servlet sebagai rantai adaptor misalnya.
Menghapus satu versi itu mudah, hapus adaptor yang sesuai dan kode uji.
sumber
Percabangan tampaknya jauh lebih baik bagi saya, dan saya menggunakan pendekatan ini dalam kasus saya.
Ya, seperti yang telah Anda sebutkan - perbaikan bug backport akan membutuhkan usaha, tetapi pada saat yang sama mendukung beberapa versi di bawah satu basis sumber (dengan perutean dan semua hal lainnya) akan membutuhkan Anda jika tidak kurang, tetapi setidaknya upaya yang sama, membuat sistem lebih banyak rumit dan mengerikan dengan cabang-cabang logika yang berbeda di dalamnya (pada beberapa titik pembuatan versi Anda pasti akan sampai pada poin besar
case()
ke modul versi yang memiliki kode duplikat, atau bahkan lebih burukif(version == 2) then...
). Juga jangan lupa bahwa untuk tujuan regresi Anda masih harus membuat pengujian bercabang.Mengenai kebijakan pembuatan versi: saya akan menyimpan versi max -2 dari saat ini, menghentikan dukungan untuk versi lama - yang akan memberikan motivasi bagi pengguna untuk pindah.
sumber
[Version(From="v1", To="v2")]
,[Version(From="v2", To="v3")]
,[Version(From="v1")] // All versions
Hanya menjelajahi sekarang, pernah mendengar siapa pun melakukannya?Biasanya, pengenalan versi utama API yang membuat Anda berada dalam situasi harus mempertahankan beberapa versi adalah peristiwa yang tidak (atau seharusnya tidak) terjadi sangat sering. Namun, hal itu tidak bisa dihindari sepenuhnya. Saya pikir secara keseluruhan adalah asumsi yang aman bahwa versi mayor, setelah diperkenalkan, akan tetap menjadi versi terbaru untuk jangka waktu yang relatif lama. Berdasarkan ini, saya lebih suka mencapai kesederhanaan dalam kode dengan mengorbankan duplikasi karena ini memberi saya kepercayaan diri yang lebih baik untuk tidak melanggar versi sebelumnya ketika saya memperkenalkan perubahan pada yang terbaru.
sumber