GoSuda

Go ve OpenAPI ekosistemi

By iwanhae
views ...

Giriş

Go dili ile Üretim Arka Uç sunucusu geliştirirken, çoğu geliştiricinin karşılaştığı ilk zorluklardan biri şudur:

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

Bu konuda biraz araştırma yapıldığında, OpenAPI spesifikasyonuna uygun belgeler oluşturmanın faydalı olduğu anlaşılır ve doğal olarak OpenAPI ile entegre olan bir kütüphane aranır. Ancak bu kararı verdikten sonra bile bir sonraki sorun ortaya çıkar.

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

Bu belge, bu durumu deneyimleyen Go diline yeni başlayanlar için yazılmış kısa bir kütüphane tanıtım yazısıdır. Belge 2024 sonu itibarıyla yazılmıştır ve dil ekosistemi sürekli değiştiği için, referans alırken her zaman en güncel durumu da incelemeniz önerilir.

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

Zaten bildiğiniz gibi, OpenAPI, REST API'lerini açıkça tanımlamak ve belgelemek için kullanılan bir spesifikasyondur. API'lerin uç noktalarını, isteklerini, yanıt biçimlerini YAML veya JSON formatında tanımlayarak geliştiricilerin yanı sıra ön uç ve arka uç kodlarının otomatik olarak oluşturulmasını sağlayarak anlamsız tekrarları azaltmaya ve küçük insan hatalarını en aza indirmeye büyük yardımcı olur.

Bu tür OpenAPI'yi projelerle doğal olarak birleştirmek için, Go ekosistemindeki kütüphaneler genel olarak aşağıdaki üç stratejiyi benimser.

1. Go Açıklamalarını OpenAPI Spesifikasyon Belgesine Dönüştürme

OpenAPI'ye uygun API'ler geliştirirken zorlu noktalardan biri, gerçek belgenin ve bu belgeyi uygulayan kodun tamamen farklı konumlarda, ayrı dosyalarda bulunmasıdır. Bu durum, kod güncellendiğinde belgenin güncellenmemesi veya belge güncellendiğinde kodun güncellenmemesi gibi sıkıntılara yol açabilmektedir.

Basit bir örnek vermek gerekirse:

  1. ./internal/server/user.go dosyasındaki API ile ilgili mantığı değiştirdiniz, ancak
  2. Gerçek belge ./openapi3.yaml içinde bulunuyor ve bu değişikliği yapmayı yanlışlıkla unutabilirsiniz.
  3. Bu değişiklik sorununu fark etmeden bir Çekme İsteği (Pull Request) gönderir ve meslektaşlarınızdan inceleme isterseniz,
  4. İnceleyiciler de ./openapi3.yaml dosyasındaki değişiklikleri görmeyecekleri için, API spesifikasyonu aynı kalırken gerçek API uygulaması değişmiş olabilir.

Go açıklamaları şeklinde API belgeleri yazmak, bu sorunu bir ölçüde çözebilir. Kod ve belge tek bir yerde bulunduğundan, kodu değiştirirken açıklamalar da birlikte güncellenebilir. Bu açıklamalar temel alınarak, otomatik olarak OpenAPI spesifikasyon belgeleri üreten araçlar mevcuttur.

Bu alanda öne çıkan bir proje Swag'dir. Swag, Go kodundaki açıklamaları ayrıştırarak OpenAPI 2 formatında belgeler üretir. Kullanımı basittir. İşleyici fonksiyonların üstüne, her kütüphanenin belirlediği formata uygun açıklamalar yazmanız yeterlidir.

 1// @Summary Kullanıcı Oluştur
 2// @Description Yeni bir kullanıcı oluşturur.
 3// @Tags Kullanıcılar
 4// @Accept json
 5// @Produce json
 6// @Param user body models.User true "Kullanıcı Bilgileri"
 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 açıklamalar yazıldığında, Swag adlı CLI bu açıklamaları ayrıştırarak OpenAPI 2 belgesi oluşturur. Genellikle CI sürecinde bu işlem yapılır ve oluşturulan OpenAPI spesifikasyon belgesi Git Repository'ye, nihai derleme sonucuna veya ayrı bir harici API belge yönetim sistemine dağıtılarak diğer projelerle işbirliği yapılırken kullanılır.

Avantajları:

  • Açıklamalar kodla birlikte olduğu için, gerçek kod ile belge arasında tutarsızlık oluşma olasılığı azalır.
  • Ayrı bir araç veya karmaşık bir ayar yapmadan, sadece açıklamalarla kolayca ve serbestçe dokümantasyon yapılabilir.
  • Açıklamalar gerçek API mantığını etkilemediğinden, belge olarak yayınlanması zor olan geçici özellikler eklemek kolaydır.

Dezavantajları:

  • Açıklama satır sayısı arttıkça, tek bir kod dosyasının okunabilirliği azalabilir.
  • Açıklama formatıyla tüm API spesifikasyonunu ifade etmek zor olabilir.
  • Belge kodu zorlamadığı için, OpenAPI belgesi ile gerçek mantığın eşleştiği garanti edilemez.

2. OpenAPI Spesifikasyon Belgesinden Go Kodu Oluşturma

Tek Gerçek Kaynak (Single Source of Truth - SSOT) ilkesini Go kodunda değil, belge tarafında tutmanın da bir yolu vardır. Bu, OpenAPI spesifikasyonunu önce tanımlayıp, tanımlanan içeriğe dayanarak Go kodu oluşturma yöntemidir. API spesifikasyonu doğrudan kodu oluşturduğu için, geliştirme kültürü açısından önce API tasarımının yapılması zorunlu hale gelir. Ayrıca, geliştirme sürecinde API spesifikasyonunun tanımlanması ilk adım olduğundan, geliştirme tamamlandıktan sonra gözden kaçan noktaların fark edilmesi ve API spesifikasyonuyla birlikte tüm kodun değiştirilmesi gibi olumsuz durumlar erkenden önlenmiş olur.

Bu yaklaşımı benimseyen başlıca projeler oapi-codegen ve OpenAPI Generator'dır. Kullanımı basittir:

  1. OpenAPI spesifikasyonuna uygun bir YAML veya JSON belgesi yazılır,
  2. CLI çalıştırılır,
  3. Buna karşılık gelen Go stub kodu oluşturulur,
  4. Artık bu stub'ın kullanabilmesi için her bir API'nin ayrıntılı mantığını doğrudan uygulamanız yeterlidir.

Aşağıda, oapi-codegen tarafından oluşturulan bir kod örneği bulunmaktadır:

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 arayüz aracılığıyla, oapi-codegen'in oluşturduğu kod, sorgu parametrelerini, başlıkları, gövde ayrıştırmasını ve doğrulamasını (Validation) yapar ve arayüzde bildirilen uygun metodu çağırır. Kullanıcılar sadece yukarıdaki arayüzün bir uygulamasını oluşturarak API uygulamasında gerekli işlemleri tamamlar.

Avantajları:

  • Spesifikasyon önce belirlenip geliştirme sonra yapıldığı için, birden çok ekibin işbirliği yaptığı durumlarda işleri paralel yürütmek kolaylaşır.
  • Tekrarlanan ve zahmetli işler için kod otomatik olarak oluşturulduğundan, iş verimliliği artar ve hata ayıklama hala kolaydır.
  • Belge ve kodun daima tutarlı olduğu kolayca garanti edilebilir.

Dezavantajları:

  • OpenAPI spesifikasyonu hakkında bilgi sahibi olmayanlar için başlangıç öğrenme eğrisi biraz dik olabilir.
  • API'leri işleyen kodun yapısı projeler tarafından otomatik olarak oluşturulduğundan, özelleştirme gerektiğinde uyum sağlamak zor olabilir.

Yazarın Notu. 2024 Ekim itibariyle, OpenAPI Generator tarafından oluşturulan Go kodu, yalnızca API mantığını değil, tüm proje yapısını zorlar ve projenin yapısı katı olduğu için, gerçek Üretim ortamlarında ihtiyaç duyulan çeşitli işlevleri eklemek için uygun olmayan bir kod yapısı oluşturur. Bu yöntemi benimseyenlere oapi-codegen kullanmalarını şiddetle tavsiye ederim. Yazar, oapi-codegen + echo + StrictServerInterface kullanmaktadır.

3. Go Kodu ile OpenAPI Spesifikasyon Belgesi Oluşturma

Aynı sunucu üzerinde onlarca, yüzlerce kişinin geliştirme yapması durumunda kaçınılmaz olarak ortaya çıkan sorun, her bir API'nin tutarlılığının bozulabilmesidir. Basit bir örnekle, 100'den fazla API uç noktasının (Endpoint) spesifikasyonunu tek bir OpenAPI YAML dosyasına tanımladığınızda, bu dosya 10.000 satırı aşan bir canavar haline gelecektir. Yeni bir API uç noktası tanımlarken, aynı modelleri tekrar tekrar tanımlamak, bazı alanları gözden kaçırmak veya konvansiyona uymayan Path adlandırmaları oluşturmak gibi durumlar genel API tutarlılığını bozmaya başlayacaktır.

Bu sorunu çözmek için OpenAPI YAML'i yöneten bir sahibi görevlendirebilir, bir Linter geliştirerek CI sürecinde otomatik olarak yakalanmasını sağlayabilirsiniz. Ancak Go dilinde Domain-specific language (DSL) tanımlayarak tüm API'lerin tutarlı olmasını zorunlu kılabilirsiniz.

Bu tekniği kullanan başlıca projeler Kubernetes (ayrı bir kütüphane olmadan kendisi tarafından oluşturulmuş) ile go-restful ve goa gibi projelerdir. Aşağıda goa kullanımına bir örnek 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 hem uygulaması hem de belge tanımı aynı anda tamamlanmış olur ve bu da önemli bir avantaj sağlar.

Avantajları:

  • Her şey koddan çıktığı için, projenin tamamında API tutarlılığını sağlamak kolaylaşır.
  • Go'nun güçlü tip sistemini kullanarak, OpenAPI3'ün tüm özelliklerini kullandığınızdan daha kesin ve tartışmasız bir spesifikasyon elde edebilirsiniz.

Dezavantajları:

  • Her çerçevenin tanımladığı DSL'yi öğrenmeniz gerekir ve mevcut koda uygulamak zor olabilir.
  • Çerçevenin önerdiği kurallara uymak zorunda kalacağınız 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. En önemli şey, hangi yöntemin iyi olduğundan ziyade, mevcut durumunuza en uygun çözümün ne olduğunu değerlendirip geliştirme verimliliğini yüksek tutarak erken işten çıkmak ve tatmin edici bir iş-yaşam dengesinin tadını çıkarmaktır.

Bu makale 2024 Ekim ayı itibarıyla yazılmış olsa da, Go ve OpenAPI ekosistemi sürekli olarak gelişmektedir. Bu nedenle, bu yazıyı okuduğunuz zaman aralığını göz önünde bulundurarak, her kütüphane ve projenin güncel durumunu ve değişen avantaj ve dezavantajlarını sürekli olarak takip etmenizi öneririm.

Mutlu Go hayatı dilerim~ 😘