Go dan Ekosistem OpenAPI
Pendahuluan
Ketika mengembangkan server Production Backend dengan bahasa Go, hampir semua pengembang akan menemui salah satu tantangan pertama sebagai berikut:
Bagaimana cara mendokumentasikan API...?
Setelah melakukan sedikit pencarian, akan disadari bahwa membuat dokumentasi yang sesuai dengan spesifikasi OpenAPI adalah hal yang bermanfaat, dan secara alami akan mencari library yang terintegrasi dengan OpenAPI. Namun, bahkan setelah mengambil keputusan ini, masalah lain akan muncul.
Ada banyak library terkait OpenAPI.. Mana yang harus saya gunakan...?
Dokumen ini adalah pengantar singkat tentang library yang ditulis untuk pemula Go yang mengalami situasi ini. Dokumen ini ditulis pada akhir tahun 2024, dan karena ekosistem bahasa selalu berubah secara dinamis, disarankan untuk selalu memeriksa perkembangan terbaru sambil mengacu pada dokumen ini.
Strategi Library dalam Menghadapi OpenAPI
Seperti yang mungkin sudah Anda ketahui, OpenAPI adalah spesifikasi untuk mendefinisikan dan mendokumentasikan REST API secara jelas. Dengan mendefinisikan endpoint API, permintaan, dan format respons dalam format YAML atau JSON, hal ini sangat membantu dalam mengurangi pengulangan yang tidak berarti dan mengurangi kesalahan manusia kecil dengan mengotomatiskan pembuatan kode front-end dan back-end, tidak hanya untuk pengembang.
Untuk mengintegrasikan OpenAPI secara alami dengan proyek, library di ekosistem Go mengambil tiga strategi utama berikut.
1. Menggabungkan Anotasi Go menjadi Dokumen Spesifikasi OpenAPI
Salah satu kesulitan saat mengembangkan API sesuai dengan OpenAPI adalah fakta bahwa dokumen sebenarnya dan kode yang mengimplementasikan dokumen tersebut berada di lokasi yang sama sekali berbeda sebagai file terpisah, sehingga sering terjadi situasi di mana kode telah diperbarui tetapi dokumen belum, atau dokumen telah diperbarui tetapi kode belum.
Sebagai contoh sederhana:
- Logika untuk API telah dimodifikasi dalam file
./internal/server/user.go
, tetapi - Dokumen sebenarnya ada di
./openapi3.yaml
, dan perubahannya mungkin terlupakan karena kesalahan. - Jika Pull Request diajukan tanpa menyadari masalah perubahan ini, dan kemudian ditinjau oleh rekan kerja
- Karena perubahan pada
./openapi3.yaml
tidak terlihat oleh reviewer, dapat terjadi insiden di mana spesifikasi API tetap sama, tetapi implementasi API yang sebenarnya telah berubah.
Dengan menulis dokumentasi API dalam bentuk anotasi Go, masalah ini dapat diatasi sampai batas tertentu. Karena kode dan dokumen berada di satu tempat, anotasi dapat diperbarui saat kode dimodifikasi. Ada alat yang secara otomatis menghasilkan dokumen spesifikasi OpenAPI berdasarkan anotasi ini.
Proyek yang paling representatif adalah Swag. Swag mem-parsing anotasi kode Go untuk menghasilkan dokumen dalam format OpenAPI 2. Penggunaannya sederhana. Cukup tulis anotasi di atas fungsi handler sesuai dengan format yang ditentukan oleh setiap library.
1// @Summary Membuat pengguna
2// @Description Membuat pengguna baru.
3// @Tags Users
4// @Accept json
5// @Produce json
6// @Param user body models.User true "Informasi pengguna"
7// @Success 200 {object} models.User
8// @Failure 400 {object} models.ErrorResponse
9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11 // ...
12}
Dengan menulis anotasi seperti ini, CLI Swag akan mem-parsing anotasi ini untuk menghasilkan dokumen OpenAPI 2. Biasanya, operasi ini dilakukan dalam proses CI, dan dokumen spesifikasi OpenAPI yang dihasilkan didistribusikan ke Git Repository, hasil build akhir, dan sistem manajemen dokumen API eksternal terpisah untuk digunakan saat berkolaborasi dengan proyek lain.
Keuntungan:
- Karena anotasi ada bersama kode, kemungkinan perbedaan antara bentuk kode dan dokumen yang sebenarnya berkurang.
- Dokumentasi dapat dilakukan dengan mudah dan bebas hanya dengan menggunakan anotasi tanpa alat atau pengaturan yang rumit.
- Karena anotasi tidak memengaruhi logika API yang sebenarnya, mudah untuk menambahkan fitur sementara yang tidak nyaman untuk dipublikasikan sebagai dokumentasi.
Kerugian:
- Seiring bertambahnya jumlah baris anotasi, keterbacaan file kode tunggal dapat menurun.
- Mungkin sulit untuk mengekspresikan semua spesifikasi API dalam bentuk anotasi.
- Karena dokumentasi tidak memaksa kode, tidak ada jaminan bahwa dokumen OpenAPI dan logika aktual akan cocok.
2. Menghasilkan Kode Go dari Dokumen Spesifikasi OpenAPI
Ada juga cara untuk menempatkan Single Source of Truth (SSOT) di sisi dokumen, bukan kode Go. Ini adalah metode untuk mendefinisikan spesifikasi OpenAPI terlebih dahulu, dan kemudian menghasilkan kode Go berdasarkan konten yang ditentukan. Karena spesifikasi API segera menghasilkan kode, secara budaya pengembangan dapat memaksa desain API dilakukan terlebih dahulu, dan dalam urutan pengembangan, mendefinisikan spesifikasi API adalah hal pertama yang dilakukan, sehingga dapat mencegah insiden di mana bagian yang terlewat disadari hanya setelah pengembangan selesai, dan seluruh kode dimodifikasi bersama dengan perubahan spesifikasi API.
Proyek-proyek perwakilan yang menggunakan metode ini adalah oapi-codegen dan OpenAPI Generator. Penggunaannya sederhana.
- Tulis dokumen yaml atau json sesuai dengan spesifikasi OpenAPI, dan
- Jalankan CLI, maka
- Kode stub Go yang sesuai akan dihasilkan.
- Sekarang, yang perlu Anda lakukan hanyalah mengimplementasikan logika detail untuk setiap API agar stub ini dapat digunakan.
Berikut adalah contoh kode yang dihasilkan oleh oapi-codegen.
1// StrictServerInterface merepresentasikan semua handler server.
2type StrictServerInterface interface {
3 // ...
4 // Mengembalikan semua hewan peliharaan
5 // (GET /pets)
6 FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7 // ...
8}
Melalui interface di atas, kode yang dihasilkan oleh oapi-codegen menjalankan logika seperti parsing dan Validasi parameter query, header, dan body, dan memanggil metode yang sesuai yang dideklarasikan dalam interface. Pengguna hanya perlu mengimplementasikan implementasi untuk interface di atas untuk menyelesaikan pekerjaan yang diperlukan untuk implementasi API.
Keuntungan:
- Karena spesifikasi keluar terlebih dahulu dan pengembangan dilakukan, mudah untuk melakukan pekerjaan secara paralel jika beberapa tim bekerja sama.
- Karena kode untuk bagian-bagian yang sebelumnya dikerjakan dengan pekerjaan berulang dihasilkan secara otomatis, efisiensi kerja meningkat sementara debugging tetap menguntungkan.
- Mudah untuk memastikan bahwa bentuk dokumen dan kode selalu cocok.
Kerugian:
- Jika Anda tidak terbiasa dengan spesifikasi OpenAPI, kurva pembelajaran awal akan ada.
- Karena bentuk kode yang menangani API dihasilkan secara otomatis oleh proyek, mungkin sulit untuk menanganinya jika kustomisasi diperlukan.
Komentar penulis. Per Oktober 2024, kode Go yang dihasilkan oleh OpenAPI Generator memaksakan bentuk keseluruhan proyek serta logika API, dan struktur proyek menjadi kaku, sehingga menghasilkan kode yang tidak cocok untuk menambahkan berbagai fungsi yang diperlukan untuk lingkungan Production yang sebenarnya. Bagi mereka yang mengadopsi metode ini, sangat disarankan untuk menggunakan oapi-codegen. Penulis menggunakan oapi-codege + echo + StrictServerInterface.
3. Menghasilkan Dokumen Spesifikasi OpenAPI dengan Kode Go
Masalah yang pasti akan muncul ketika puluhan atau ratusan orang mengembangkan server yang sama adalah kurangnya keseragaman untuk setiap API individual. Sebagai contoh intuitif, jika spesifikasi untuk lebih dari 100 Endpoint API dideklarasikan dalam satu file yaml OpenAPI, file tersebut akan menjadi monster yang lebih dari 10.000 baris, dan saat mendeklarasikan Endpoint API baru, model yang sama akan berulang kali dideklarasikan, atau beberapa field akan hilang, atau penamaan Path yang tidak sesuai dengan konvensi akan muncul, dan keseragaman keseluruhan API akan mulai rusak.
Untuk mengatasi masalah ini, Anda dapat menunjuk Owner terpisah untuk mengelola yaml OpenAPI, atau mengembangkan Linter untuk mengambil tindakan agar dapat ditangkap secara otomatis selama proses CI, tetapi dengan mendefinisikan bahasa khusus domain (DSL) dalam bahasa Go, Anda dapat memaksakan semua API untuk memiliki konsistensi yang konsisten.
Proyek representatif yang menggunakan teknik ini adalah Kubernetes (dibangun sendiri tanpa library terpisah), dan Anda juga dapat menggunakannya menggunakan proyek seperti go-restful, goa. Berikut adalah contoh penggunaan goa
.
1var _ = Service("user", func() {
2 Method("create", func() {
3 Payload(UserPayload)
4 Result(User)
5 HTTP(func() {
6 POST("/users")
7 Response(StatusOK)
8 })
9 })
10})
Dengan menulis kode Go yang dapat dikompilasi seperti di atas, Anda dapat memperoleh keuntungan karena implementasi dan definisi dokumentasi untuk API POST /users
selesai secara bersamaan.
Keuntungan:
- Karena semuanya berasal dari kode, mudah untuk mempertahankan konsistensi API untuk seluruh proyek.
- Dengan memanfaatkan sistem strong type Go, Anda bisa mendapatkan spesifikasi yang lebih akurat dan tidak kontroversial daripada saat semua fungsi OpenAPI3 digunakan.
Kerugian:
- Anda harus mempelajari DSL yang didefinisikan di setiap framework, dan mungkin sulit untuk menerapkannya pada kode yang ada.
- Karena Anda harus mengikuti aturan yang diusulkan oleh framework secara paksa, kebebasan dan fleksibilitas dapat berkurang.
Sebagai Penutup
Setiap metode memiliki kelebihan dan kekurangan, dan penting untuk memilih metode yang sesuai tergantung pada persyaratan proyek dan preferensi tim. Yang terpenting adalah bukan metode mana yang terbaik untuk digunakan, tetapi untuk menilai solusi mana yang paling sesuai dengan situasi Anda saat ini, dan meningkatkan produktivitas pengembangan untuk menikmati pulang kerja lebih awal dan keseimbangan kerja dan kehidupan yang memuaskan.
Meskipun artikel ini ditulis pada Oktober 2024, ekosistem Go dan OpenAPI terus berkembang, jadi harap pertimbangkan jeda waktu antara saat Anda membaca artikel ini dan terus menindaklanjuti perkembangan terbaru dari setiap library dan proyek serta kelebihan dan kekurangan mereka yang telah berubah.
Semoga hidup Go Anda menyenangkan~ 😘