Temiz Bir RESTful API Tasarımı için 13 Best-Practice

Doğru bir API tasarımı, kullanım kolaylığı, çabuk anlaşılabilirlik, hata tamirinde hız ve kararlılığa katkı sağlar. Ayrıca API arkasındaki aplikasyonun doğru bir yapıyla inşa edilmesinde ve geliştirilmesinde geliştiriciyi olumlu yönlendirir.

Bu maddelerin önem sırasına göre dizilmediğini bildirmeliyim. Bence hepsi birbirini tamamlayıcı pratikler.

İsim kullan, fiil kullanma

REST (Temsili Durum Aktarımı) kaynak yönelimlidir (resource-oriented). Ve bir kaynak URI ile ifade edilir. Dolayısıyla kaynak isimle ifade edilir. Fiil zaten metottur. RESTful’da end-point ise fiil ve isimin bir kombinasyonu ile ifade edilir:

Resource: /cars
Method: GET
End-point: GET /cars
İş: Araba listesi döndürür

Resource: /cars
Method: POST
End-point: POST /cars
İş: Araba oluşturur

Resource: /cars
Method: PUT
End-point: PUT /cars
İş: Arabaları topluca replace eder

Resource: /cars
Method: PATCH
End-point: PATCH /cars
İş: Arabaları topluca kısmi günceller

Resource: /cars
Method: DELETE
End-point: DELETE /cars
İş: Arabaların hepsini siler

Resource: /cars/123
Method: GET
End-point: GET /cars/123
İş: ID'si 123 olan arabayı döndürür

Resource: /cars/123
Method: POST
End-point: POST /cars/123
İş: Method Not Allowed 405 hatası döndürür

Resource: /cars/123
Method: PUT
End-point: PUT /cars/123
İş: ID'si 123 olan arabayı replace eder

Resource: /cars/123
Method: PATCH
End-point: PATCH /cars/123
İş: ID'si 123 olan arabayı kısmi günceller

Resource: /cars/123
Method: DELETE
End-point: DELETE /cars/123
İş: ID'si 123 olan arabayı siler

Resource yolu olarak fiil kullanmayın. Şunlar gibi:

/getAllCars
/createNewCar
/deleteAllBlackCars

Anlaşıldığı üzre metotlar yani fiiller CRUD işlemlerine karşılık geliyor: GET oku‘ya, POST oluştur‘a, PUT güncelle‘ye ya da değiştir‘e, PATCH kısmi güncelle‘ye, DELETE sil‘e denktir.

GET metodu ve query parametreleri nesne veya nesnelerin state’ini değiştirmemeli

Nesnelerin state’ini değiştirmek için POST, PUT, PATCH veya DELETE kullanın.

Örneğin şunları kullanmayın:

GET /cars/123?activate
GET /cars/123/activate

Yalnız çoğul isim kullan

Tüm resource’lar için şunlarda olduğu gibi yalnızca çoğul isim kullanın:

/cars/123 ( /car/123 değil)
/users ( /user değil)
/settings/sending-notification ( /setting/sending-notification değil)

Bir resource eğer başka bir resource ile ilişkili ise sub-resource düzenini kullan

Resource: /cars/123/drivers
Method: GET
İş: ID'si 123 olan arabanın sürücü listesini döndürür

Resource: /cars/123/drivers/456
Method: GET
İş: ID'si 123 olan arabanın sürücülerinden ID'si 456 olanı döndürür

Döndürülmesi istenen içerik tipini header bölümünde iste ve döndürülecek içeriğin tipini header bölümünde belirt

İstemci de sunucu da hangi format’ta iletişim kurması gerektiğini bilmelidir. Bu bilgi HTTP header’ları ile aktarılmalıdır.
Content-Type header’ı request format’ını tanımlamak için;
Accept header’ı response format’ını veya format listesini bildirmek için kullanılmalıdır.

Serializasyon formatı için HTTP header’larını kullan

Serialize çıktı yapısı bir ya da birden fazla parametreye bağlı olacaksa bu bir header unsuru olarak bildirilmelidir. Örneğin:

X-Serialization-Groups: Details,DriverDetails

HATEOAS kullan

Hypermedia as the Engine of Application State, daha iyi API navigasyonu sağlayacak olan bir hypertext link kullanımı prensibidir. Örneğin:

{
  "id": 123,
  "manufacturer": "BMC",
  "drivers": [
    {
      "id": 456,
      "name": "Abdullah Pazarbasi",
      "links": [
        {
          "rel": "self",
          "href": "/api/v2/cars/123/drivers/456"
        }
      ]
    }
  ]
}

Koleksiyonlar için alan seçimi, filtreleme, sıralama ve sayfalama bilgisi sağla

Alan Seçimi

Bir fields query parametresinin içinde virgülle ayrılmış alan isimlerini göndererek seçilen alanlar haricindekilerin değerlerinin null olarak dönmesini sağlayabilirsiniz. Bazen iletişim yükünü azaltmak için işe yarayabilir. Örneğin:

GET /cars/123?fields=id,manufacturer,model

Filtreleme

Süzme için şu örnekteki gibi parametre gönderebilirsiniz:

GET /cars?color=black&classification=sport

Sıralama

Artan ve azalan sıralama bildirmek için birden fazla alan için bile olsa şunun gibi kullanabilirsiniz:

GET /cars?sort=-manufacturer,+model

Sayfalama

offset ve limit parametreleri ile sayfalama sağlayabilirsiniz. Örneğin:

GET /cars?offset=0&limit=10

total count’u istemciye bildirmek için X-Total-Count header’ını kullanın.

Versiyonla

Şunun gibi resource yolu öncesi versiyon bildirin:

GET /api/v2/cars/123

Dikkat edilmesi gereken şeyler version başına v karakteri koymak ve versiyon numarasında nokta ( . ) kullanmamak.

Hataları, HTTP Status Code’ları ile ele al

Tüm status code’larını kullanmamıza gerek yok. Şunları kullanın:

200 - OK : Herşey yolunda
201 - Created : Yeni bir resource oluşturuldu
202 - Accepted : Güncelleme isteği kabul edildi ve işlenecek
204 - No Content : Resource listesi boş / Resource silindi
304 - Not Modified : İstemci önbelleklenen veriyi kullanabilir

4xx hataları error-payload’unda açıklanmalıdır.

400 - Bad Request : Geçersiz request gövdesi / Eksik parametre / Geçersiz parametre
401 - Unauthorized : User authentication gerekiyor
403 - Forbidden : Sunucu talebinizi algıladı ve fakat reddetti / Sunucu talebinizi algıladı ve fakat talebi yapmaya yetkiniz yok
404 - Not Found : URI ardındaki tekil resource mevcut değil
405 - Method Not Allowed : Geçerli HTTP metodu söz konusu URI için kullanılamaz
406 - Not Acceptable : İfade biçimi kabul edilemez durumda
409 - Conflict : Birbiri içinde çelişki bulunan parametre seti alınmış
415 - Unsupported Media Type : Desteklenmeyen içerik tipi / Desteklenmeyen request/response gövde notasyonu (json, xml vb.)
422 - Unprocessable Entity : Ön koşulları gerçekleşmemiş olduğu için hedeflenen state değişimi gerçekleştirilemedi

5xx hatalarının detayları production ortamında istemiciye ulaştırılmamalı ve fakat log’lanmalıdır.

Burada sıralanan HTTP status’ları arasında yanlış anlaşılabilenler var.

Her başarılı işleme cevap olarak 200 OK dönülmemeli. İşlemin türüne göre ayrım yapılmalıdır.

202 Accepted, bir PUT talebinin veya bir PATCH talebinin anında gerçekleşmesi sonucu döndürülmemesi gerekir. 202 Accepted talebin geçerli olduğunu ve gerçekleştirilmek üzere kuyruğa alındığını bildirmek içindir. Başarılı bir PUT ya da PATCH sonucunda 200 OK dönülmelidir.

Bir hata sonucunda meydana gelmemiş bir boş liste döndürülecekse 204 No Content ile döndürülmelidir. Böyle bir durumda 200 OK ya da 404 Not found kullanılmamalıdır. 404 Not Found tekil bir resource’un bulunamaması durumunda kullanılmalıdır.

Başarılı bir DELETE işlemi sonucu ister tekil resource için olsun ister birden fazla resource için olsun 204 No Content döndürülmelidir.

İsimlerin çağrıştırdığının tersi olarak 401 Unauthorized “unauthenticated” durumu, 403 Forbidden ise “unauthorized” durumu için kullanılmalıdır.

406 Not Acceptable, 202 Accepted‘ın aksi olarak kullanılmalıdır.

Peki error-payload ne sunmalı? Şu şekilde düzenlenebilir:

{
  "errors": [
    {
      "userMessage": "Foo",
      "internalMessage": "Bar",
      "code": "E123",
      "moreInfo": "https://services.example.com/api/v2/errors/e123"
    }
  ]
}

HTTP Metodu Override’ına izin ver

Bazı proxy’ler yalnız GET ve POST metodlarını destekler. Aktüel metod GET ya da POST olsa da X-HTTP-Method-Override header’ının değeri kastedilen metod olarak verilip engel kaldırılabilir.

URI’ın sonundaki taksim işaretini kızmadan ele al

GET /cars/123/

Burada görüldüğü gibi sonuna fazladan taksim işareti konmuş. Böyle bir talebi kızarak geri çevirmeyip 301 kalıcı yönlendirmesi ile karşılayın.

Tekil resource ve sub-resource’ların URI’daki karşılıkları için kullanılabilecek regular expression’lar (PCRE)

Sayısal ID’ler için:

^[1-9]\d*$

UUID’ler için:

^[\da-f]{8}\-[\da-f]{4}\-[1-5][\da-f]{3}\-[89ab][\da-f]{3}\-[\da-f]{12}$

Slug name’ler için:

^[a-z\d]+(?:\-[a-z\d]+)*$