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

REST API'lerde İlişkisel Kaynaklar ve Derinlik Kontrolü

İlişkisel kaynaklar, REST API'lerinde varlıklar arasındaki bağları URI yapısına yansıtır. Alt kaynak (nested) modelleme ilişkiyi netleştirirken, aşırı derinlik okunabilirliği ve bakımı zorlaştırır. Maksimum 2-3 seviye derinlik, bağımsız kaynak ilkesi, expand/include parametreleriyle seçmeli genişletme ve beyaz liste doğrulaması, N+1 problemini önleyen eager loading ile birlikte uygulanmalıdır.

REST API'lerde İlişkisel Kaynaklar ve Derinlik Kontrolü

REST API'lerde İlişkisel Kaynaklar ve Derinlik Kontrolü

REST API tasarımında varlıklar nadiren yalnız başına bulunur. Bir siparişin müşterisi, kalemleri, kalemlerin ürünleri, ürünlerin kategorileri vardır. Bu ilişkilerin URI yapısına nasıl yansıtılacağı, API'nin kullanılabilirliğini ve bakımını doğrudan etkiler. /users/123/orders/456/items/789/product gibi bir URI kabul edilebilir mi? Bu yazıda, ilişkisel kaynakların modellenmesini ve derinlik kontrolünü ele alacağız.


1. İlişkisel Kaynak Modelleme Yaklaşımları

A. Alt Kaynak (Nested Resource)

İlişkili kaynak, ana kaynağın URI'si altında modellenir.

text

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

Avantaj: İlişki URI'de açıkça görünür.
Dezavantaj: Derinlik arttıkça URI karmaşıklaşır; kaynak bağımsız erişilemez.

B. Üst Düzey Kaynak + Filtre

İlişkili kaynak, kendi başına üst düzey kaynak olarak modellenir ve filtre ile ilişki kurulur.

text

GET /api/orders?userId=123
GET /api/orders/456

Avantaj: Kaynak bağımsızdır; farklı filtrelerle erişilebilir.
Dezavantaj: İlişki URI'de açıkça görünmez.

C. Hibrit Yaklaşım (En İyi Pratik)

Her iki yaklaşımı birlikte sunun:

text

GET /api/users/123/orders    → Kısayol
GET /api/orders?userId=123   → Filtrelenmiş üst düzey kaynak
GET /api/orders/456          → Doğrudan erişim

2. URL Derinliği: Ne Kadar Derin?

Kabul edilebilir (2-3 seviye):

text

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

Kaçınılması gereken (4+ seviye):

text

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

Aşırı derinlik neden kötüdür?

  • Okunabilirlik kaybı: URI'ler uzar, anlaşılması zorlaşır.

  • Kırılganlık: Üst kaynaklardan biri değişirse tüm alt URI'ler etkilenir.

  • Bağımlılık: Alt kaynağa erişmek için tüm üst kaynak ID'lerini bilmek gerekir.

Kural: Bir kaynak, üst kaynağa bağımlı olmadan da anlamlıysa, onu üst düzey kaynak yapın.

text

❌ /api/users/123/orders/456/items/789
✅ /api/order-items/789

3. Bağımsız Kaynak İlkesi

Bir kaynak şu özelliklere sahipse üst düzey kaynak olarak modellenmelidir:

  • Kendi benzersiz kimliği varsa (orderItemId)

  • Üst kaynağa bağımlı olmadan anlamlıysa

  • Farklı üst kaynaklar altında da görünebiliyorsa


4. İlişki Genişletme (Expansion) Parametreleri

İlişkili veriyi URI'de iç içe göstermek yerine, query parametresi ile genişletin:

text

GET /api/orders/456?expand=customer,items
GET /api/orders/456?include=items.product
GET /api/orders/456?fields=id,total,status

Bu yaklaşım, URI'yi sade tutarken ilişkili veriye erişim sağlar.

Güvenlik Uyarısı (Kritik): Kullanıcı tarafından gönderilen include parametrelerini beyaz liste ile doğrulamadan kullanmayın. Aksi halde kötü niyetli bir istemci, çok derin iç içe yüklemelerle sunucuyu çökertebilir (DoS).

csharp

private static readonly HashSet<string> AllowedIncludes = new()
{
    "customer", "items", "items.product"
};

var includes = include?.Split(',') ?? Array.Empty<string>();
if (includes.Except(AllowedIncludes).Any())
    return BadRequest("Geçersiz include parametresi.");

5. N+1 Problemi ve Eager Loading

İlişkisel kaynaklar, N+1 problemini beraberinde getirir.

❌ N+1:

csharp

var orders = await _context.Orders.ToListAsync();
var result = orders.Select(o => new OrderDto
{
    Id = o.Id,
    CustomerName = _context.Customers.Find(o.CustomerId).Name // Her sipariş için ayrı sorgu!
});

✅ Eager Loading:

csharp

var query = _context.Orders.AsNoTracking();
if (expand?.Contains("customer") == true)
    query = query.Include(o => o.Customer);
if (expand?.Contains("items") == true)
    query = query.Include(o => o.Items);

var orders = await query.ToListAsync();

6. DTO Tasarımı ile İlişki Yönetimi

csharp

public class OrderDto
{
    public int Id { get; set; }
    public decimal Total { get; set; }
    public int CustomerId { get; set; }

    // Sadece expand=customer olduğunda dolu
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public CustomerDto? Customer { get; set; }

    // Sadece expand=items olduğunda dolu
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public List<OrderItemDto>? Items { get; set; }
}

Varsayılan olarak minimal temsil dönün; expand ile genişletin.


7. En İyi Pratikler

Pratik Açıklama
Maksimum 2-3 seviye derinlik Daha derin ilişkiler için üst düzey kaynak kullanın.
Bağımsız kaynak ilkesi Kaynak kendi başına anlamlıysa, üst düzey kaynak yapın.
Hibrit yaklaşım Alt kaynak + filtrelenmiş üst düzey kaynak birlikte sunun.
expand/include parametreleri İlişkili veriyi URI'de değil, query parametresiyle genişletin.
Beyaz liste doğrulaması include parametrelerini mutlaka doğrulayın (DoS koruması).
Eager Loading N+1 problemini Include ile çözün.
Minimal + genişletilmiş temsil Varsayılan minimal, expand ile genişletilmiş.

8. Gerçek Dünya Örneği

İyi Tasarlanmış:

text

GET /api/v1/orders?userId=123               → Filtrelenmiş
GET /api/v1/orders/456?expand=customer,items → Genişletilmiş
GET /api/v1/order-items/789                 → Bağımsız kaynak
GET /api/v1/order-items/789/product         → 2 seviye

Kaçınılması Gereken:

text

❌ /api/v1/users/123/orders/456/items/789/product/category/101/reviews

Doğrusu:

text

✅ /api/v1/products/789
✅ /api/v1/products/789/category
✅ /api/v1/products/789/reviews

Sonuç

REST API'lerinde ilişkisel kaynakların modellenmesi, API'nizin uzun vadeli başarısını belirleyen kritik bir karardır. Alt kaynaklar ilişkiyi net gösterirken, üst düzey kaynaklar bağımsızlık ve esneklik sunar. En iyi yaklaşım, hibrit model kullanmak ve maksimum 2-3 seviye derinlik ile sınırlamaktır. expand/include parametreleriyle ilişkili veriyi URI'yi kirletmeden genişletebilir, beyaz liste doğrulaması ile güvenliği sağlayabilir, Eager Loading ile N+1 problemini çözebilirsiniz. Unutmayın: İyi bir REST API, veritabanı şemasının HTTP üzerinden yansıması değil, iş alanının doğal ve kullanılabilir bir modellemesidir.

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