Takip et

GraphQL Nedir ve Dosya Yüklemede Neden Tercih Edilmeli?

Go ile GraphQL API: Dosya Yükleme İşlemlerini Spaces’a Nasıl Taşırsınız?

Modern web uygulamaları, kullanıcıların dosya yüklemesine sıkça ihtiyaç duyar. Fotoğraflar, videolar, belgeler veya diğer medya türleri olsun, bu dosyaların güvenli, ölçeklenebilir ve erişilebilir bir şekilde depolanması kritik öneme sahiptir. Geleneksel dosya yükleme yöntemleri karmaşık olabilirken, Go dilinin performansı ve GraphQL’in esnekliği bir araya geldiğinde, bu süreç oldukça basitleşebilir. Bu makale, Go ve GraphQL kullanarak dosyaları S3 uyumlu Object Storage (Spaces gibi) hizmetlerine yüklemek için güçlü bir API nasıl oluşturulacağını adım adım açıklayacaktır.

GraphQL Nedir ve Dosya Yüklemede Neden Tercih Edilmeli?

GraphQL, API’ler için bir sorgu dili ve çalışma zamanıdır. Facebook tarafından geliştirilen ve açık kaynak hale getirilen GraphQL, istemcilerin tam olarak ihtiyaç duydukları veriyi tek bir istekte almalarını sağlar. RESTful API’lerin aksine, GraphQL’de istemci, hangi veriye ihtiyacı olduğunu belirten bir sorgu gönderir ve sunucu yalnızca o veriyi döndürür. Bu, “aşırı veri çekme” (over-fetching) veya “eksik veri çekme” (under-fetching) sorunlarını ortadan kaldırır ve ağ trafiğini optimize eder. Peki, dosya yükleme gibi ikili (binary) veri işlemlerinde GraphQL neden tercih edilmeli?

Geleneksel REST API’lerde dosya yükleme genellikle ayrı bir endpoint (örn. POST /upload) üzerinden multipart/form-data formatında gerçekleştirilir. GraphQL de benzer bir mekanizmayı destekler ancak bunu kendi şema tanımı içine entegre eder. GraphQL’in en büyük avantajlarından biri, API’nizin tüm yeteneklerini tek bir şema altında tanımlayabilmenizdir. Dosya yükleme, diğer tüm veri işlemleri (sorgular, mutasyonlar) gibi bir mutasyon olarak tanımlanır ve bu, API’nizin tutarlılığını artırır. Ayrıca, GraphQL istemcileri, dosya yükleme mutasyonuna ek olarak, yüklenen dosyanın metadata’sını (URL, boyut, tür gibi) hemen geri alabilirler. Bu, istemci tarafında ek bir istek yapma ihtiyacını ortadan kaldırır ve geliştirme sürecini hızlandırır. Örneğin, bir kullanıcı profil fotoğrafını yüklediğinde, sunucu fotoğrafı depolayıp, hemen ardından güncellenmiş profil bilgilerini ve fotoğrafın yeni URL’sini tek bir GraphQL yanıtında döndürebilir. Bu entegrasyon, kullanıcı deneyimini iyileştirirken, geliştiricilerin de daha az kod yazmasını sağlar. Özellikle mikroservis mimarilerinde veya karmaşık veri modellerine sahip uygulamalarda, GraphQL’in sunduğu bu esneklik, dosya yükleme işlemlerini yönetmeyi çok daha basit ve öngörülebilir hale getirir. Bu yaklaşım, API’nizin zamanla evrim geçirmesini de kolaylaştırır, zira yeni dosya türleri veya yükleme gereksinimleri ortaya çıktığında, şema üzerinde yapılacak küçük değişikliklerle mevcut yapıya kolayca adapte olunabilir.

Go Neden Bu Mimari İçin İdeal Bir Seçimdir?

Go (Golang), Google tarafından geliştirilen, derlenmiş, statik olarak yazılmış bir programlama dilidir. Performansı, eşzamanlılık yetenekleri ve basit sözdizimi sayesinde modern, yüksek performanslı backend servisleri geliştirmek için popüler bir tercihtir. Dosya yükleme gibi I/O yoğun işlemler için Go’nun sunduğu avantajlar saymakla bitmez:

  • Eşzamanlılık (Concurrency): Go’nun goroutine’leri ve kanalları, birden fazla dosya yükleme isteğini aynı anda ve verimli bir şekilde işlemenize olanak tanır. Her bir yükleme isteği için hafif bir goroutine başlatılabilir, bu da sunucunun diğer işlemleri engellemeden çalışmaya devam etmesini sağlar. Bu, özellikle yüksek trafikli uygulamalarda yanıt sürelerini önemli ölçüde iyileştirir.
  • Performans: Go, derlenmiş bir dil olduğu için çalışma zamanı performansı oldukça yüksektir. Düşük bellek kullanımı ve hızlı işlem yeteneği, büyük dosyaların işlenmesi ve ağ üzerinden aktarılması sırasında kritik bir avantaj sağlar. Bu, sunucu kaynaklarının daha verimli kullanılmasına ve maliyetlerin düşürülmesine yardımcı olur.
  • Basitlik ve Okunabilirlik: Go’nun temiz ve minimalist sözdizimi, kodun yazılmasını ve bakımını kolaylaştırır. Bu, özellikle büyük ekiplerde veya uzun vadeli projelerde geliştirme hızını artırır ve hataları azaltır.
  • Kapsamlı Standart Kütüphane: Go, ağ işlemleri, dosya sistemi etkileşimleri ve kriptografi gibi birçok yaygın görevi destekleyen zengin bir standart kütüphaneye sahiptir. Bu, harici bağımlılıklara olan ihtiyacı azaltır ve geliştirme sürecini basitleştirir.
  • Güçlü Ekosistem: GraphQL için 99designs/gqlgen ve S3 uyumlu depolama hizmetleriyle entegrasyon için AWS SDK for Go gibi olgun ve iyi desteklenen kütüphaneler mevcuttur. Bu kütüphaneler, geliştiricilerin karmaşık işlevleri hızlı bir şekilde uygulamasına olanak tanır.

Bu özellikler, Go’yu dosya yükleme ve genel API geliştirme için sağlam ve güvenilir bir seçim haline getirir. Go’nun derlenmiş yapısı sayesinde, uygulamanız tek bir ikili dosya olarak dağıtılabilir, bu da dağıtım süreçlerini basitleştirir. Ayrıca, statik tipleme, derleme zamanında birçok hatanın yakalanmasına yardımcı olur, bu da çalışma zamanı hatalarını azaltır ve uygulamanın kararlılığını artırır. Sonuç olarak, Go ile geliştirilen bir GraphQL API, hem performans hem de sürdürülebilirlik açısından beklentileri karşılayacaktır.

Object Storage (Spaces) Nedir ve Nasıl Çalışır?

Object Storage (Nesne Depolama), verileri nesneler olarak depolayan, ölçeklenebilir ve dayanıklı bir depolama mimarisidir. Geleneksel dosya sistemlerinin (örneğin, sunucudaki bir dizin) veya blok depolamanın (veritabanı diskleri) aksine, nesne depolama, verileri hiyerarşik bir yapı yerine düz (flat) bir adresleme şemasıyla yönetir. Her nesne, verinin kendisi, benzersiz bir tanımlayıcı (anahtar) ve isteğe bağlı metadata (veri hakkında bilgi) içerir. DigitalOcean Spaces, Amazon S3, Google Cloud Storage ve Azure Blob Storage gibi hizmetler, bu teknolojinin popüler örnekleridir.

Spaces gibi S3 uyumlu bir Object Storage hizmeti, özellikle web uygulamalarında dosya yükleme için idealdir çünkü:

  • Ölçeklenebilirlik: Milyonlarca hatta milyarlarca nesneyi depolayabilir ve depolama alanınızı gerektiğinde otomatik olarak genişletebilirsiniz. Bu, uygulamanızın büyümesiyle birlikte depolama altyapınızı endişelenmeden ölçekleyebileceğiniz anlamına gelir.
  • Dayanıklılık ve Güvenilirlik: Nesneler genellikle birden fazla fiziksel konumda çoğaltılır, bu da donanım arızalarına karşı yüksek dayanıklılık sağlar. Verilerinizin kaybolma riski oldukça düşüktür.
  • Maliyet Etkinliği: Kullanım başına ödeme modeli sayesinde, yalnızca depoladığınız veri miktarı ve yaptığınız istekler için ödeme yaparsınız. Bu, özellikle büyük miktarda veri depolayan ancak nadiren erişilen durumlar için çok ekonomiktir.
  • Erişilebilirlik: Nesneler HTTP/HTTPS üzerinden URL’ler aracılığıyla erişilebilir. Bu, doğrudan web tarayıcılarından veya diğer uygulamalardan dosyalara kolayca erişilmesini sağlar. Ayrıca, çoğu Object Storage hizmeti, CDN (İçerik Dağıtım Ağı) entegrasyonunu kolaylaştırarak içeriğin dünya çapında hızlı bir şekilde dağıtılmasına yardımcı olur.
  • Güvenlik: Detaylı erişim kontrol politikaları (ACL’ler, bucket politikaları, IAM rolleri) ile hangi kullanıcıların veya uygulamaların hangi nesnelere erişebileceğini hassas bir şekilde yönetebilirsiniz.

Bir dosya Spaces’a yüklendiğinde, aslında bir “bucket” (kova) adı verilen bir konteynere bir “nesne” olarak kaydedilir. Her nesnenin benzersiz bir anahtarı (genellikle dosya adı) ve isteğe bağlı olarak ek metadata’sı bulunur. Örneğin, bir resim dosyası yüklendiğinde, anahtar “profil-resmi-123.jpg” olabilir ve metadata “Content-Type: image/jpeg” içerebilir. Bu yapı, Go ile GraphQL API’mizin dosyaları depolama, yönetme ve erişme şeklini doğrudan etkileyecektir. Dolayısıyla, bu hizmetleri anlamak, API’mizi doğru bir şekilde tasarlamak için temel bir adımdır. Nesne depolamanın bu esnek yapısı, sadece dosya yükleme için değil, aynı zamanda statik web sitelerinin barındırılması, yedekleme ve arşivleme gibi birçok farklı senaryo için de tercih edilmesini sağlar. Bu sayede, uygulamanızın depolama gereksinimlerini geleceğe yönelik olarak da karşılayabilirsiniz.

Go Projesini Başlatma ve Temel Bağımlılıkları Kurma

Go ile GraphQL API’mizi oluşturmaya başlamak için öncelikle yeni bir Go modülü başlatmamız ve gerekli bağımlılıkları eklememiz gerekiyor. Bu bölüm, projenin temel iskeletini oluşturma adımlarını içermektedir.

Go Modülünü Başlatma ve Bağımlılıkları Ekleme

İlk olarak, projeniz için bir dizin oluşturun ve bu dizine gidin:


mkdir go-graphql-upload
cd go-graphql-upload

Şimdi, Go modülünü başlatın. Bu komut, projenizin bağımlılıklarını yönetecek olan go.mod dosyasını oluşturacaktır:


go mod init github.com/kullaniciadi/go-graphql-upload

github.com/kullaniciadi/go-graphql-upload yerine kendi GitHub kullanıcı adınızı veya tercih ettiğiniz modül yolunu kullanabilirsiniz.

Ardından, GraphQL sunucusu için gqlgen kütüphanesini ve S3 uyumlu Object Storage (Spaces) ile etkileşim kurmak için AWS SDK for Go’yu (özellikle S3 ve S3 Manager paketlerini) eklememiz gerekiyor. gqlgen, GraphQL şemanızdan Go kodunu otomatik olarak oluşturan güçlü bir araçtır.


go get github.com/99designs/gqlgen
go get github.com/aws/aws-sdk-go/aws
go get github.com/aws/aws-sdk-go/aws/session
go get github.com/aws/aws-sdk-go/service/s3
go get github.com/aws/aws-sdk-go/service/s3/s3manager
go get github.com/joho/godotenv # Ortam değişkenlerini yönetmek için (opsiyonel ama önerilir)

Bu komutlar, belirtilen kütüphaneleri projenize indirir ve go.mod dosyanıza ekler. godotenv, yerel geliştirme ortamınızda API anahtarları gibi hassas bilgileri .env dosyasından yüklemek için kullanışlıdır.

gqlgen ile Temel Yapılandırma

gqlgen, projenizin GraphQL şemasını temel alarak birçok boilerplate kodunu sizin için oluşturur. Bu, geliştirme sürecini hızlandırır ve hataları azaltır. Temel yapılandırmayı oluşturmak için şu komutu çalıştırın:


go run github.com/99designs/gqlgen init

Bu komut, projenizin kök dizininde aşağıdaki dosyaları oluşturacaktır:

  • gqlgen.yml: gqlgen yapılandırma dosyası. Giriş şemanızı, model bağlamalarını ve diğer ayarları tanımlar.
  • graph/schema.graphqls: GraphQL şemanızın tanımlanacağı dosya. Başlangıçta örnek sorgu ve mutasyonları içerecektir.
  • graph/resolver.go: GraphQL resolver fonksiyonlarınızın yer alacağı dosya. Burası iş mantığınızı yazacağınız yerdir.
  • graph/generated.go: gqlgen tarafından şemanızdan otomatik olarak oluşturulan kod. Bu dosyayı elle düzenlememelisiniz.
  • server.go: GraphQL sunucunuzu başlatan ana dosya.

Bu adımlarla, GraphQL API’nizi geliştirmeye başlamak için sağlam bir temel oluşturmuş olursunuz. gqlgen‘in otomatik kod oluşturma yeteneği, özellikle büyük ve karmaşık GraphQL şemalarıyla çalışırken zaman kazandırır. Artık schema.graphqls dosyamızı dosya yükleme için gerekli mutasyonları içerecek şekilde düzenleyebiliriz.

GraphQL Şemasını Tanımlama: Dosya Yükleme Mutasyonu Nasıl Oluşturulur?

GraphQL şeması, API’nizin sunduğu tüm veri türlerini, sorguları (query) ve mutasyonları (mutation) tanımlayan merkezi bir sözleşmedir. Dosya yükleme işlevini API’mize eklemek için özel bir mutasyon tanımlamamız ve Upload scalar türünü kullanmamız gerekecek. Bu bölüm, schema.graphqls dosyasını nasıl düzenleyeceğimizi adım adım gösterecektir.

schema.graphqls Dosyasının Düzenlenmesi

gqlgen init komutuyla oluşturulan graph/schema.graphqls dosyasını açın. İçeriği muhtemelen şöyle görünecektir:


# GraphQL schema example
#
# https://gqlgen.com/getting-started/

type Todo {
  id: ID!
  text: String!
  done: Boolean!
}

type Query {
  todos: [Todo!]!
}

type Mutation {
  createTodo(text: String!): Todo!
}

Bu örnek şemayı dosya yükleme mutasyonumuzu içerecek şekilde değiştirmemiz gerekiyor. Öncelikle, GraphQL’in dosya yükleme için özel olarak tasarlanmış Upload scalar türünü tanımlamalıyız. Bu scalar, multipart/form-data isteğindeki dosya bölümünü temsil eder. Ardından, bir dosya yükleme mutasyonu ve yüklenen dosya hakkında bilgi döndürecek bir File türü oluşturacağız.

graph/schema.graphqls dosyanızı aşağıdaki gibi güncelleyin:


# Dosya yüklemeleri için özel scalar türü
scalar Upload

# Yüklenen dosya hakkında bilgi döndürecek tip
type File {
  id: ID!
  filename: String!
  mimetype: String!
  encoding: String!
  url: String! # Dosyanın depolandığı URL
  size: Int! # Dosya boyutu (byte cinsinden)
}

# Sorgular (Queries) - Şimdilik boş bırakabiliriz veya ekleyebiliriz
type Query {
  hello: String!
}

# Mutasyonlar (Mutations) - Dosya yükleme işlevimizi buraya ekleyeceğiz
type Mutation {
  # Tek bir dosya yüklemek için mutasyon
  uploadFile(file: Upload!): File!
  # Birden fazla dosya yüklemek için (isteğe bağlı)
  uploadMultipleFiles(files: [Upload!]!): [File!]!
}

Bu şema tanımında:

  • scalar Upload: GraphQL’e dosya yükleme yeteneği kazandırır. gqlgen bu scalar’ı otomatik olarak işleyecektir.
  • type File: Yüklenen bir dosya hakkında istemciye geri döndürülecek bilgileri tanımlar. id, filename, mimetype, encoding, url ve size gibi alanlar içerir. url alanı, dosyanın Object Storage’da depolandığı genel erişilebilir URL’yi tutacaktır.
  • type Mutation: API’mizin veri üzerinde değişiklik yapma yeteneğini tanımlar.
    • uploadFile(file: Upload!): File!: Tek bir dosya yüklemek için bir mutasyon. file argümanı zorunlu bir Upload türündedir ve mutasyon başarılı olursa bir File nesnesi döndürür.
    • uploadMultipleFiles(files: [Upload!]!): [File!]!: İsteğe bağlı olarak, birden fazla dosyayı aynı anda yüklemek için bir mutasyon. files argümanı bir Upload dizisi alır ve bir File dizisi döndürür.

Şema Değişikliklerini Uygulama

schema.graphqls dosyasını güncelledikten sonra, gqlgen‘in bu değişiklikleri Go koduna yansıtması için yeniden çalıştırmamız gerekir:


go run github.com/99designs/gqlgen generate

Bu komut, graph/generated.go dosyasını ve graph/models_gen.go dosyasını güncelleyecektir. models_gen.go dosyası, şemada tanımladığımız File türü için Go yapılarını (struct) içerecektir. Örneğin, File türü için şöyle bir yapı oluşturulacaktır:


// models_gen.go içinde oluşacak yapıya benzer
type File struct {
	ID       string json:"id"
	Filename string json:"filename"
	Mimetype string json:"mimetype"
	Encoding string json:"encoding"
	URL      string json:"url"
	Size     int    json:"size"
}

Artık GraphQL şemamız dosya yüklemeyi destekleyecek şekilde hazır. Bir sonraki adım, bu mutasyon için Go tarafında resolver fonksiyonlarını ve dosya işleme mantığını geliştirmektir. Bu, sunucumuzun gelen dosyaları nasıl alacağını, işleyeceğini ve Object Storage’a nasıl göndereceğini belirleyeceğimiz yerdir.

Go Sunucusu ve Resolver Fonksiyonlarını Geliştirme: Dosya İşleme Mantığı

Şemamızı tanımladıktan ve gqlgen ile gerekli Go kodlarını oluşturduktan sonra, sıra dosya yükleme mutasyonumuz için gerçek iş mantığını yazmaya geldi. Bu, resolver.go dosyasında gerçekleşecek ve gelen dosyayı işleyip Object Storage’a gönderme adımlarını içerecektir. Ayrıca, GraphQL sunucumuzu başlatan server.go dosyasında bazı ayarlamalar yapacağız.

server.go Dosyasının Yapılandırılması

gqlgen init komutuyla oluşturulan server.go dosyası, temel bir GraphQL sunucusu başlatır. Dosya yükleme desteği için, gqlgen‘in Upload scalar’ını doğru şekilde işlemesi için bazı middleware’lara ihtiyacımız olabilir. Ancak gqlgen‘in kendisi, multipart/form-data isteklerini ve Upload scalar’ını yerleşik olarak destekler, bu nedenle genellikle ek bir middleware’a gerek kalmaz.

server.go dosyanızın genel yapısı şöyle olmalıdır:


package main

import (
	"log"
	"net/http"
	"os"

	"github.com/99designs/gqlgen/graphql/handler"
	"github.com/99designs/gqlgen/graphql/playground"
	"github.com/kullaniciadi/go-graphql-upload/graph"
	"github.com/kullaniciadi/go-graphql-upload/graph/generated"
	"github.com/joho/godotenv" // Ortam değişkenleri için
)

const defaultPort = "8080"

func main() {
	// .env dosyasından ortam değişkenlerini yükle
	err := godotenv.Load()
	if err != nil {
		log.Println("Error loading .env file, assuming environment variables are set")
	}

	port := os.Getenv("PORT")
	if port == "" {
		port = defaultPort
	}

	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))

	http.Handle("/", playground.Handler("GraphQL playground", "/query"))
	http.Handle("/query", srv)

	log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

Burada önemli olan, generated.Config{Resolvers: &graph.Resolver{}} kısmıdır. Bu, graph/resolver.go dosyasında tanımlayacağımız resolver’larımızın sunucuya bağlanmasını sağlar.

resolver.go Dosyasında Dosya Yükleme Mantığı

graph/resolver.go dosyası, GraphQL şemamızdaki her alan için gerçek iş mantığını içerir. gqlgen generate komutunu çalıştırdıktan sonra, bu dosyada UploadFile ve UploadMultipleFiles mutasyonları için placeholder fonksiyonlar göreceksiniz. Şimdi bu fonksiyonları, gelen dosyayı işleyecek ve Object Storage’a yükleyecek şekilde dolduralım.

Öncelikle, Resolver yapımıza Object Storage istemcisini eklememiz gerekiyor. Bu, S3 uyumlu bir servise (DigitalOcean Spaces gibi) bağlanmak için kullanılacak.


package graph

import (
	"context"
	"fmt"
	"io"
	"log"
	"mime"
	"path/filepath"
	"os"

	"github.com/99designs/gqlgen/graphql"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"
	"github.com/google/uuid" // Benzersiz ID'ler için
	"github.com/kullaniciadi/go-graphql-upload/graph/generated"
	"github.com/kullaniciadi/go-graphql-upload/graph/model"
)

// This file will not be regenerated automatically.
//
// It serves as dependency injection for your app, add any dependencies you require here.

type Resolver struct {
	Uploader *s3manager.Uploader
	BucketName string
	Region string
	Endpoint string
}

// Resolver yapımızın başlangıçta nasıl oluşturulacağını gösteren örnek.
// main.go'da çağrılacak.
func NewResolver() *Resolver {
	// AWS SDK oturumunu başlat
	sess := session.Must(session.NewSession(&aws.Config{
		Region:   aws.String(os.Getenv("AWS_REGION")),
		Endpoint: aws.String(os.Getenv("AWS_ENDPOINT")), // DigitalOcean Spaces için gerekli
	}))

	// S3 Uploader oluştur
	uploader := s3manager.NewUploader(sess)

	return &Resolver{
		Uploader: uploader,
		BucketName: os.Getenv("S3_BUCKET_NAME"),
		Region: os.Getenv("AWS_REGION"),
		Endpoint: os.Getenv("AWS_ENDPOINT"),
	}
}

// Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }

// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }

type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }

func (r *queryResolver) Hello(ctx context.Context) (string, error) {
	return "Merhaba, GraphQL Dosya Yükleme API'sine hoş geldiniz!", nil
}

func (r *mutationResolver) UploadFile(ctx context.Context, file graphql.Upload) (*model.File, error) {
	log.Printf("Yükleme isteği alındı: %s, %s, %d bytes", file.Filename, file.ContentType, file.Size)

	// Benzersiz bir dosya adı oluştur
	fileExt := filepath.Ext(file.Filename)
	uniqueFilename := fmt.Sprintf("%s%s", uuid.New().String(), fileExt)
	objectKey := "uploads/" + uniqueFilename // Spaces içinde bir klasöre kaydet

	// Dosyayı Spaces'a yükle
	uploadInput := &s3manager.UploadInput{
		Bucket: aws.String(r.BucketName),
		Key:    aws.String(objectKey),
		Body:   file.File, // graphql.Upload'ın io.Reader'ı
		ContentType: aws.String(file.ContentType),
	}

	result, err := r.Uploader.Upload(uploadInput)
	if err != nil {
		log.Printf("Dosya yüklenirken hata oluştu: %v", err)
		return nil, fmt.Errorf("dosya yüklenemedi: %w", err)
	}

	log.Printf("Dosya başarıyla yüklendi: %s", *result.Location)

	// Yüklenen dosya bilgisini döndür
	return &model.File{
		ID:       uuid.New().String(), // Veya Spaces'tan gelen bir ID kullanabilirsiniz
		Filename: file.Filename,
		Mimetype: file.ContentType,
		Encoding: "binary", // GraphQL'in 'Upload' scalar'ı genellikle binary'dir
		URL:      *result.Location,
		Size:     int(file.Size),
	}, nil
}

func (r *mutationResolver) UploadMultipleFiles(ctx context.Context, files []*graphql.Upload) ([]*model.File, error) {
	var uploadedFiles []*model.File
	for _, file := range files {
		uploadedFile, err := r.UploadFile(ctx, *file) // Tek dosya yükleme fonksiyonunu tekrar kullan
		if err != nil {
			// Hata durumunda diğer dosyaların yüklemesini durdurmak isteyebilirsiniz
			// veya hataları toplayıp tümünü döndürebilirsiniz.
			log.Printf("Birden fazla dosya yüklenirken hata oluştu: %v", err)
			return nil, fmt.Errorf("dosyalardan biri yüklenemedi: %w", err)
		}
		uploadedFiles = append(uploadedFiles, uploadedFile)
	}
	return uploadedFiles, nil
}

main.go dosyasını da NewResolver() fonksiyonunu çağıracak şekilde güncellemeniz gerekiyor:


// main.go dosyasında değişiklik
// ...
func main() {
	// ... (godotenv.Load ve port ayarları) ...

	// Resolver'ı başlat ve S3 Uploader'ı enjekte et
	resolver := graph.NewResolver()

	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: resolver}))

	// ... (http.Handle ve log ayarları) ...
}

Bu kodda:

  • Resolver yapısına Uploader, BucketName, Region ve Endpoint eklendi. Bu, resolver fonksiyonlarının Object Storage ile etkileşime geçmesini sağlar.
  • NewResolver() fonksiyonu, AWS SDK oturumunu ve s3manager.Uploader‘ı başlatır. Bu fonksiyon, main.go içinde çağrılarak resolver’a bağımlılık enjeksiyonu sağlar. Ortam değişkenlerinden (AWS_REGION, AWS_ENDPOINT, S3_BUCKET_NAME) gerekli bilgileri alır.
  • UploadFile mutasyonunda:
    • graphql.Upload türünden gelen dosya (file.File), io.Reader arayüzünü uygular, bu da onu doğrudan S3 uploader’ına beslememizi sağlar.
    • uuid.New().String() ile benzersiz bir dosya adı oluşturulur ve dosyanın orijinal uzantısı korunur. Bu, adlandırma çakışmalarını önler.
    • s3manager.Uploader kullanılarak dosya Object Storage’a yüklenir. UploadInput nesnesine bucket adı, anahtar (dosya yolu), dosya içeriği ve içerik türü bilgileri eklenir.
    • Yükleme başarılı olursa, Object Storage’dan dönen konum (URL) ve diğer dosya bilgileri ile bir model.File nesnesi oluşturulur ve istemciye döndürülür.
  • UploadMultipleFiles mutasyonu, tek dosya yükleme fonksiyonunu her bir dosya için tekrar kullanarak birden fazla dosyanın yüklenmesini sağlar.

Bu sayede, GraphQL API’miz artık gelen dosya yükleme isteklerini işleyebilir, dosyaları benzersiz bir şekilde adlandırabilir ve güvenli bir şekilde Object Storage’a depolayabilir. Sonraki adım, bu entegrasyonu detaylandırmak ve Object Storage servisleriyle nasıl daha verimli çalışabileceğimizi görmektir.

Spaces (S3 Uyumlu Object Storage) ile Entegrasyon: Dosyaları Yükleme

Önceki bölümde, Go resolver’ımızı kullanarak dosya yükleme mantığını taslağını oluşturduk. Şimdi bu mantığı S3 uyumlu bir Object Storage hizmeti olan DigitalOcean Spaces ile tam olarak entegre edelim. AWS SDK for Go, S3 API’si ile uyumlu tüm hizmetlerle çalışabildiği için, Spaces ile etkileşim kurmak oldukça kolaydır. Temel olarak, SDK’yı Spaces’ın endpoint’ini gösterecek şekilde yapılandırmamız yeterlidir.

AWS SDK’sını Spaces İçin Yapılandırma

AWS SDK’sını DigitalOcean Spaces gibi S3 uyumlu bir hizmetle kullanmak için, oturum yapılandırmasında Endpoint parametresini belirtmeniz gerekir. Ayrıca, Spaces erişim anahtarlarınızı (Access Key ve Secret Key) ortam değişkenleri aracılığıyla sağlamanız önemlidir.

.env dosyanızda aşağıdaki değişkenleri tanımladığınızdan emin olun:


# .env
AWS_REGION=fra1 # DigitalOcean Spaces bölgeniz (örn: nyc3, fra1)
AWS_ENDPOINT=https://fra1.digitaloceanspaces.com # Spaces endpoint URL'si
S3_BUCKET_NAME=your-space-name # Oluşturduğunuz Spaces'ın adı
AWS_ACCESS_KEY_ID=YOUR_DO_SPACES_ACCESS_KEY # DigitalOcean Spaces Erişim Anahtarınız
AWS_SECRET_ACCESS_KEY=YOUR_DO_SPACES_SECRET_KEY # DigitalOcean Spaces Gizli Anahtarınız

graph/resolver.go dosyasındaki NewResolver() fonksiyonu, bu ortam değişkenlerini kullanarak bir AWS SDK oturumu başlatacaktır:


// graph/resolver.go içerisindeki NewResolver fonksiyonu
func NewResolver() *Resolver {
	// AWS SDK oturumunu başlat
	sess := session.Must(session.NewSession(&aws.Config{
		Region:   aws.String(os.Getenv("AWS_REGION")),
		Endpoint: aws.String(os.Getenv("AWS_ENDPOINT")), // Spaces için önemli!
		// Kimlik bilgileri varsayılan olarak ortam değişkenlerinden (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) alınır.
	}))

	// S3 Uploader oluştur
	uploader := s3manager.NewUploader(sess)

	return &Resolver{
		Uploader: uploader,
		BucketName: os.Getenv("S3_BUCKET_NAME"),
		Region: os.Getenv("AWS_REGION"),
		Endpoint: os.Getenv("AWS_ENDPOINT"),
	}
}

Bu yapılandırma ile uploader nesnesi artık DigitalOcean Spaces’a dosya yüklemek için hazır hale gelmiştir. Kimlik bilgileri (Access Key ID ve Secret Access Key) varsayılan olarak ortam değişkenlerinden (AWS_ACCESS_KEY_ID ve AWS_SECRET_ACCESS_KEY) alınır, bu da kod içinde hassas bilgileri saklamaktan kaçınmamızı sağlar.

Dosyaları Spaces’a Yükleme Mekanizması

UploadFile resolver fonksiyonumuzun kalbi, s3manager.Uploader‘ın Upload metodunu kullanmaktır. Bu metod, büyük dosyaları otomatik olarak parçalara ayırıp çok parçalı yükleme (multipart upload) yaparak performansı ve güvenilirliği artırır.


// graph/resolver.go içerisindeki UploadFile fonksiyonundan ilgili kısım
func (r *mutationResolver) UploadFile(ctx context.Context, file graphql.Upload) (*model.File, error) {
	// ... (Benzersiz dosya adı oluşturma) ...
	objectKey := "uploads/" + uniqueFilename // Spaces içinde bir 'uploads' klasörüne kaydetmek için

	// Dosyayı Spaces'a yükle
	uploadInput := &s3manager.UploadInput{
		Bucket: aws.String(r.BucketName),
		Key:    aws.String(objectKey),
		Body:   file.File, // graphql.Upload'ın io.Reader'ı
		ContentType: aws.String(file.ContentType),
		ACL:    aws.String("public-read"), // Dosyanın genel erişilebilir olmasını sağlar
	}

	result, err := r.Uploader.Upload(uploadInput)
	if err != nil {
		log.Printf("Dosya yüklenirken hata oluştu: %v", err)
		return nil, fmt.Errorf("dosya yüklenemedi: %w", err)
	}

	log.Printf("Dosya başarıyla yüklendi: %s", *result.Location)

	// Yüklenen dosya bilgisini döndür
	return &model.File{
		ID:       uuid.New().String(),
		Filename: file.Filename,
		Mimetype: file.ContentType,
		Encoding: "binary",
		URL:      *result.Location, // Yüklenen dosyanın URL'si
		Size:     int(file.Size),
	}, nil
}

Burada eklediğimiz önemli bir detay, ACL: aws.String("public-read") satırıdır. Bu, yüklenen dosyanın herkes tarafından okunabilir olmasını sağlar. Eğer dosyaların genel erişilebilir olmasını istemiyorsanız, bu satırı kaldırabilir ve erişimi Spaces bucket politikaları veya ön imzalı URL’ler (presigned URLs) aracılığıyla yönetebilirsiniz. result.Location, yüklenen dosyanın tam URL’sini içerir, bu da istemciye dosyanın nerede bulunduğunu bildirmek için mükemmeldir. Bu entegrasyon adımları sayesinde, Go GraphQL API’niz artık DigitalOcean Spaces gibi S3 uyumlu herhangi bir Object Storage hizmetine güvenilir ve verimli bir şekilde dosya yükleyebilir. Bu, uygulamanızın depolama ihtiyaçları için sağlam bir temel oluşturur ve gelecekteki ölçeklenebilirlik gereksinimlerini karşılamanıza yardımcı olur. Artık API’miz, dosyaları alıp bulut depolamaya güvenle aktarabilir bir durumda.

İleri Düzey Konular: Büyük Dosya Yüklemeleri ve Güvenlik İpuçları

Dosya yükleme API’leri oluştururken, büyük dosyaların yönetimi ve güvenlik en önemli konulardan ikisidir. Bu bölümde, bu zorlukların üstesinden gelmek için bazı ileri düzey teknikleri ve en iyi uygulamaları inceleyeceğiz.

Büyük Dosya Yüklemeleri İçin Optimizasyonlar

Küçük resimler veya belgeler için doğrudan yükleme yöntemi yeterli olsa da, gigabaytlarca büyüklükteki dosyaları yüklerken performansı ve güvenilirliği artırmak için farklı stratejiler uygulamak gerekir.

  1. Ön İmzalı URL’ler (Presigned URLs) ile Doğrudan Yükleme:

    Bu, büyük dosyaları yüklemek için en yaygın ve önerilen yöntemlerden biridir. Mantık şöyledir: İstemci, API’nize bir dosya yüklemek istediğini belirtir (genellikle dosya adı ve türü ile). API’niz, Spaces’tan belirli bir süre geçerli olacak, doğrudan o dosyayı yükleyebileceği bir “ön imzalı URL” (PUT isteği için) veya “ön imzalı form alanı” (POST isteği için) ister. API, bu URL’yi istemciye geri döndürür. İstemci daha sonra dosyayı doğrudan Spaces’a, API’nizin üzerinden geçmeden yükler. Bu yaklaşımın faydaları:

    • Sunucu Yükünü Azaltma: Dosya içeriği API sunucunuz üzerinden akmadığı için, sunucunuzun CPU, bellek ve ağ bant genişliği üzerindeki yükü önemli ölçüde azalır.
    • Daha Hızlı Yüklemeler: İstemci, dosyayı doğrudan Object Storage’a yüklediği için, API sunucunuzun coğrafi konumundan bağımsız olarak daha düşük gecikmeyle daha hızlı yükleme deneyimi yaşayabilir.
    • Gelişmiş Güvenlik: API, yalnızca belirli bir dosya için belirli bir süre geçerli olan imzalı bir URL oluşturur. Bu, yetkisiz erişimi sınırlar.

    Go ile ön imzalı URL oluşturma, AWS SDK’nın s3.PresignRequest veya s3.PutObjectRequest metodları kullanılarak yapılabilir. İstemciye döndürülen URL’yi kullanarak dosya yüklemesi tamamlandıktan sonra, istemci API’nize bir bildirim gönderebilir ve dosyanın Object Storage’a başarıyla yüklendiğini doğrulayabilir.

  2. Çok Parçalı Yükleme (Multipart Upload) ve Chunking:

    s3manager.Uploader zaten bu mekanizmayı dahili olarak kullanır, ancak istemci tarafında da dosyaları parçalara ayırıp paralel olarak yüklemek, özellikle zayıf ağ bağlantılarında veya çok büyük dosyalarda faydalı olabilir. İstemci, dosyayı küçük “parçalara” (chunks) böler ve her bir parçayı ayrı ayrı yükler. API’niz veya doğrudan Object Storage, bu parçaları daha sonra birleştirir. Bu, başarısız olan parçaların yeniden denemesini kolaylaştırır ve yükleme işlemini daha dayanıklı hale getirir.

Güvenlik İpuçları

Dosya yükleme API’lerinde güvenlik her zaman öncelikli olmalıdır. Aşağıdaki ipuçları, uygulamanızın güvenliğini artırmanıza yardımcı olacaktır:

  1. Yetkilendirme ve Kimlik Doğrulama:

    Her dosya yükleme isteğinin yetkili bir kullanıcıdan geldiğinden emin olun. JWT (JSON Web Tokens), OAuth veya API anahtarları gibi mekanizmalar kullanarak kullanıcıların kimliğini doğrulayın ve dosya yükleme mutasyonuna erişimlerini yetkilendirin. Örneğin, sadece oturum açmış kullanıcıların dosya yüklemesine izin verin.

  2. Dosya Tipi ve Boyutu Doğrulama:

    Kullanıcıların yükleyebileceği dosya türlerini (MIME tipleri) ve maksimum dosya boyutunu sınırlayın. Bu, kötü amaçlı dosyaların (örneğin, çalıştırılabilir dosyalar) yüklenmesini önler ve sunucu kaynaklarının aşırı kullanımını engeller. Bu doğrulamayı hem istemci tarafında (kullanıcı deneyimi için) hem de sunucu tarafında (güvenlik için) yapın. Go’da file.ContentType ve file.Size alanlarını kontrol edebilirsiniz.

  3. Benzersiz Dosya Adları ve Yollar:

    Yüklenen dosyalar için benzersiz adlar ve güvenli depolama yolları kullanın (örneğin, uuid.New().String() ile). Bu, dizin geçişi (directory traversal) saldırılarını ve dosya adlandırma çakışmalarını önler. Ayrıca, kullanıcı tarafından sağlanan dosya adlarını doğrudan kullanmaktan kaçının veya bunları güvenli bir şekilde temizleyin.

  4. Erişim Kontrolü (ACLs ve Bucket Politikaları):

    Object Storage’daki dosyalarınız için uygun erişim kontrol listeleri (ACL’ler) veya bucket politikaları belirleyin. Varsayılan olarak tüm dosyaların herkese açık olmasını istemiyorsanız, public-read ACL’sini kullanmaktan kaçının. Bunun yerine, sadece belirli kullanıcıların veya uygulamanızın dosyaya erişmesine izin veren daha kısıtlayıcı politikalar uygulayın.

  5. Virüs Taraması ve Zararlı İçerik Kontrolü:

    Mümkünse, yüklenen dosyaları virüs ve zararlı yazılım taramalarından geçirin. Bu genellikle ayrı bir hizmet veya mikroservis aracılığıyla arka planda yapılır. Dosya taramadan geçtikten sonra, güvenli olduğu onaylanırsa erişime açılabilir.

Bu ileri düzey konular ve güvenlik önlemleri, Go GraphQL dosya yükleme API’nizin sadece işlevsel değil, aynı zamanda sağlam, ölçeklenebilir ve güvenli olmasını sağlayacaktır. Özellikle büyük ölçekli ve hassas veri içeren uygulamalarda bu yaklaşımlar kritik öneme sahiptir.

Sonuç: Go ve GraphQL ile Güçlü Dosya Yükleme API’leri

Bu makale boyunca, Go programlama dilinin gücünü ve GraphQL’in esnekliğini kullanarak S3 uyumlu Object Storage (DigitalOcean Spaces gibi) hizmetlerine dosya yükleyen sağlam bir API’nin nasıl oluşturulacağını adım adım inceledik. GraphQL’in tek bir endpoint üzerinden esnek veri işlemleri sunması, Go’nun eşzamanlılık ve performans yetenekleri ile birleştiğinde, dosya yükleme gibi I/O yoğun görevler için ideal bir çözüm ortaya koydu. Ayrıca, Object Storage’ın ölçeklenebilirlik, dayanıklılık ve maliyet etkinliği avantajlarını da detaylıca ele aldık.

Projenin başlangıcından itibaren, Go modülünü başlatma, gqlgen ile GraphQL şemasını tanımlama, dosya yükleme mutasyonlarını oluşturma ve bu mutasyonlar için Go resolver fonksiyonlarını geliştirme adımlarını takip ettik. Özellikle, AWS SDK for Go’yu kullanarak dosyaları Spaces’a güvenli ve verimli bir şekilde nasıl yükleyeceğimizi gösterdik. İleri düzey konular bölümünde ise, büyük dosya yüklemeleri için ön imzalı URL’ler gibi optimizasyon tekniklerine ve API’nizin güvenliğini sağlamak için kimlik doğrulama, dosya doğrulama ve erişim kontrolü gibi kritik ipuçlarına değindik. Bu bilgiler, uygulamanızın sadece mevcut ihtiyaçlarını karşılamakla kalmayıp, aynı zamanda gelecekteki büyüme ve güvenlik gereksinimlerine de hazır olmasını sağlayacaktır. Sonuç olarak, Go ve GraphQL kombinasyonu, modern, yüksek performanslı ve sürdürülebilir dosya yükleme çözümleri geliştirmek için mükemmel bir araç setidir.

Sıkça Sorulan Sorular

GraphQL ile dosya yüklemek neden REST’ten daha iyi bir seçenek olabilir?
GraphQL, dosya yükleme mutasyonunu diğer tüm API işlemlerinizle tek bir şema altında birleştirmenize olanak tanır. Bu, API tutarlılığını artırır ve istemcilerin yüklenen dosyanın metadata’sını (URL, boyut vb.) aynı istekte almasını sağlar, bu da istemci tarafında ek HTTP isteklerine olan ihtiyacı ortadan kaldırır. Ayrıca, GraphQL’in esnekliği, API’nizin değişen ihtiyaçlara daha kolay adapte olmasını sağlar.
Hangi S3 uyumlu Object Storage servislerini kullanabilirim?
Bu makalede DigitalOcean Spaces’ı örnek olarak kullandık, ancak Amazon S3, Google Cloud Storage (S3 uyumlu mod), Azure Blob Storage, Linode Object Storage, Wasabi ve MinIO (kendi sunucunuzda çalıştırılan) gibi birçok S3 uyumlu Object Storage hizmetini kullanabilirsiniz. AWS SDK for Go, Endpoint yapılandırması ile bu hizmetlerin çoğuyla uyumlu çalışır.
Büyük dosyaları yüklerken performansı nasıl artırabilirim?
Büyük dosyalar için en etkili yöntem, “ön imzalı URL’ler” (Presigned URLs) kullanmaktır. Bu yöntemle istemci, dosyayı doğrudan Object Storage’a yükler ve API sunucunuzun üzerinden geçmez, bu da sunucu yükünü ve gecikmeyi azaltır. Ayrıca, çok parçalı yükleme (multipart upload) ve istemci tarafında dosya parçalama (chunking) teknikleri de büyük dosya yüklemelerinin güvenilirliğini ve hızını artırabilir.
Yüklenen dosyaların güvenliğini nasıl sağlayabilirim?
Güvenlik için birden fazla katmanlı bir yaklaşım benimsemek önemlidir:

  1. Tüm dosya yükleme isteklerinde kullanıcı kimlik doğrulaması ve yetkilendirmesi yapın.
  2. Yüklenen dosya türlerini (MIME tipleri) ve boyutlarını hem istemci hem de sunucu tarafında doğrulayın.
  3. Dosya adları için benzersiz tanımlayıcılar kullanın (UUID gibi) ve kullanıcı tarafından sağlanan adları doğrudan kullanmaktan kaçının.
  4. Object Storage’da uygun erişim kontrol listeleri (ACL’ler) veya bucket politikaları uygulayın.
  5. Mümkünse, yüklenen dosyaları virüs ve kötü amaçlı yazılım taramasından geçirin.
gqlgen yerine başka bir GraphQL kütüphanesi kullanabilir miyim?
Evet, Go için gqlgen dışında graphql-go/graphql gibi başka GraphQL kütüphaneleri de mevcuttur. Ancak gqlgen, şema odaklı yaklaşımı ve otomatik kod oluşturma yetenekleri sayesinde genellikle daha hızlı geliştirme ve daha az boilerplate kodu sunar. Seçiminiz, projenizin özel gereksinimlerine ve ekibinizin tercihlerine bağlı olabilir.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.