GoSuda

Go ve OpenAPI Ekosistemi

By iwanhae
views ...

서론

Go dili ile Production Backend sunucusu geliştirirken, çoğu geliştiricinin karşılaştığı ilk zorluklardan biri şöyledir:

API dokümantasyonu nasıl yapılır...?

Bu konuda biraz araştırma yapıldığında, OpenAPI Specification'a uygun dokümanlar hazırlamanın faydalı olduğu anlaşılır ve doğal olarak OpenAPI ile entegre çalışan kütüphaneler aranır. Ancak bu karar verildiğinde bile bir sonraki sorun ortaya çıkar.

Çok sayıda OpenAPI ile ilgili kütüphane var... Hangisini kullanmalıyım...?

Bu belge, bu durumu deneyimleyen Go başlangıç seviyesi kullanıcıları 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ış bir belgedir ve dil ekosistemi sürekli değiştiği için referans alırken her zaman en son gelişmeleri de takip etmeniz önerilir.

OpenAPI'ye Yaklaşan Kütüphanelerin Stratejileri

Bildiğiniz üzere, OpenAPI, REST API'lerini açıkça tanımlamak ve belgelemek için bir Specification'dır. API'nin uç noktalarını, isteklerini, yanıt formatlarını YAML veya JSON formatında tanımlayarak sadece geliştiricilere değil, ön yüz ve arka yüz kod üretimini otomatikleştirerek anlamsız tekrarları azaltmaya ve küçük insan hatalarını minimize etmeye büyük katkı sağlar.

Bu OpenAPI'yi projelerle doğal bir şekilde birleştirmek için Go ekosistemindeki kütüphaneler genellikle şu üç stratejiyi benimser:

1. Go Yorumlarını OpenAPI Specification Belgesine Dönüştürme

API'yi OpenAPI'ye uygun olarak geliştirirken karşılaşılan zorluklardan biri, gerçek doküman ile bu dokümanı uygulayan kodun ayrı dosyalarda ve tamamen farklı konumlarda bulunması nedeniyle, kodu güncelledikten sonra dokümanı güncellemeyi unutmak veya dokümanı güncelledikten sonra kodu güncelleyememe durumunun beklenenden daha sık yaşanmasıdır.

Basit bir örnek vermek gerekirse:

  1. ./internal/server/user.go adlı bir dosya içinde API'ye ilişkin mantık değiştirildiğinde
  2. Gerçek doküman ./openapi3.yaml içinde bulunur ve bu değişikliği yanlışlıkla atlamak mümkündür.
  3. Bu değişikliklerle ilgili bir sorunun farkına varmadan bir Pull Request gönderilip ekip arkadaşlarına inceleme için sunulduğunda
  4. İnceleyicilerin de ./openapi3.yaml üzerindeki değişiklikleri görememesi nedeniyle API Specification'ı aynı kalırken, gerçek API uygulamasının değişmesi gibi talihsiz bir durum ortaya çıkabilir.

Go yorumları şeklinde API dokümantasyonu yazmak bu sorunu bir dereceye kadar çözebilir. Kod ve doküman tek bir yerde toplandığı için, kodu düzenlerken yorumları da birlikte güncelleyebilirsiniz. Bu yorumlara dayanarak otomatik olarak OpenAPI Specification belgesi oluşturan araçlar mevcuttur.

Temsilci projelerden biri Swag'dir. Swag, Go kodundaki yorumları ayrıştırarak OpenAPI 2 formatında dokümanlar oluşturur. Kullanımı basittir. Handler fonksiyonunun üzerine her kütüphanenin belirlediği formata uygun yorumlar yazmanız yeterlidir.

 1// @Summary Kullanıcı Oluştur
 2// @Description Yeni bir kullanıcı oluşturur.
 3// @Tags Users
 4// @Accept json
 5// @Produce json
 6// @Param user body models.User true "Kullanıcı Bilgisi"
 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 bu yorumları ayrıştırarak OpenAPI 2 dokümanı oluşturur. Genellikle CI sürecinde bu işlem gerçekleştirilir ve oluşturulan OpenAPI Specification dokümanı Git Repository, nihai derleme çıktısı veya ayrı bir harici API doküman yönetim sistemine dağıtılarak diğer projelerle işbirliğinde kullanılır.

Avantajlar:

  • Yorumlar kodla birlikte olduğu için gerçek kod ile doküman arasındaki farklılaşma olasılığı azalır.
  • Ayrı bir araca veya karmaşık bir yapılandırmaya gerek kalmadan sadece yorumlarla kolayca ve özgürce dokümantasyon yapılabilir.
  • Yorumlar gerçek API mantığını etkilemediği için, dokümanda yayınlaması zor olabilecek 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 Specification'ını yorum formatında ifade etmek zor olabilir.
  • Doküman kodu zorlamadığı için OpenAPI dokümanı ile gerçek mantığın uyumlu olduğunu garanti edemezsiniz.

2. OpenAPI Specification Belgesinden Go Kodu Oluşturma

Single source of Truth (SSOT)'yi Go kodu yerine doküman tarafına yerleştiren bir yöntem de mevcuttur. Bu, önce OpenAPI Specification'ı tanımlamak ve tanımlanan içeriğe dayanarak Go kodu oluşturmak şeklindedir. API Specification'ı doğrudan kodu oluşturduğu için, geliştirme kültürü açısından API tasarımını öncelikli hale getirmeyi zorunlu kılabilir ve geliştirme sırasına göre API Specification'ı tanımlamak en başta başladığı için, geliştirme tamamlandıktan sonra kaçırılan kısımların farkına varılması ve API Specification değişikliğiyle birlikte tüm kodun değiştirilmesi gibi talihsiz durumları erken aşamada önleyebilen güçlü bir yanı vardır.

Bu yaklaşımı benimseyen temsilci projeler arasında oapi-codegen ve OpenAPI Generator bulunmaktadır. Kullanımı basittir.

  1. OpenAPI Specification'a uygun bir yaml veya json belgesi oluşturulur.
  2. CLI çalıştırılır.
  3. Buna karşılık gelen Go stub kodu oluşturulur.
  4. Artık bu stub'ın kullanabileceği şekilde her bir API için detaylı mantık doğrudan uygulanır.

Aşağıda oapi-codegen tarafından oluşturulan kod örneği verilmiştir.

1// StrictServerInterface tüm sunucu işleyicilerini temsil eder.
2type StrictServerInterface interface {
3	// ...
4	// Tüm evcil hayvanları döndürür
5	// (GET /pets)
6	FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7	// ...
8}

Yukarıdaki interface'i aracı olarak kullanan oapi-codegen tarafından oluşturulan kod, query parameters, header, body ayrıştırma ve Validation gibi mantıkları gerçekleştirir ve interface'de belirtilen uygun method'u çağırır. Kullanıcının sadece yukarıdaki interface için bir uygulama yapması, API uygulamasının tamamlanması için yeterlidir.

Avantajlar:

  • Specification önce gelir ve geliştirme ilerler bu nedenle birden fazla ekibin işbirliği yaptığı durumlarda işleri paralel olarak yürütmek avantajlıdır.
  • Tekrarlayan el emeğiyle yapılan işler için kod otomatik olarak oluşturulduğu için, iş verimliliği artar ve hata ayıklama hala avantajlıdır.
  • Doküman ile kodun her zaman uyumlu olduğunu garanti etmek kolaydır.

Dezavantajlar:

  • OpenAPI Specification'ına aşina olunmadığı durumda başlangıçta bir öğrenme eğrisi olabilir.
  • API'yi işleyen kodun formatı proje tarafından otomatik olarak oluşturulduğu için özelleştirme gerektiğinde yanıt vermek zor olabilir.

Yazarın yorumu. Ekim 2024 itibarıyla, OpenAPI Generator tarafından oluşturulan Go kodu, API mantığına ek olarak tüm proje yapısını zorlamakta ve projenin yapısı katı olduğundan, gerçek Production ortamında gerekli çeşitli özellikleri eklemek için uygun olmayan bir kod formatı oluşturmaktadır. Bu yöntemi benimseyen kişilerin oapi-codegen kullanmasını şiddetle tavsiye ederim. Yazar, oapi-codege + echo + StrictServerInterface kullanmaktadır.

3. Go Kodu ile OpenAPI Specification Belgesi Oluşturma

Onlarca, hatta yüzlerce kişinin aynı sunucu üzerinde geliştirme yapması durumunda kaçınılmaz olarak ortaya çıkan bir sorun, ayrı API'ler arasında tutarlılığın bozulabilmesidir. Sezgisel bir örnek olarak, 100'den fazla API Endpoint'inin Specification'ını tek bir OpenAPI yaml dosyasına bildirmek, bu dosyanın 10.000 satırı aşan bir canavara dönüşmesine neden olacak ve yeni bir API Endpoint bildiriminde kaçınılmaz olarak aynı modelin tekrar tekrar bildirilmesi, bazı alanların eksik bırakılması veya convention'a uymayan Path isimlendirmelerinin ortaya çıkması gibi genel API tutarlılığının bozulmaya başlaması söz konusu olacaktır.

Bu tür sorunları çözmek için OpenAPI yaml'ı yöneten ayrı bir Owner atanabilir veya bir Linter geliştirilerek CI sürecinde otomatik olarak yakalanması sağlanabilir, ancak Go diliyle Domain-specific language (DSL) tanımlayarak tüm API'lerin tutarlı bir bütünlüğe sahip olması zorunlu kılınabilir.

Bu tekniği kullanan temsilci projelerden biri Kubernetes'tir (ayrı bir kütüphane olmaksızın kendi bünyesinde geliştirilmiştir) ve go-restful, goa gibi projeler kullanılarak denenebilir. 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ı ve dokümantasyonunun tanımlanması aynı anda tamamlanmış olur, bu da önemli bir avantajdır.

Avantajlar:

  • Her şey koddan türediği için tüm projenin API tutarlılığını sağlamak kolaydır.
  • Go'nun güçlü tip sistemini kullanarak, OpenAPI3'ün tüm özelliklerini kullanmaya kıyasla daha doğru ve tartışmasız bir Specification elde edilebilir.

Dezavantajlar:

  • Her framework'ün tanımladığı DSL'i öğrenmek gereklidir ve mevcut koda uygulamak zor olabilir.
  • Framework tarafından önerilen kurallara zorunlu olarak uyulması gerektiği için ö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. Her zaman en önemli olan, hangi yöntemin daha iyi olduğu değil, mevcut durumunuza en uygun çözümün ne olduğuna dair bir değer yargısı oluşturmak ve geliştirme verimliliğini yüksek tutarak erken mesai bitimi ve tatmin edici bir iş-yaşam dengesi elde etmektir.

Bu yazı, Ekim 2024 itibarıyla yazılmış olsa da, Go ve OpenAPI ekosistemi sürekli geliştiği için, bu yazıyı okuduğunuz zaman dilimini göz önünde bulundurarak, ilgili kütüphanelerin ve projelerin güncel durumlarını ve değişen avantaj ve dezavantajlarını sürekli takip etmeniz önerilir.

Mutlu Go yaşamları dilerim~ 😘