Sitemap

Modern API Tasarımı Üzerine

6 min readSep 24, 2025

--

Henüz okul yıllarımda tanıştığım Php ile başlayan backend yolculuğuma GraphQL ve NodeJS öğrenerek devam ediyorum.Bu yolculukta öğrendiğim en önemli derslerden biri, mükemmellik ile pratik kullanılabilirlik arasındaki dengeyi bulmak oldu.

REST mimarisi 2000 yılında tanıtıldığında, web API’leri için devrim niteliğinde bir standart sundu. Ancak gerçek dünya uygulamaları geliştirirken, pure REST prensiplerinin bazen işleri zorlaştırdığını fark ettim.

Bir e-ticaret platformunda sipariş iptal etmek için müşteriye “PATCH ile status field’ını ‘cancelled’ yap” demek yerine, açık ve anlaşılır bir “cancel” action’ı sunmak çok daha mantıklıydı.

Bu yazıda, gerçek bir uygulama senaryosu üzerinden modern API tasarımında nasıl pragmatik kararlar verilebileceğini anlatacağım. Örnek olarak, hepimizin aşina olduğu bir kullanıcı dashboard’unu ele alacağız: profil bilgileri, ödeme kartları, sosyal medya bağlantıları ve siparişler. Bu menü yapısı üzerinden, farklı tasarım yaklaşımlarının artı ve eksilerini, gerçek kod örnekleriyle inceleyeceğiz.

Press enter or click to view image in full size

Farklı Yaklaşımların Karşılaştırması

Pure REST’in Sınırları

Klasik REST yaklaşımında her şey resource üzerinden tanımlanır. Kullanıcı bilgileri için /users/{id}, kartlar için /users/{id}/cards, siparişler için /users/{id}/orders gibi endpoint'ler tasarlarız. Bu yaklaşım ilk bakışta temiz ve tutarlı görünür, ancak pratikte sorunlar ortaya çıkar.

Mesela bir siparişi iptal etmek istediğinizi düşünün. Pure REST’te bunu şöyle yaparsınız:

// Pure REST yaklaşımı - Sipariş iptali
PATCH /users/123/orders/456
{
"status": "cancelled",
"cancellationReason": "Customer request",
"cancelledAt": "2024-01-15T10:30:00Z",
"refundStatus": "pending"
}

Bu yaklaşımın sorunu, client’ın hangi field’ları göndermesi gerektiğini bilmesi gerekmesidir. Peki ya iptal işlemi email gönderme, stok güncelleme, ödeme iadesi gibi yan etkiler içeriyorsa? Client bunların hepsini bilmek zorunda mı?

Pragmatik Alternatif: Action-Based Endpoints

Gerçek dünyada, bazı işlemler basit veri güncellemelerinden fazlasıdır. Bunlar iş süreçleridir ve açıkça tanımlanmalıdır. İşte pragmatik yaklaşım:

// Pragmatik yaklaşım - Açık action endpoint
POST /orders/456/cancel
{
"reason": "Customer request"
}
// Response
{
"orderId": "456",
"status": "cancelled",
"refundInitiated": true,
"estimatedRefundDate": "2024-01-17",
"notificationsSent": ["email", "sms"]
}

Görüyorsunuz, endpoint’in kendisi ne yaptığını açıkça söylüyor. Client’ın kompleks business logic’i bilmesine gerek yok. Ayrıca response’da işlemin tüm yan etkileri hakkında bilgi veriliyor.

Gerçek Dünya Senaryosu: Kullanıcı Dashboard’u

Şimdi somut bir örnek üzerinden ilerleyelim. Tipik bir kullanıcı dashboard’unda şu bölümler olsun:

  1. Bilgilerim — Profil, ayarlar, tercihler
  2. Kartlarım — Kredi ve banka kartları
  3. Bağlantılarım — Sosyal medya entegrasyonları
  4. Siparişlerim — Alışveriş geçmişi

Her bölümün kendine özgü ihtiyaçları var. Hepsini tek bir yaklaşımla ele almak yerine, her domain için en uygun pattern’i seçebiliriz.

Profil Yönetimi: Resource-Based Ama Esnek

Profil bilgileri genelde CRUD operasyonlarına uygundur, ancak bazı action’lar da gerekir:

// Temel profil bilgileri - Resource based
GET /profile
PUT /profile/basic-info
{
"firstName": "Ahmet",
"lastName": "Yılmaz",
"phoneNumber": "+905551234567"
}
// Ancak şifre değişimi bir action'dır
POST /profile/change-password
{
"currentPassword": "oldPass123",
"newPassword": "newSecurePass456"
}
// Avatar yükleme de özel bir işlemdir
POST /profile/upload-avatar
Content-Type: multipart/form-data

Burada hibrit bir yaklaşım kullanıyoruz. Basit veri güncellemeleri için REST resource pattern’ini koruyoruz, ancak özel işlemler için action endpoint’leri ekliyoruz. Bu sayede hem tutarlılığı koruyoruz hem de API’nin anlaşılırlığını artırıyoruz.

Ödeme Kartları: Güvenlik Öncelikli Tasarım

Finansal veriler söz konusu olduğunda, güvenlik ve açıklık kritiktir. Her işlem net olarak tanımlanmalıdır:

// Kart listesi
GET /payment-methods
{
"cards": [
{
"id": "card_123",
"last4": "4242",
"brand": "visa",
"isDefault": true,
"expiryMonth": 12,
"expiryYear": 2025
}
]
}
// Kartı varsayılan yapma - Açık action
POST /payment-methods/cards/card_123/set-default
// Response
{
"success": true,
"previousDefault": "card_456",
"newDefault": "card_123"
}
// Kart bloklama - Geri alınamaz işlem için açık endpoint
POST /payment-methods/cards/card_123/block
{
"reason": "suspicious_activity",
"notifyUser": true
}

Dikkat ederseniz, hassas işlemler için her zaman POST kullanıyoruz ve action’ı URL’de açıkça belirtiyoruz. Bu yaklaşım, hem güvenlik açısından hem de audit log’ları için çok daha uygundur.

Sosyal Medya Bağlantıları: Provider-Specific Pattern

Her sosyal medya platformunun kendine özgü OAuth flow’u ve permission yapısı vardır. Generic bir endpoint yerine, provider-specific endpoint’ler daha mantıklıdır:

// Generic endpoint yerine
POST /connections
{
"provider": "google",
"scopes": ["email", "profile"]
}
// Provider-specific endpoint tercih edilir
POST /connections/link-google
{
"scopes": ["email", "profile"],
"redirectUri": "myapp://callback"
}
// Her provider için özel permission yönetimi
GET /connections/google/permissions
{
"granted": ["email", "profile"],
"available": ["calendar", "drive", "photos"],
"revoked": []
}
POST /connections/google/request-permissions
{
"additionalScopes": ["calendar.readonly"]
}

Bu yaklaşımın avantajı, her provider’ın özel ihtiyaçlarını karşılayabilmesidir. Google için calendar permission’ı mantıklıyken, Instagram için bu anlamsızdır. Generic bir yapı bu farklılıkları handle etmekte zorlanır.

State Machine ve İş Akışları

Siparişler gibi state machine mantığıyla çalışan resource’lar için, state transition’ları açıkça modellemek önemlidir:

// Sipariş durumu kontrolü
GET /orders/789/state
{
"currentState": "payment_pending",
"allowedTransitions": ["confirm_payment", "cancel"],
"stateHistory": [
{
"state": "created",
"timestamp": "2024-01-15T09:00:00Z"
},
{
"state": "payment_pending",
"timestamp": "2024-01-15T09:01:00Z"
}
]
}
// State transition validation
POST /orders/789/transitions/validate
{
"targetState": "shipped"
}
// Response
{
"valid": false,
"blockers": [
"Payment not confirmed",
"Shipping address not verified"
]
}
// Geçiş yapma
POST /orders/789/transitions/confirm-payment
{
"paymentId": "pay_xyz",
"amount": 299.90
}

Bu pattern, karmaşık iş akışlarını yönetmeyi ve debug etmeyi çok kolaylaştırır. Her state transition’ı ayrı bir endpoint olduğu için, hangi işlemin ne zaman kimler tarafından yapılabileceğini kontrol etmek basittir.

Aggregation ve Performance Optimizasyonu

Mobil uygulamalar için API tasarlarken, network round-trip’leri minimize etmek kritiktir. Bunun için aggregated endpoint’ler kullanabiliriz:

// Chatty approach - Çok fazla request
GET /profile/basic
GET /profile/preferences
GET /profile/notifications
GET /profile/privacy
// Aggregated approach - Tek request
GET /profile?include=basic,preferences,notifications,privacy
// Ya da field selection ile
GET /profile?fields=id,name,email,preferences.language,preferences.theme
// Response
{
"id": "user_123",
"name": "Ahmet Yılmaz",
"email": "ahmet@example.com",
"preferences": {
"language": "tr",
"theme": "dark"
}
}

Bu yaklaşımla client, ihtiyacı olan veriyi tek seferde alabilir. GraphQL’in popülerlik kazanmasının sebeplerinden biri de budur, ancak REST API’lerde de benzer esnekliği sağlayabiliriz. (Araya eklemek isterim ben tam bir Hasura-GraphQL taraftarıyım. Tanıştığım ilk an yıldırım aşkı gerçekleşti aramızda. Self hosted olarak sunucuma mutlaka kuruyorum)

Batch Operations: Verimlilik İçin

Bazen birden fazla resource üzerinde aynı işlemi yapmak gerekir. Batch endpoint’ler bu durumlar için idealdir:

// Toplu kart silme
POST /payment-methods/cards/batch-delete
{
"cardIds": ["card_123", "card_456", "card_789"],
"reason": "User requested cleanup"
}
// Response
{
"succeeded": ["card_123", "card_789"],
"failed": [
{
"cardId": "card_456",
"error": "Cannot delete default payment method"
}
],
"summary": {
"requested": 3,
"succeeded": 2,
"failed": 1
}
}

Batch operation’larda önemli olan, kısmi başarı durumlarını doğru handle etmektir. Bazı işlemler başarılı olurken bazıları başarısız olabilir, ve client bunun farkında olmalıdır.

Discovery ve Self-Documenting API’ler

İyi bir API, kullanıcısına ne yapabileceğini söyler. OPTIONS method’u veya özel discovery endpoint’leri bunun için kullanılabilir:

// OPTIONS ile available action'ları keşfetme
OPTIONS /orders/123
// Response
{
"availableActions": [
{
"action": "cancel",
"method": "POST",
"href": "/orders/123/cancel",
"available": true,
"requirements": "Order must be in pending or processing state"
},
{
"action": "return",
"method": "POST",
"href": "/orders/123/return",
"available": false,
"reason": "Return period has expired (>30 days)"
},
{
"action": "reorder",
"method": "POST",
"href": "/orders/123/reorder",
"available": true,
"description": "Create a new order with same items"
}
]
}

Bu yaklaşım, client developer’ların API’yi keşfetmesini kolaylaştırır ve runtime’da hangi action’ların mümkün olduğunu anlamasını sağlar.

Yıllardır API geliştirirken öğrendiğim en önemli ders, mükemmel teorik çözümlerin her zaman en iyi pratik çözümler olmadığıdır. Pure REST elegant ve tutarlı olabilir, ancak gerçek dünya ihtiyaçları karşısında yetersiz kalabilir.

Modern API tasarımında başarının anahtarı, dogmatik olmamaktır. Her domain’in, her use case’in kendine özgü ihtiyaçları vardır. Finansal işlemler için güvenlik ve açıklık önceliklidir, sosyal medya entegrasyonları için esneklik, e-ticaret için state management kritiktir.

Önemli olan, seçtiğiniz yaklaşımda tutarlı olmak ve iyi dokümante etmektir. Bir endpoint’te action-based yaklaşım kullanıyorsanız, benzer durumlar için aynı pattern’i kullanın. Aggregation destekliyorsanız, tüm major endpoint’lerde bu özelliği sunun. Endpoint’lerinizin ne yaptığı URL’den anlaşılmalı, error mesajlarınız açıklayıcı olmalı, dokümantasyonunuz güncel ve örneklerle dolu olmalıdır.

Son olarak, API’niz bir contract’tır. Bir kez yayınladıktan sonra değiştirmek zordur. Bu yüzden baştan esnek ve evrilebilir tasarlayın. Versiyonlama stratejinizi belirleyin, deprecation policy’nizi netleştirin, ve her zaman geriye uyumluluğu gözetin.

Teknik mükemmellik ile pratik kullanılabilirlik arasındaki dengeyi bulmak, deneyim ve empati gerektirir. Her API kararınızda kendinize sorun: “Bu endpoint’i kullanan developer ne hisseder? İşini kolaylaştırır mı, zorlaştırır mı?”

Cevap genelde doğru yolu gösterecektir diye düşünüyorum.

Umarım bu yazım ilgilisi ve meraklısı için faydalı olur. Sonraki yazılarda görüşmek üzere…

Github: www.github.com/abdullah017
Linkedin: www.linkedin.com/in/abdullahtas
Stackoverflow:
https://stackoverflow.com/users/13807726/abdullah-t

#FREEPALESTINA

--

--

AbdullahTaş
AbdullahTaş

Written by AbdullahTaş

Full Stack | Ai | Mobile Developer | Project Management | Software Team Lead |