Go ve OpenAPI Ekosistemi

Giriş
Go dilinde Production Backend sunucusu geliştirirken neredeyse her geliştiricinin karşılaştığı ilk zorluklardan biri şudur:
API dokümantasyonu nasıl yapılacak...?
Bu konuda biraz araştırma yapıldığında, OpenAPI spesifikasyonuna uygun dokümantasyon yazmanın faydalı olduğu anlaşılır ve doğal olarak OpenAPI ile entegre çalışan kütüphaneler aranmaya başlanır. Ancak bu kararı verseniz bile bir sonraki sorun ortaya çıkar.
OpenAPI ile ilgili kütüphane çok var.. hangisini kullanmalıyım...?
Bu belge, bu durumla karşılaşan Go başlangıç seviyesindeki geliştiriciler için hazırlanmış kısa bir kütüphane tanıtım yazısıdır. 2024 yılı sonu itibarıyla yazılmıştır; dil ekosistemi sürekli değiştiğinden, bu belgeye başvururken en güncel gelişmeleri de takip etmeniz önerilir.
OpenAPI'ye Yaklaşan Kütüphanelerin Stratejileri
Muhtemelen zaten bildiğiniz gibi OpenAPI, REST API'lerini açık biçimde tanımlamak ve belgelemek için kullanılan bir spesifikasyondur. API'nin uç noktaları, istek ve yanıt biçimleri gibi bilgiler YAML veya JSON formatında tanımlanarak; geliştiriciler arasında olduğu kadar ön yüz ve arka yüz kodunun üretilmesinde de otomasyon sağlanır, böylece anlamsız tekrarlar azaltılır ve küçük insan kaynaklı hataların önüne geçilmesine büyük katkı sunulur.
Bu OpenAPI yapısını projeyle doğal biçimde bütünleştirmek için Go ekosistemindeki kütüphaneler genel olarak şu üç stratejiyi benimser:
1. Go yorumlarını OpenAPI spesifikasyon belgesine dönüştürme
OpenAPI'ye uygun şekilde API geliştirirken zorluklardan biri, gerçek belge ile bu belgeyi uygulayan kodun birbirinden ayrı dosyalarda, tamamen farklı konumlarda bulunmasıdır. Bu nedenle kod güncellenip belge güncellenmeyebilir ya da belge güncellenip kod güncellenmeyebilir; bu durum beklenenden daha sık yaşanır.
Basit bir örnek verecek olursak:
./internal/server/user.godosyasında API'nin mantığı değiştirilmiştir,- Asıl belge ise
./openapi3.yamldosyasında bulunur ve bu dosyadaki değişikliği yanlışlıkla unutabilirsiniz. - Bu sorunu fark etmeden Pull Request açılır ve ekip arkadaşlarından inceleme alınırsa,
- İnceleyenler de
./openapi3.yamldosyasındaki değişiklikleri göremeyeceği için, API spesifikasyonu aynı kalırken gerçek API uygulamasının değiştiği bir talihsizlik ortaya çıkabilir.
API dokümantasyonunu Go yorumları biçiminde yazmak bu sorunların bir kısmını çözebilir. Kod ve belge tek bir yerde bulunduğundan, kod değiştirilirken yorumlar da birlikte güncellenebilir. Bu yorumlara dayanarak OpenAPI spesifikasyon belgesini otomatik olarak üreten araçlar mevcuttur.
Bunun temsilci projelerinden biri Swagtir. Swag, Go kodunun yorumlarını ayrıştırarak OpenAPI 2 formatında bir belge üretir. Kullanımı basittir: işleyici (handler) fonksiyonunun üzerine, ilgili kütüphanenin belirlediği formata uygun yorumlar yazmanız yeterlidir.
1// @Summary 유저 생성
2// @Description 새로운 유저를 생성합니다.
3// @Tags Users
4// @Accept json
5// @Produce json
6// @Param user body models.User true "유저 정보"
7// @Success 200 {object} models.User
8// @Failure 400 {object} models.ErrorResponse
9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11 // ...
12}
Bu şekilde yorumlar yazıldığında, Swag adlı CLI aracı bu yorumları ayrıştırarak OpenAPI 2 belgesini oluşturur. Genellikle bu işlem CI sürecinde gerçekleştirilir ve üretilen OpenAPI spesifikasyon belgesi Git Repository'sine, nihai derleme çıktılarına veya ayrı bir harici API dokümantasyon yönetim sistemine dağıtılarak diğer projelerle işbirliği sırasında kullanılır.
Avantajlar:
- Yorumlar kodla birlikte bulunduğundan gerçek kod ile belgenin biçiminin farklılaşma olasılığı azalır.
- Ek bir araç veya karmaşık yapılandırma gerektirmeden, yalnızca yorumlarla kolay ve esnek bir şekilde dokümantasyon yapılabilir.
- Yorumlar gerçek API mantığını etkilemediğinden, dokümante edilmesi rahatsız edici olan geçici özellikler eklemek için uygundur.
Dezavantajlar:
- Yorum satırı sayısı arttıkça tek bir kod dosyasının okunabilirliği düşebilir.
- Tüm API spesifikasyonunu yorum biçiminde ifade etmek zor olabilir.
- Belgenin kodu zorunlu kılmadığı için OpenAPI belgesi ile gerçek mantığın uyumlu olduğu garanti edilemez.
2. OpenAPI spesifikasyon belgesinden Go kodu üretme
Single source of Truth (SSOT) olarak Go kodu yerine belgeyi esas almak da bir yöntemdir. Bu yaklaşımda önce OpenAPI spesifikasyonu tanımlanır ve tanımlanan içerik temel alınarak Go kodu üretilir. API spesifikasyonu kodu zaten ürettiği için, geliştirme kültürü açısından API tasarımının önce yapılması zorunlu hale getirilebilir. Geliştirme sürecinde API spesifikasyonu en başta tanımlandığından, geliştirme tamamlandıktan sonra fark edilen eksikliklerin API spesifikasyonu değişikliğiyle birlikte tüm kodun yeniden düzenlenmesi gibi olumsuzluklar erken aşamada önlenebilir; bu yaklaşımın güçlü yanı budur.
Bu yöntemi benimseyen temsilci projeler oapi-codegen ve OpenAPI Generator projeleridir. Kullanımı basittir:
- OpenAPI spesifikasyonuna uygun bir yaml veya json belgesi yazılır,
- CLI çalıştırılır,
- Buna karşılık gelen Go stub kodu üretilir,
- Artık bu stub'ın kullanabilmesi için yalnızca her bir API'nin ayrıntılı mantığını kendiniz uygulamanız yeterlidir.
Aşağıda oapi-codegen tarafından üretilen kodun bir örneği verilmiştir:
1// StrictServerInterface represents all server handlers.
2type StrictServerInterface interface {
3 // ...
4 // Returns all pets
5 // (GET /pets)
6 FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7 // ...
8}
oapi-codegen'in bu interface üzerinden ürettiği kod; query parametreleri, header ve body ayrıştırma ile Validation gibi işlemleri gerçekleştirir ve interface'de tanımlanan uygun method'u çağırır. Kullanıcının yalnızca bu interface'in bir uygulamasını yazması, API uygulaması için gereken işlerin tamamlanması anlamına gelir.
Avantajlar:
- Önce spesifikasyon ortaya çıktığı ve geliştirme ondan sonra ilerlediği için, birden fazla ekibin işbirliği yaptığı durumlarda işleri paralel olarak yürütmek avantajlıdır.
- Tekrarlayan ve sıkıcı işlerle yapılan kısımların kodu otomatik olarak üretildiğinden, iş verimliliği artar ve hata ayıklama açısından hâlâ avantajlıdır.
- Belge ile kodun biçiminin her zaman uyumlu olduğunu garanti etmek kolaydır.
Dezavantajlar:
- OpenAPI spesifikasyonu hakkında bilgi sahibi değilseniz başlangıçta öğrenme eğrisi biraz dik olabilir.
- API'yi yöneten kodun biçimi proje tarafından otomatik üretildiğinden, özelleştirme gerektiğinde buna uyum sağlamak zor olabilir.
Yazarın yorumu. Ekim 2024 itibarıyla OpenAPI Generator'ın ürettiği Go kodu, yalnızca API mantığını değil tüm proje yapısını da zorunlu kılmakta ve proje yapısını katılaştırmaktadır; bu nedenle gerçek Production ortamının gerektirdiği çeşitli işlevleri eklemek için uygun olmayan bir kod üretmektedir. Bu yöntemi tercih edenlerin oapi-codegen kullanmasını şiddetle tavsiye ederim. Yazar olarak ben oapi-codegen + echo + StrictServerInterface kullanıyorum.
3. Go kodundan OpenAPI spesifikasyon belgesi üretme
Onlarca, yüzlerce kişi aynı sunucu üzerinde geliştirme yaptığında, kaçınılmaz olarak ortaya çıkan bir sorun, API'ler arasındaki tutarlılığın bozulabilmesidir. Somut bir örnek vermek gerekirse: 100'ü aşan API Endpoint'inin spesifikasyonu tek bir OpenAPI yaml dosyasında tanımlanırsa, bu dosya on binlerce satırlık bir canavara dönüşür. Yeni bir API Endpoint'i tanımlanırken aynı modelin tekrar tanımlanması, bazı alanların unutulması veya kurallara uymayan Path adlandırmalarının ortaya çıkması gibi nedenlerle tüm API'nin tutarlılığı bozulmaya başlar.
Bu sorunu çözmek için OpenAPI yaml dosyasını yönetecek bir Owner atanabilir ya da CI sürecinde otomatik olarak yakalanabilmesi için bir Linter geliştirilebilir. Ancak Go dilinde Domain-specific language (DSL) tanımlanarak tüm API'lerin tutarlı bir yapıya sahip olması zorunlu kılınabilir.
Bu tekniği kullanan temsilci proje Kubernetes'tir (herhangi bir ek kütüphane olmadan kendi yapısıyla oluşturulmuştur). Ayrıca go-restful, goa gibi projeler de kullanılabilir. Aşağıda goa kullanım örneği verilmiştir:
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})
Yukarıdaki gibi derlenebilir Go kodu yazıldığında, POST /users API'sinin uygulanması ile belgesinin tanımlanmasının aynı anda tamamlanması gibi bir güç elde edilir.
Avantajlar:
- Her şey koddan üretildiği için tüm proje genelinde API tutarlılığını sağlamak kolaydır.
- Go'nun güçlü tip sistemi kullanılarak, OpenAPI3'ün tüm özelliklerinin kullanıldığı duruma kıyasla daha kesin ve tartışmaya yer bırakmayan bir spesifikasyon elde edilebilir.
Dezavantajlar:
- Her framework'ün tanımladığı DSL'i öğrenmek gerekir ve mevcut koda uygulanması zor olabilir.
- Framework'ün önerdiği kurallara zorunlu olarak uyulması gerektiğinden özgürlük ve esneklik azalabilir.
Sonuç olarak
Her yöntemin avantajları ve dezavantajları vardır; projenin gereksinimlerine ve ekibin tercihlerine göre uygun yöntemi seçmek önemlidir. En önemli şey hangi yöntemin daha iyi olduğu değil, içinde bulunulan duruma en uygun çözümün ne olduğunu değerlendirmek ve geliştirme verimliliğini yüksek tutarak erken çıkışın ve dengeli bir iş-yaşam dengesinin keyfini çıkarmaktır.
Bu yazıyı Ekim 2024 itibarıyla yazmış olsam da Go ve OpenAPI ekosistemi sürekli gelişmektedir; bu yazıyı okuduğunuz zamanla aramızdaki farkı göz önünde bulundurarak, kütüphanelerin ve projelerin güncel durumunu ve değişen avantaj-dezavantajlarını da takip etmenizi dilerim.
Mutlu Go yolculukları dilerim~ 😘