API Tasarımı & REST Mimarisi 7 dk okuma 4 okunma

REST'te Kaynak Modelleme ve İlişkiler

REST mimarisinde kaynak (resource) kavramı, kaynak isimlendirme kuralları, tekil ve çoğul isimlendirme, hiyerarşik kaynaklar, kaynak ilişkileri (bire-bir, bire-çok, çoka-çok), alt kaynaklar vs. üst düzey kaynaklar, ilişki tasarımında denormalizasyon, hypermedia ile ilişki keşfi ve .NET'te kaynak modelleme stratejileri ele alınır

REST'te Kaynak Modelleme ve İlişkiler

REST'te Kaynak Modelleme ve İlişkileri Tasarımı

REST mimarisinin merkezinde kaynaklar (resources) vardır. Bir kaynak, bir varlığı (entity), bir koleksiyonu veya bir hizmeti temsil eder. REST API'nizin başarısı, büyük ölçüde kaynaklarınızı ne kadar doğru modellediğinize ve bu kaynaklar arasındaki ilişkileri ne kadar tutarlı tasarladığınıza bağlıdır. Yanlış bir kaynak modellemesi, API'nizi kullanılamaz hale getirebilir; doğru bir modelleme ise yıllar boyunca bakımı kolay, anlaşılır ve ölçeklenebilir bir API sunar. Bu yazıda, REST'te kaynak modelleme prensiplerini, isimlendirme kurallarını, ilişki türlerini ve .NET'te kaynak modelleme stratejilerini detaylıca ele alacağız.


1. Kaynak Nedir? Temel Kavramlar

REST'te kaynak, URI ile tanımlanabilen ve HTTP metotları ile üzerinde işlem yapılabilen herhangi bir şeydir. Kaynaklar somut (ör. bir kullanıcı, bir ürün) veya soyut (ör. bir süreç, bir oturum) olabilir.

Kaynak Türleri:

  • Tekil Kaynak (Singleton Resource): Sadece bir örneği olan kaynaklar. Ör. /api/profile (kullanıcının kendi profili).

  • Koleksiyon Kaynağı (Collection Resource): Birden fazla öğe içeren kaynaklar. Ör. /api/products.

  • Öğe Kaynağı (Item Resource): Koleksiyon içindeki tek bir öğe. Ör. /api/products/123.

  • Alt Kaynak (Sub-Resource): Başka bir kaynağa bağlı kaynak. Ör. /api/users/123/orders.

  • Kontrol Kaynağı (Controller Resource): Bir eylemi temsil eden kaynak. Ör. /api/orders/123/cancel (eylem odaklı, tercihen kaçınılmalı).


2. Kaynak İsimlendirme Kuralları

A. Çoğul İsimler Kullanın
Koleksiyonlar her zaman çoğul isimlerle ifade edilmelidir.

✅ Doğru:

text

GET /api/users
GET /api/products
GET /api/orders

❌ Yanlış:

text

GET /api/user
GET /api/product
GET /api/order

İstisna: Tekil kaynaklar (ör. /api/profile) doğal olarak tekildir.

B. İsimler Fiil Değil, İsim Olmalıdır
URI'ler kaynağı tanımlar, eylemi değil. Eylem, HTTP metodunun kendisidir.

✅ Doğru:

text

GET    /api/users        → Kullanıcıları listele
POST   /api/users        → Yeni kullanıcı oluştur
GET    /api/users/123    → Kullanıcı detayını getir
PUT    /api/users/123    → Kullanıcıyı güncelle
DELETE /api/users/123    → Kullanıcıyı sil

❌ Yanlış:

text

GET /api/getUsers
POST /api/createUser
GET /api/getUserById?id=123
POST /api/updateUser/123
POST /api/deleteUser/123

C. Küçük Harf ve Tire (kebab-case) Kullanın
URI'ler küçük harf içermeli, kelime ayırıcı olarak tire (-) kullanılmalıdır.

✅ Doğru:

text

/api/order-items
/api/user-profiles
/api/payment-methods

❌ Yanlış:

text

/api/OrderItems
/api/order_items
/api/userProfiles

D. Dosya Uzantısı Kullanmayın
URI'de dosya uzantısı olmamalıdır; format Accept başlığı ile belirtilir.

✅ Doğru:

text

GET /api/products/123
Accept: application/json

❌ Yanlış:

text

GET /api/products/123.json
GET /api/products/123.xml

E. Sürümleme (Versioning)
Sürüm bilgisi URI'ye eklenmelidir (en yaygın yaklaşım).

text

/api/v1/users
/api/v2/users

3. Kaynak İlişkileri: Bire-Bir, Bire-Çok, Çoka-Çok

A. Bire-Bir (One-to-One) İlişki
Her iki kaynak da birbirine yalnızca bir kez bağlıdır. Örneğin, User ve Profile.

Yaklaşım 1: Alt Kaynak (Sub-Resource)

text

GET /api/users/123/profile
POST /api/users/123/profile
PUT /api/users/123/profile

Yaklaşım 2: Doğrudan Kaynak (önerilen)
Eğer ilişki zorunluysa ve profil kullanıcıdan bağımsız düşünülemezse:

text

GET /api/users/123        → Yanıtta profil bilgisi de döner

B. Bire-Çok (One-to-Many) İlişki
Bir kaynak, diğerinden birden fazlasına bağlıdır. Örneğin, User ve Order.

Yaklaşım 1: Alt Kaynak (Nested)

text

GET /api/users/123/orders           → Kullanıcının tüm siparişleri
POST /api/users/123/orders          → Kullanıcı için yeni sipariş
GET /api/users/123/orders/456       → Kullanıcının belirli bir siparişi

Yaklaşım 2: Filtreli Üst Düzey Kaynak (önerilen)

text

GET /api/orders?userId=123          → Kullanıcının siparişleri
GET /api/orders/456                 → Sipariş detayı (userId bilgisi içinde)

Hangi Yaklaşım Ne Zaman?

  • Alt Kaynak: İlişki zorunluysa (kullanıcı olmadan sipariş anlamsızsa).

  • Filtreli Kaynak: Kaynak bağımsız bir varlıksa ve farklı filtrelerle erişilecekse.

C. Çoka-Çok (Many-to-Many) İlişki
Her iki kaynak da diğerinden birden fazlasına bağlıdır. Örneğin, Product ve Category.

Yaklaşım 1: Ayrı İlişki Kaynağı

text

GET /api/products/123/categories            → Ürünün kategorileri
GET /api/categories/456/products            → Kategorinin ürünleri

Yaklaşım 2: Ara Kaynak (Junction Resource)
İlişkinin kendisi bir kaynak olarak modellenir.

text

GET /api/product-categories?productId=123   → Ürün-kategori ilişkileri
POST /api/product-categories                → Yeni ilişki oluştur
DELETE /api/product-categories/789          → İlişkiyi sil

Bu yaklaşım, ilişkiye ait ek veriler (ör. createdAt, priority) varsa idealdir.


4. Hiyerarşi Derinliği: Ne Kadar İç İçe?

URI hiyerarşisi, kaynaklar arasındaki ilişkiyi yansıtır. Ancak aşırı derin iç içe yapılar (deep nesting), API'yi karmaşık ve kullanılamaz hale getirir.

Kural: Maksimum 2-3 seviye derinlik

✅ Kabul Edilebilir:

text

GET /api/users/123/orders/456/items

❌ Kaçınılması Gereken:

text

GET /api/users/123/orders/456/items/789/reviews/101/comments

Bu durumda, daha üst düzey bir kaynak kullanmak daha iyidir:

text

GET /api/order-items/789/reviews
GET /api/reviews/101/comments

İlke: Eğer bir alt kaynak, üst kaynağa bağımlı olmadan da anlamlıysa, onu üst düzey bir kaynak olarak modelleyin.


5. Kaynak Temsili ve Denormalizasyon

REST'te her kaynak, kendi temsiline (representation) sahiptir. İlişkili kaynakların nasıl sunulacağı önemli bir tasarım kararıdır.

A. Minimal Temsil (Reference by ID)
İlişkili kaynak, sadece ID ile referans edilir.

json

{
  "id": 456,
  "total": 250.00,
  "status": "shipped",
  "customerId": 123
}

Avantaj: Küçük payload, tutarlı veri.
Dezavantaj: İstemci, ilişkili veriyi almak için ek çağrı yapmalıdır (N+1 problemi).

B. Genişletilmiş Temsil (Embedded)
İlişkili kaynak, yanıtın içine gömülür.

json

{
  "id": 456,
  "total": 250.00,
  "status": "shipped",
  "customer": {
    "id": 123,
    "name": "Ahmet Yılmaz",
    "email": "ahmet@example.com"
  }
}

Avantaj: Tek çağrıda tüm veri, istemci kolaylığı.
Dezavantaj: Büyük payload, veri tekrarı, tutarlılık zorluğu.

C. Hibrit Yaklaşım (En İyi Pratik)
İstemciye, ilişkili veriyi nasıl alacağını seçme hakkı tanıyın.

text

GET /api/orders/456                   → Minimal temsil (sadece customerId)
GET /api/orders/456?expand=customer    → Genişletilmiş temsil (customer detayları dahil)

Bu yaklaşım, GraphQL'in select mantığına benzer bir esneklik sunar.


6. Hypermedia ile İlişki Keşfi (HATEOAS)

HATEOAS kullanıyorsanız, ilişkili kaynakların link'lerini yanıta ekleyerek istemcinin ilişkileri keşfetmesini sağlayabilirsiniz.

json

{
  "id": 456,
  "total": 250.00,
  "status": "shipped",
  "links": [
    { "rel": "self", "href": "/api/orders/456", "method": "GET" },
    { "rel": "customer", "href": "/api/users/123", "method": "GET" },
    { "rel": "items", "href": "/api/orders/456/items", "method": "GET" },
    { "rel": "cancel", "href": "/api/orders/456/cancel", "method": "POST" }
  ]
}

7. Kaynak Modellemede Sık Yapılan Hatalar

Hata Çözüm
Fiil içeren URI'ler (/getUser, /createOrder) Sadece isim kullanın; eylemi HTTP metodu belirler.
Aşırı derin iç içe yapılar Maksimum 2-3 seviye derinlik; bağımsız kaynakları üst düzeye taşıyın.
Tutarsız isimlendirme (/users, /product, /OrderItems) Tutarlı bir kural belirleyin (çoğul, küçük harf, tire).
Her ilişki için alt kaynak Kaynak bağımsızsa, filtrelenmiş üst düzey kaynak kullanın.
Çoka-çok ilişkilerde ara kaynak eksikliği İlişkiye ait veri varsa, ara kaynağı modelleyin.
İstemciye aşırı veri yükleme Minimal temsil + expand parametresi ile hibrit yaklaşım kullanın.

8. .NET'te Kaynak Modelleme Stratejileri

A. Controller Yapısı

csharp

// Koleksiyon ve öğe kaynakları
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class OrdersController : ControllerBase
{
    // GET /api/v1/orders
    [HttpGet]
    public async Task<IActionResult> GetOrders([FromQuery] int? userId) { ... }

    // GET /api/v1/orders/456
    [HttpGet("{id}")]
    public async Task<IActionResult> GetOrder(int id) { ... }

    // GET /api/v1/users/123/orders
    [HttpGet("/api/v1/users/{userId}/orders")]
    public async Task<IActionResult> GetUserOrders(int userId) { ... }
}

B. DTO Tasarımı ile Hibrit Temsil

csharp

public class OrderDto
{
    public int Id { get; set; }
    public decimal Total { get; set; }
    public string Status { get; set; }
    public int CustomerId { get; set; }
    
    // Sadece expand=customer olduğunda doldurulur
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public CustomerDto? Customer { get; set; }
}

// Controller'da
[HttpGet("{id}")]
public async Task<IActionResult> GetOrder(int id, [FromQuery] string? expand)
{
    var order = await _orderService.GetByIdAsync(id);
    var dto = _mapper.Map<OrderDto>(order);

    if (expand?.Contains("customer") == true)
    {
        dto.Customer = _mapper.Map<CustomerDto>(order.Customer);
    }

    return Ok(dto);
}

C. GraphQL ile İlişki Modelleme (Alternatif)
GraphQL, ilişkileri modellemek için doğal bir yol sunar:

graphql

type Order {
  id: ID!
  total: Float!
  status: String!
  customer: Customer!   # İlişki doğrudan tanımlı
}

type Customer {
  id: ID!
  name: String!
  orders: [Order!]!     # Ters ilişki
}

9. En İyi Pratikler

  1. Kaynakları İş Alanına Göre Modelleyin: Veritabanı tablolarını değil, iş alanındaki varlıkları modelleyin.

  2. Tutarlı İsimlendirme: Çoğul, küçük harf, tire ayırıcı.

  3. İlişkileri Basitleştirin: Aşırı iç içe yapılardan kaçının, bağımsız kaynakları üst düzeye taşıyın.

  4. Hibrit Temsil Kullanın: Minimal temsil + expand parametresi.

  5. Bağımsız Kaynak İlkesi: Bir kaynak, üst kaynağa bağımlı olmadan da anlamlıysa, üst düzey kaynak olarak modelleyin.

  6. Dokümantasyon: Her kaynağın ilişkilerini ve URI yapısını dokümante edin.

  7. Tutarlılık: Tüm API'de aynı kuralları uygulayın.

Sonuç

REST'te kaynak modelleme ve ilişki tasarımı, API'nizin uzun vadeli başarısını belirleyen en kritik kararlardır. Kaynakları doğru isimlendirmek, ilişkileri tutarlı modellemek, aşırı iç içe yapılardan kaçınmak ve istemciye esneklik sunmak, bakımı kolay, anlaşılır ve ölçeklenebilir bir API'nin temelini oluşturur. Unutmayın: İyi bir REST API, bir veritabanı şemasının HTTP üzerinden yansıması değil, iş alanının doğal bir modellemesidir.

Tüm yazılar Soru ya da yorumun mu var? Yaz