Go Programming

Go'da Generic HTTP İstemcisi ve Repository Kalıpları: Pratik Uygulama Rehberi

Go 1.18 ile birlikte jeneriklerin tanıtılmasıyla birlikte, dil basitlik ve performans temel felsefesinden ödün vermeden eşsiz bir esneklik kazanmıştır. Orta ve ileri düzey geliştiriciler için, HTTP istekleri yapma veya Repository kalıbını uygulama gibi yaygın iskelet kodlarını soyutlamak amacıyla jeneriklerden yararlanmak, artık sadece şık bir teknik değil, ölçeklenebilir ve sürdürülebilir mikro servisler oluşturmak için en iyi uygulamadır.

Bu rehberde, Go'nun tür parametrelerini kullanarak generic bir HTTP istemci yardımcı programı oluşturmanın ve Repository kalıbını uygulamanın yollarını keşfedeceğiz. Bu yaklaşım, sıkı tip güvenliğini korurken projenizdeki kod tekrarını önemli ölçüde azaltır.

Generic Bir HTTP İstemcisi Oluşturma

Geleneksel olarak, Go'da bir HTTP isteği yapmak istemciyi kurmayı, isteği oluşturmayı, bağlamı yönetmeyi ve JSON yanıtını bir yapıya (struct) manuel olarak dönüştürmeyi içerir. Birden fazla uç noktanız (endpoint) varsa, bu mantık genellikle tekrarlanır. Generic bir işlev tanıtarak, ayrıştırma adımını soyutlayabiliriz.

Aşağıdaki uygulamayı ele alalım. DoRequest adında generic bir T türü kabul eden bir işlev tanımlıyoruz. Bu, derleyicinin yanıt gövdesinin beklediğiniz belirli yapıya başarıyla ayrıştırılmasını sağlar.

package client

import (
	"encoding/json"
	"io"
	"net/http"
)

// DoRequest, bir HTTP GET isteği gerçekleştirir ve JSON yanıtını sağlanan generic T türüne ayrıştırır.
func DoRequest[T any](ctx context.Context, client *http.Client, url string) (*T, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("beklenmeyen durum kodu: %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var result T
	if err := json.Unmarshal(body, &result); err != nil {
		return nil, fmt.Errorf("yanıt ayrıştırılamadı: %w", err)
	}

	return &result, nil
}

Bu tek işlev artık, URL'nin geçerli olması ve HTTP yönteminin GET olması koşuluyla, User yapılarını, Order yapılarını veya JSON'a serileştirilebilen diğer herhangi bir türü almak için kullanılabilir. Tam bir üretim öncesi istemci yeniden denemeleri ve zaman aşımını yönetse de, bu örnek generic tür kısıtlamalarının gücünü göstermektedir.

Generic Repository Kalıbı

Repository kalıbı, alan (domain) ve veri eşleme katmanları arasında bir aracı görevi görür ve alan nesnelerine erişim için koleksiyon benzeri bir arayüz sağlar. Go'da sınıflarımız olmasa da, arayüzleri ve jenerikleri kullanarak son derece yeniden kullanılabilir bir repository soyutlaması oluşturabiliriz.

Generic bir arayüz tanımlayarak, iş mantığımızı uygulayan herhangi bir yapının belirli CRUD (Oluştur, Oku, Güncelle, Sil) işlemlerine bağlı kalmasını zorunlu kılabiliriz. Bu, farklı veritabanları (örn. SQL vs. NoSQL) veya testlerde hizmetleri taklit ederken (mocking) özellikle yararlıdır.

package repository

import "context"

// Repository, CRUD işlemleri için generic bir arayüz tanımlar.
type Repository[T any, ID comparable] interface {
	Get(ctx context.Context, id ID) (*T, error)
	Create(ctx context.Context, item *T) error
	Delete(ctx context.Context, id ID) error
}

// UserService, kullanıcılar için iş mantığını yönetir.
type UserService struct {
	repo Repository[User, int]
}

func NewUserService(repo Repository[User, int]) *UserService {
	return &UserService{repo: repo}
}

func (s *UserService) GetUserProfile(ctx context.Context, id int) (*User, error) {
	// Veritabanından çekmeden önce burada iş mantığı uygulanabilir
	user, err := s.repo.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	
	// Örnek: Kullanıcı verilerini zenginleştir
	user.Status = "active"
	return user, nil
}

Pratik Faydalar ve Dikkat Edilmesi Gerekenler

HTTP istemcisi ve Repository kalıpları için jenerikler kullanmak üç ana avantaj sunar:

  1. Tip Güvenliği: Go derleyicisi, uyumsuz türleri çalışma zamanı yerine derleme zamanında yakalar; bu da bir JSON yanıtının yanlış yapıya ayrıştırıldığı ince hataları önler.
  2. Kod Tekrarını Azaltma: Tekrarlayan serileştirme ve hata işleme kodlarını ortadan kaldırmak, kod tabanınızı daha okunabilir ve sürdürülebilir hale getirir.
  3. Test Edilebilirlik: Generic arayüzler, bağımlılıkları kolayca taklit etmenizi sağlar. Örneğin, gerçek bir veritabanına veya API'ye erişmeden UserService'i test etmek için önceden tanımlanmış veri döndüren bir MockRepository[T] oluşturabilirsiniz.

Bununla birlikte, geliştiriciler aşırı mühendislik yapmamaya dikkat etmelidir. Her işlev generic olmak zorunda değildir. Tür parametreleştirme kalıbının açıkça görüldüğü yerlerde jenerikler kullanın. Basit betikler veya tek seferlik yardımcı işlevler için standart somut türler genellikle daha okunabilir ve performanslıdır.

Sonuç

Go'daki jenerikler, daha önce uygulamak zor veya uzun sürmesi gereken daha sofistike tasarım kalıplarının kapısını aralamıştır. Generic bir HTTP istemcisini Repository kalıbıyla birleştirerek, hem esnek hem de sağlam arka uç sistemleri oluşturabilirsiniz. Mevcut Go projelerinizi yeniden düzenlerken, tekrarlayan türye özgü mantığı generic soyutlamalara ayırmak için fırsatları arayın. Daha temiz, daha sürdürülebilir bir kod tabanı için gelecekteki siz ve ekibiniz size minnettar olacaktır.

Share: