REST API’ler modern uygulamaların omurgasıdır. Mikroservis, mobil uygulama arka ucu ya da üçüncü taraf entegrasyonu kuruyor olun, iyi tasarlanmış bir .NET API’si yük altında ayakta kalan bir platformla zorlanan bir platform arasındaki farkı belirler. Bu rehber, bir .NET REST API’sini üretimde hızlı ve güvenilir tutan teknikleri anlatıyor.
Neden .NET
Modern .NET, ölçeklenebilir API’ler için güçlü bir seçenek:
- Performans: ASP.NET Core, bağımsız karşılaştırmalarda (TechEmpower gibi) üst sıralarda yer alır
- Async temelli yapı:
async/awaitile çok sayıda eş zamanlı bağlantı az iş parçacığıyla taşınır - Tek ekosistem: web, servis ve arka plan işleri aynı kütüphanelerle
- Uzun süreli destek: çift numaralı sürümler (.NET 8, .NET 10) üç yıl LTS desteği alır; tek numaralı sürümler daha kısa desteklenir, üretimde LTS tercih edin
Mimari ilkeler
Kod yazmadan önce sağlam bir iskelet kurun.
Sorumlulukların ayrılması
Projeyi net katmanlara ayırın:
- Sunum katmanı: HTTP uçları
- Uygulama katmanı: iş akışları ve orkestrasyon
- Alan katmanı: iş kuralları ve varlıklar
- Altyapı katmanı: veritabanı, dış servisler, mesajlaşma
Bu yapı kod tabanı büyüdükçe bakımı ve testi kolaylaştırır. Bağımlılıklar içe doğru akar: alan katmanı hiçbir altyapı paketini bilmez.
Bağımlılık enjeksiyonu
ASP.NET Core’un yerleşik DI kapsayıcısı bağımlılıkları gevşek tutar ve testi kolaylaştırır. Güncel .NET’te kayıtlar doğrudan Program.cs içinde yapılır:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
Asenkron işleme
API’de iş parçacığını asla bloklamayın. .Result ya da .Wait() yerine baştan sona async/await kullanın ve iptal belirtecini (cancellation token) aşağıya kadar taşıyın:
[HttpGet("{id:guid}")]
public async Task<ActionResult<UserDto>> GetUserById(Guid id, CancellationToken ct)
{
var user = await _userService.GetUserByIdAsync(id, ct);
return user is null ? NotFound() : Ok(user);
}
Böylece veritabanını bekleyen istekler iş parçacığı tutmaz ve aynı sunucu çok daha fazla eş zamanlı isteği karşılar. Kullanıcı isteği iptal ettiğinde sorgu da iptal olur.
Uygulamada iyi pratikler
1. EF Core’u verimli kullanın
Entity Framework Core güçlüdür ama ölçekte dikkat ister:
public sealed class UserRepository(AppDbContext db) : IUserRepository
{
public Task<UserSummary?> GetSummaryAsync(Guid userId, CancellationToken ct) =>
db.Users
.AsNoTracking() // salt okuma: değişiklik takibi yok
.Where(u => u.Id == userId)
.Select(u => new UserSummary(u.Id, u.Name, u.Orders.Count)) // yalnız gereken sütunlar
.FirstOrDefaultAsync(ct);
public Task<List<UserListItem>> GetActiveAsync(DateTimeOffset? after, int pageSize, CancellationToken ct) =>
db.Users
.AsNoTracking()
.Where(u => u.IsActive && (after == null || u.CreatedAt < after))
.OrderByDescending(u => u.CreatedAt)
.Take(pageSize) // imleç tabanlı sayfalama
.Select(u => new UserListItem(u.Id, u.Name, u.CreatedAt))
.ToListAsync(ct);
}
Temel kurallar:
- Salt okuma sorgularında
AsNoTracking() - Varlık yerine projeksiyonla yalnız gereken sütunları çekin
- Tembel yükleme (lazy loading) yerine açık
Includeya da projeksiyon; N+1 sorgusundan kaçının - Büyük listelerde
Skipyerine imleç tabanlı sayfalama: derin sayfalardaSkipher seferinde atlanan satırları da okur
2. Önbellek
Veritabanı yükünü azaltmak için önbellek kullanın. Tek sunuculu senaryoda bellek içi önbellek yeter; birden fazla kopya çalışıyorsa dağıtık önbellek gerekir. Microsoft.Extensions.Caching.Hybrid paketindeki HybridCache ikisini birleştirir ve aynı anahtar için eş zamanlı istekleri tek bir veritabanı çağrısında toplar:
builder.Services.AddStackExchangeRedisCache(o =>
o.Configuration = builder.Configuration.GetConnectionString("Redis"));
builder.Services.AddHybridCache();
public sealed class UserService(IUserRepository repo, HybridCache cache) : IUserService
{
public ValueTask<UserDto?> GetUserByIdAsync(Guid id, CancellationToken ct) =>
cache.GetOrCreateAsync(
$"user:{id}",
async token => await repo.GetDtoAsync(id, token),
new HybridCacheEntryOptions { Expiration = TimeSpan.FromMinutes(10) },
cancellationToken: ct);
}
Önbellekten önce neyin ne kadar bayat olabileceğine karar verin; kullanıcı güncellendiğinde ilgili anahtarı silmek süre dolmasını beklemekten daha doğrudur.
3. Yanıt sıkıştırma
Bant genişliğini azaltmak için sıkıştırmayı açın:
builder.Services.AddResponseCompression(o =>
{
o.EnableForHttps = true;
o.Providers.Add<BrotliCompressionProvider>();
o.Providers.Add<GzipCompressionProvider>();
});
app.UseResponseCompression();
Uygulama bir ters vekil ya da CDN arkasındaysa sıkıştırmayı orada yapmak çoğu zaman daha verimlidir.
4. Hız sınırı
API’yi kötüye kullanıma karşı koruyun ve kaynakları adil paylaştırın. ASP.NET Core’un yerleşik hız sınırlayıcısı:
builder.Services.AddRateLimiter(o =>
{
o.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
o.AddFixedWindowLimiter("per-minute", l =>
{
l.PermitLimit = 100;
l.Window = TimeSpan.FromMinutes(1);
});
});
app.UseRateLimiter();
app.MapControllers().RequireRateLimiting("per-minute");
Birden fazla kopya çalışıyorsa bu sınır kopya başınadır; küme geneli sınır için API geçidini ya da Redis tabanlı bir sayacı kullanın.
Örnek: sezonluk yük alan bir e-ticaret API’si
- CQRS: okuma ve yazmayı ayırarak ikisini bağımsız ölçeklemek
- Mesaj kuyruğu: sipariş işleme ve e-posta gibi uzun işleri arka plan servislerine devretmek
- Veritabanı: okuma kopyaları, doğru indeksler, gerekirse bölümleme
- API sürümleme: değişikliği istemcileri kırmadan yapabilmek
- İzleme: gecikme, hata oranı ve kuyruk derinliği
Dağıtım
Konteyner
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["MyApi.csproj", "./"]
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish --no-restore
FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app/publish .
USER app
ENTRYPOINT ["dotnet", "MyApi.dll"]
USER app satırı uygulamayı kök kullanıcı olmadan çalıştırır; resmî .NET imajları bu kullanıcıyı hazır getirir.
Yatay ölçekleme
API’yi durumsuz tasarlayın ki yük dengeleyicinin arkasında birden fazla kopya çalışabilsin. Oturum ve önbellek gibi durumlar süreç içinde değil, dağıtık depoda tutulur. Uygulamanın durumunu (sağlıklı, hazır) yük dengeleyiciye MapHealthChecks ile bildirin.
İzleme ve gözlemlenebilirlik
Ölçmediğinizi iyileştiremezsiniz. OpenTelemetry, .NET’te metrik, iz ve kayıt için satıcıdan bağımsız standarttır:
builder.Services.AddOpenTelemetry()
.ConfigureResource(r => r.AddService("users-api"))
.WithTracing(t => t.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter())
.WithMetrics(m => m.AddAspNetCoreInstrumentation()
.AddRuntimeInstrumentation()
.AddOtlpExporter());
İzlenecek temel metrikler:
- Yanıt süresi yüzdelikleri (p95, p99)
- Hata oranı ve türleri
- Veritabanı sorgu süreleri
- Önbellek isabet oranı
- Kaynak kullanımı
Sonuç
.NET ile ölçeklenebilir REST API geliştirmek mimariye, performansa ve işletme disiplinine birlikte dikkat etmeyi gerektirir. Katmanlı yapı, baştan sona async, verimli veri erişimi, önbellek ve izleme ile büyümeyi kaldıran API’ler kurarsınız.
Önemli bir API projesi planlıyorsanız bize yazın.
ByteGurus
Yazar