Temiz Bir RESTful API Tasarımı için 15 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 yerleş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

Sistemin state’ini değiştirmeye yönelik taleplerde POST, PUT, PATCH veya DELETE metotlarını 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

Burada /cars/123/drivers/456 resource path’indeki drivers/456 ile gösterilenin bir sub-resource olduğunu varsaymalısınız. Eğer sub-resource değil de resource olsaydı /drivers/456 şeklinde tek başına ifade edilebiliyor olurdu.

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: CarDetails,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/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 payload’da bulunmamasını sağlayabilirsiniz. Bazen iletişim yükünü azaltmak için işe yarayabilir. Örneğin:

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

Fakat tam entegrasyon sağlanması gereken noktalarda alan seçimi doğru bir uygulama olmaz.

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.

Ya da page ve size parametreleri ile de sayfalama sağlayabilirsiniz. Örneğin:

GET /cars?page=1&size=10

total pages’i istemciye bildirmek için X-Total-Pages header’ını kullanın.

Eğer API response payload’unu zarflıyorsanız sayfalama geribildirim bilgisini envelope meta’sı olarak gömmek daha iyidir.

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 istemci 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 : Accept* header'ları ile bildirilerek talep edilen ifade biçimi kabul edilebilirler arasında değil
409 - Conflict : Kendi içinde çelişki bulunan parametre seti veya hedef resource'ın geçerli state'i ile çelişkide olan parametre seti alındı
415 - Unsupported Media Type : Desteklenmeyen içerik tipi / Desteklenmeyen request 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

Şöyle bir örnek ile bazı 4xx hataları arasındaki farklar daha iyi anlaşılabilir:

  • Gönderilmesi beklenen email alanı gönderilmemişse 400 dönülür.
  • Gönderilen email alanı geçersiz bir e-posta adresi barındırıyorsa 422 dönülür.
  • Gönderilen email alanındaki e-posta adresi daha önce kaydedilenlerden ise 409 dönülür.

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

500 - Internal Server Error : Tamamen beklenmedik bir hata oluştu

500 hatası uygulamada, yakalanmaya çalışılmayan veya başka bir değişle kendisine try/catch tuzağı kurulmayan exception’lar için kullanılmalı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 PATCH sonucunda 200 OK dönülmelidir. Başarılı bir PUT sonucunda -ki bu istekteki payload, identifier ile birlikte gelmiş olmalı- eğer yeni bir hedef resource oluştuysa 201 Created, eğer var olan hedef resource güncellendiyse 200 OK döndürülmesi gerekir. Bu arada PUT ile gönderilen payload identifier içermiyorsa talebin PUT yerine POST ile gerçekleştiriliyor olması gerekir.

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.

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

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

Response’u zarfa koy

Döndürülmek istenen şey şunun gibi yalın item da olsa:

{
  "id": 123,
  "name": "Ahmet Yilmaz"
}

şunun gibi item collection da olsa:

[
  {
    "id": 123,
    "name": "Ahmet Yilmaz"
  },
  {
    "id": 456,
    "name": "Abdullah Pazarbasi"
  }
]

data payload zarflanarak bir response payload oluşturulmalı ve oluşturulan zarf gönderilmeli. Şunun gibi:

{
  "errors": [],
  "warnings": [],
  "notices": [],
  "info": [
    {
      "userMessage": "",
      "internalMessage": "Process 1.234 seconds elapsed"
    }
  ],
  "data": [
    {
      "id": 456,
      "name": "Abdullah Pazarbasi",
      "links": [
        "rel": "self",
        "href": "/api/v2/drivers/456"
      ]
    }
  ],
  "pagination": {
    "currentPage": 2,
    "pageSize": 1,
    "totalPages": 5,
    "links": [
      {
        "rel": "first",
        "href": "/api/v2/cars/123/drivers?page=1&size=1"
      },
      {
        "rel": "previous",
        "href": "/api/v2/cars/123/drivers?page=1&size=1"
      },
      {
        "rel": "self",
        "href": "/api/v2/cars/123/drivers?page=2&size=1"
      },
      {
        "rel": "next",
        "href": "/api/v2/cars/123/drivers?page=3&size=1"
      },
      {
        "rel": "last",
        "href": "/api/v2/cars/123/drivers?page=5&size=1"
      }
    ]
  }
}

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.

Güncellenmiş Nesneyi Döndür

Herhangi bir resource, POST, PUT ya da PATCH sorgusu sonucunda başarılı şekilde oluşturulduğunda, değiştirildiğinde ya da güncellendiğinde resource’un son state’i response’a yerleştirilmelidir.

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]+)*$