Ana içeriğe geç

Subscription ve Filtering

GraphQL subscription’lar gerçek zamanlı veri akışı sağlar, filtering ise istemciye esnek sorgulama imkanı verir; yanlış kullanımlar bellek sızıntılarına ve performans sorunlarına yol açar.


1. Subscription’da Topic Yönetimi Yapmamak

Yanlış Kullanım: Tüm event’leri tüm istemcilere göndermek.

public class Subscription
{
    [Subscribe]
    public OrderDto OnOrderCreated([EventMessage] OrderDto order) => order;
    // Tüm kullanıcılar tüm siparişleri alır
}

İdeal Kullanım: Topic ile filtrelenmiş subscription yapın.

public class Subscription
{
    [Subscribe]
    [Topic("{userId}_orders")]
    public OrderDto OnMyOrderCreated(
        [EventMessage] OrderDto order,
        int userId) => order;
}

// Event gönderimi
public class OrderService
{
    private readonly ITopicEventSender _sender;

    public async Task<OrderDto> CreateAsync(CreateOrderInput input)
    {
        var order = await SaveOrderAsync(input);
        await _sender.SendAsync($"{order.CustomerId}_orders", order);
        return order;
    }
}

2. Filtering’i Manuel Yazmak

Yanlış Kullanım: Her alan için manuel filtre parametresi eklemek.

public class Query
{
    public async Task<List<ProductDto>> GetProducts(
        string? name, decimal? minPrice, decimal? maxPrice,
        int? categoryId, bool? isActive,
        [Service] AppDbContext db)
    {
        var query = db.Products.AsQueryable();
        if (name != null) query = query.Where(p => p.Name.Contains(name));
        if (minPrice != null) query = query.Where(p => p.Price >= minPrice);
        // ... her alan için ayrı kontrol
        return await query.Select(p => new ProductDto(p)).ToListAsync();
    }
}

İdeal Kullanım: HotChocolate filtering middleware kullanın.

builder.Services
    .AddGraphQLServer()
    .AddQueryType<Query>()
    .AddFiltering()
    .AddSorting()
    .AddProjections();

public class Query
{
    [UseProjection]
    [UseFiltering]
    [UseSorting]
    public IQueryable<ProductDto> GetProducts([Service] AppDbContext db)
        => db.Products.Select(p => new ProductDto
        {
            Id = p.Id,
            Name = p.Name,
            Price = p.Price,
            CategoryName = p.Category.Name
        });
}

// İstemci sorgusu:
// {
//   products(where: { price: { gte: 100 }, categoryName: { eq: "Elektronik" } },
//            order: { price: DESC }) {
//     name, price, categoryName
//   }
// }

3. Pagination Kullanmamak

Yanlış Kullanım: Tüm listeyi döndürmek.

public class Query
{
    public async Task<List<ProductDto>> GetProducts([Service] AppDbContext db)
        => await db.Products.Select(p => new ProductDto(p)).ToListAsync();
    // 100.000 kayıt tek seferde döner
}

İdeal Kullanım: Cursor-based pagination ile sınırlı veri döndürün.

public class Query
{
    [UsePaging(IncludeTotalCount = true, MaxPageSize = 50)]
    [UseFiltering]
    [UseSorting]
    public IQueryable<ProductDto> GetProducts([Service] AppDbContext db)
        => db.Products.Select(p => new ProductDto
        {
            Id = p.Id,
            Name = p.Name,
            Price = p.Price
        });
}

// İstemci sorgusu:
// {
//   products(first: 10, after: "cursor123") {
//     nodes { id, name, price }
//     pageInfo { hasNextPage, endCursor }
//     totalCount
//   }
// }

4. Subscription Bağlantı Yönetimini İhmal Etmek

Yanlış Kullanım: WebSocket bağlantı sınırı ve timeout ayarı yapmamak.

builder.Services
    .AddGraphQLServer()
    .AddSubscriptionType<Subscription>();

app.UseWebSockets();
// Varsayılan ayarlar, bağlantı limiti yok

İdeal Kullanım: WebSocket ve subscription ayarlarını yapılandırın.

builder.Services
    .AddGraphQLServer()
    .AddSubscriptionType<Subscription>()
    .AddInMemorySubscriptions();

app.UseWebSockets(new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromSeconds(30)
});

// Redis ile ölçekleme (birden fazla sunucu)
builder.Services
    .AddGraphQLServer()
    .AddSubscriptionType<Subscription>()
    .AddRedisSubscriptions(sp =>
        sp.GetRequiredService<IConnectionMultiplexer>());

5. Field Middleware Sırasını Yanlış Yapmak

Yanlış Kullanım: Middleware sırasını karıştırmak.

public class Query
{
    [UseFiltering]    // Yanlış sıra
    [UsePaging]       // Paging filtering'den önce gelmeli
    [UseProjection]   // Projection en son çalışmalı
    public IQueryable<Product> GetProducts([Service] AppDbContext db)
        => db.Products;
}

İdeal Kullanım: Middleware’leri doğru sırada uygulayın.

public class Query
{
    [UsePaging(IncludeTotalCount = true)]  // 1. Paging (en dış)
    [UseProjection]                         // 2. Projection
    [UseFiltering]                          // 3. Filtering
    [UseSorting]                            // 4. Sorting (en iç)
    public IQueryable<ProductDto> GetProducts([Service] AppDbContext db)
        => db.Products.Select(p => new ProductDto
        {
            Id = p.Id,
            Name = p.Name,
            Price = p.Price
        });
}
// Sıra: Sorting → Filtering → Projection → Paging
// Attribute'lar ters sırada yazılır (en dıştaki en üstte)