Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124

Bir API tasarlarken aslında yalnızca bir fonksiyon, endpoint ya da servis metodu yazmış olmuyorsun. Başka geliştiricilerin senin sisteminle nasıl konuşacağını belirleyen bir sözleşme kuruyorsun. Bu yazıda iyi bir API’nin nasıl adlandırılması gerektiğini, hangi parametreleri istemesi gerektiğini, hataları nasıl ele alacağını ve büyük/veri yoğun cevaplarda nasıl davranacağını öğreneceksin.
Bir API tasarlarken aslında yalnızca bir fonksiyon, endpoint ya da servis metodu yazmış olmuyorsun. Başka geliştiricilerin senin sisteminle nasıl konuşacağını belirleyen bir sözleşme kuruyorsun. Bu yazıda iyi bir API’nin nasıl adlandırılması gerektiğini, hangi parametreleri istemesi gerektiğini, hataları nasıl ele alacağını ve büyük/veri yoğun cevaplarda nasıl davranacağını öğreneceksin.
API, yani Application Programming Interface. Yani şöyle düşün: sisteminin iç detaylarını bilmeyen biri, sadece dışarıya açtığın kurallara bakarak senden belirli bir işi yaptırabilmeli.
Örneğin bir e-ticaret sisteminde kullanıcının siparişlerini getiren bir API olduğunu düşünelim. Bu API’nin içeride hangi veritabanına gittiği, kaç servis çağırdığı veya cache kullanıp kullanmadığı dışarıdaki kişi için önemli değildir. Dışarıdaki kişi şunu bilmek ister: “Ben kullanıcı kimliğini verirsem, bana hangi durumda ne döneceksin?”
flowchart TD
A["İstemci"] --> B["getOrders(userId)"]
B --> C{"Kullanıcı var mı?"}
C -- "Hayır" --> D["USER_NOT_FOUND hatası"]
C -- "Evet" --> E["Siparişleri getir"]
E --> F["Order[] yanıtı"]Bu diyagram basit bir API sözleşmesini gösteriyor. İstemci hangi girdiyi vereceğini, hangi hatayı alabileceğini ve başarılı durumda nasıl bir yanıt bekleyeceğini bilir.
İyi bir API’nin adı, yaptığı işle birebir uyumlu olmalıdır. getOrders isimli bir API çağırıyorsan, onun siparişleri getirmesini beklersin. Aynı API kullanıcı profili, kampanya bilgisi ve ödeme geçmişi de dönüyorsa artık isim davranışı anlatmıyor demektir.
Bunu şöyle hayal edebilirsin: üzerinde “siparişler” yazan bir klasör açıyorsun ama içinde müşteri notları, fatura ayarları ve kampanya kuralları da var. İlk anda işe yarıyor gibi görünür, ama sistem büyüdükçe kimse o klasörün neyi temsil ettiğinden emin olamaz.
flowchart LR
A["Yanlış Tasarım: getOrders"] --> B["Siparişler"]
A --> C["Kullanıcı Profili"]
A --> D["Kampanya Bilgileri"]
E["Daha Temiz Tasarım"] --> F["getOrders"]
E --> G["getUserProfile"]
E --> H["getAvailableCampaigns"]Bu diyagramda ilk yaklaşım API’nin sınırlarını bulanıklaştırıyor. İkinci yaklaşımda ise her API tek ve anlaşılır bir sorumluluğa sahip.
Bir API yalnızca ihtiyaç duyduğu bilgileri istemelidir. “Belki ileride lazım olur” diye eklenen parametreler API’yi daha esnek değil, daha karışık hale getirir.
Parametre, yani şöyle düşün: API’ye işi yapması için verdiğin ham bilgidir. Eğer siparişleri getirmek için sadece userId yeterliyse, API’nin ayrıca kullanıcının e-posta adresini, segmentini veya son giriş tarihini istemesi kafa karıştırıcıdır.
flowchart TD
A["API tasarımı"] --> B{"Bu parametre gerçekten gerekli mi?"}
B -- "Evet" --> C["Sözleşmeye ekle"]
B -- "Hayır" --> D["Parametreyi çıkar"]
C --> E["Daha net API"]
D --> EBuradaki amaç API’yi daraltmak değil, niyetini berraklaştırmaktır. Çağıran kişi ne kadar az tahmin yürütürse, API o kadar iyi tasarlanmış demektir.
Response, yani şöyle düşün: API’nin yaptığı işin sonucunu paketleyip geri göndermesidir. Bu paketin içine gereğinden fazla veri koymak çoğu zaman iyi bir fikir değildir.
Bir liste ekranında sadece sipariş numarası, tarih ve toplam tutar gösterilecekse, API’nin her sipariş için teslimat adresi, ödeme sağlayıcısı detayları, kampanya geçmişi ve müşteri notlarını dönmesi gereksizdir. Bu hem ağı yorar hem de istemcileri gereksiz alanlara bağımlı hale getirir.
flowchart TD
A["İstemci sipariş listesi ister"] --> B{"Yanıt nasıl tasarlanmalı?"}
B --> C["Sadece gerekli alanlar"]
B --> D["Gereğinden fazla alan"]
C --> E["Daha hızlı ve anlaşılır API"]
D --> F["Daha büyük payload ve daha fazla bağımlılık"]Payload, yani şöyle düşün: istemci ile sunucu arasında taşınan veri yüküdür. Payload büyüdükçe performans maliyeti artar ve API’nin bakım yükü ağırlaşır.
API hataları tasarlarken iki uçtan da kaçınmak gerekir. Her şeye tek bir “GENERIC_ERROR” dönmek çağıran kişiye hiçbir şey anlatmaz. Ama sistemin içindeki her teknik detayı ayrı hata olarak dışarı açmak da API’yi gereksiz karmaşık hale getirir.
Hata tasarımında temel soru şudur: Bu hata çağıranın bekleyebileceği ve aksiyon alabileceği bir durum mu?
flowchart TD
A["API çağrısı gelir"] --> B{"Beklenen bir iş hatası mı?"}
B -- "Evet" --> C["Açık hata kodu dön"]
B -- "Hayır" --> D{"İstemci bu hatayı düzeltebilir mi?"}
D -- "Evet" --> E["Anlamlı doğrulama hatası dön"]
D -- "Hayır" --> F["Genel sistem hatası dön"]Bu diyagram iyi bir hata ayrımı gösteriyor. Kullanıcı yoksa, kayıt bulunamadıysa veya izin yoksa bunlar açıkça ifade edilmelidir. Ama veritabanının içeride hangi limitte patladığını her zaman dışarıya iş hatası gibi taşımak gerekmez.
HTTP API tasarlarken route, method ve body birbirinden ayrı düşünülmelidir. HTTP method, yani şöyle düşün: çağrının temel eylemini söyleyen parçadır. GET okuma, POST oluşturma veya işlem başlatma, PUT/PATCH güncelleme, DELETE silme niyetini taşır.
Kötü tasarımda tek bir genel endpoint açılır ve body içine action gibi bir alan konur. Bu çalışabilir ama API’nin ne yaptığı route seviyesinde anlaşılmaz.
flowchart LR
subgraph Kotu["Belirsiz Tasarım"]
A["POST /commerce"] --> B["body: action=getOrders"]
end
subgraph Iyi["Daha Temiz Tasarım"]
C["GET /v1/users/{userId}/orders"] --> D["Siparişleri oku"]
E["POST /v1/orders"] --> F["Yeni sipariş oluştur"]
endTemiz tasarımda kaynak route içinde, eylem ise HTTP method içinde görünür. Böylece API dokümantasyonuna bakmadan bile endpoint’in amacı büyük ölçüde anlaşılır.
Side effect, yani şöyle düşün: API’nin adından anlaşılmayan ek işler yapmasıdır. updateOrderStatus isimli bir API yalnızca sipariş durumunu güncelliyor gibi görünür. Ama aynı çağrı içeride fatura oluşturuyor, stok hareketi açıyor ve kullanıcıya kupon tanımlıyorsa davranış artık gizlenmiştir.
flowchart TD
A["updateOrderStatus(orderId, shipped)"] --> B["Sipariş durumunu günceller"]
A -. "Gizli yan etki" .-> C["Stok hareketi oluşturur"]
A -. "Gizli yan etki" .-> D["Bildirim gönderir"]
A -. "Gizli yan etki" .-> E["Fatura süreci başlatır"]Bu tarz API’ler test etmeyi, debug etmeyi ve güvenle kullanmayı zorlaştırır. Eğer gerçekten birden fazla adım tek bir iş akışının parçasıysa, API’nin adı bunu açıkça anlatmalıdır.
Atomicity, yani şöyle düşün: bir işlemdeki bütün adımların ya birlikte başarılı olması ya da hiçbirinin yapılmamış sayılmasıdır. Ödeme almak, stok ayırmak ve sipariş oluşturmak gibi adımlar bazen tek bir bütün olarak ele alınmalıdır.
Bu durumda sorun bir API’nin birden fazla servis çağırması değildir. Sorun, API adının bu birleşik davranışı saklamasıdır.
sequenceDiagram
participant Client as İstemci
participant API as Checkout API
participant Stock as Stok Servisi
participant Payment as Ödeme Servisi
participant Order as Sipariş Servisi
Client->>API: completeCheckout(cartId)
API->>Stock: reserveItems(cartId)
API->>Payment: chargeCustomer(cartId)
API->>Order: createOrder(cartId)
API-->>Client: checkoutResultBu diyagramda completeCheckout adı, işlemin tek bir küçük güncelleme olmadığını anlatıyor. API bir iş akışını temsil ediyor ve bu yüzden çok adımlı olması daha anlaşılır hale geliyor.
Bazı API’ler çok büyük veri döndürür. Örneğin bir eğitim platformunda bir kursun tüm öğrencilerini, ilerleme durumlarını ve sertifika bilgilerini tek çağrıda döndürmek başlangıçta kolay görünebilir. Ama veri büyüdükçe hem sunucu hem istemci zorlanır.
Pagination, yani şöyle düşün: büyük bir listeyi tek seferde almak yerine sayfa sayfa almaktır.
flowchart TD
A["İstemci"] --> B["GET /courses/{id}/students?limit=20&cursor=abc"]
B --> C["Sunucu 20 kayıt döner"]
C --> D{"Daha fazla kayıt var mı?"}
D -- "Evet" --> E["nextCursor gönder"]
D -- "Hayır" --> F["Liste tamamlandı"]Pagination özellikle kullanıcı arayüzlerinde çok işe yarar. Kullanıcı ilk sayfayı görür, devamına ihtiyaç duyarsa sonraki kayıtlar istenir.
Fragmentation ise daha çok servisler arası iletişimde anlam kazanır. Fragmentation, yani şöyle düşün: çok büyük bir cevabı küçük paketlere bölüp sırayla göndermektir.
sequenceDiagram
participant A as Servis A
participant B as Servis B
A->>B: Büyük veri isteği
B-->>A: Parça 1
B-->>A: Parça 2
B-->>A: Parça 3
B-->>A: Bitti mesajıBu yapıda alıcı servis cevabın tek seferde gelmesini beklemez. Parçaları sırayla işler ve bitiş mesajıyla yanıtın tamamlandığını anlar.
Consistency, yani şöyle düşün: API’nin döndürdüğü verinin sistemin gerçek ve en güncel haliyle ne kadar uyumlu olduğudur. Bazı durumlarda en güncel veri şarttır; bazı durumlarda birkaç saniyelik gecikme kabul edilebilir.
Ödeme sonucu, banka bakiyesi veya stok düşme gibi işlemlerde güçlü tutarlılık gerekir. Ama yorum sayısı, beğeni listesi veya öneri sonuçları birkaç saniye geriden gelebilir.
flowchart TD
A["API isteği"] --> B{"Veri kritik mi?"}
B -- "Evet" --> C["Güncel kaynaktan oku"]
B -- "Hayır" --> D{"Cache yeterli mi?"}
D -- "Evet" --> E["Cache yanıtı dön"]
D -- "Hayır" --> F["Kaynaktan oku ve cache'i güncelle"]Cache, yani şöyle düşün: sık kullanılan bir cevabı daha hızlı dönebilmek için geçici olarak saklamaktır. Cache performansı artırır ama verinin biraz eski olabileceğini kabul etmen gerekir.
Service degradation, yani şöyle düşün: sistem tamamen cevap veremez hale gelmesin diye daha az detaylı ama hâlâ işe yarar bir yanıt dönmektir. Örneğin tam profil bilgisi yerine sadece ad ve profil görseli dönmek, öneri motoru yerine varsayılan liste göstermek buna örnek olabilir.
flowchart TD
A["Trafik artar"] --> B{"Sistem zorlanıyor mu?"}
B -- "Hayır" --> C["Tam yanıt dön"]
B -- "Evet" --> D["Yanıtı sadeleştir"]
D --> E["Zorunlu alanları koru"]
D --> F["Pahalı hesaplamaları atla"]
E --> G["API cevap vermeye devam eder"]
F --> GBu yaklaşım özellikle yüksek trafikli sistemlerde önemlidir. Her şeyi kusursuz dönmeye çalışırken sistemi tamamen çökertmek yerine, daha sınırlı ama anlamlı bir cevap dönmek çoğu zaman daha iyi bir tercihtir.
İyi API tasarımı yaparken birkaç soruyu tekrar tekrar sormak gerekir. Bu API doğru serviste mi duruyor? İsmi yaptığı işi anlatıyor mu? Gereğinden fazla parametre istiyor mu? Yanıtı ihtiyacı kadar mı? Hataları çağıranın anlayabileceği şekilde mi?
flowchart TD
A["API Tasarımı"] --> B["Nerede yaşamalı?"]
A --> C["Ne yapmalı?"]
A --> D["Ne istemeli?"]
A --> E["Ne döndürmeli?"]
A --> F["Hangi hataları tanımlamalı?"]
B --> G["Doğru servis sınırı"]
C --> H["Net isim ve davranış"]
D --> I["Minimum gerekli parametre"]
E --> J["Gerektiği kadar response"]
F --> K["Anlamlı hata sözleşmesi"]Bu diyagram iyi API tasarımının ana kontrol noktalarını özetliyor. Her karar, API’yi çağıran kişinin daha az tahmin yürütmesini sağlamalıdır.
İyi API tasarımı sadece çalışan bir endpoint üretmek değildir. İyi API; davranışı isminden anlaşılan, gereksiz parametre istemeyen, ihtiyacı kadar veri dönen, beklenen hataları açıkça ifade eden ve gizli yan etkilerden kaçınan bir sözleşmedir. Veri büyüdüğünde pagination veya fragmentation düşünülmeli, tutarlılık ihtiyacı işin doğasına göre belirlenmeli ve sistem zorlandığında kontrollü sadeleşme planlanmalıdır.
Bu yazı What is an API and how do you design it? videosundan ilham alınarak yazılmıştır.