Web API Tasarımı ve RESTful Mimariler: Sağlam API'lar İnşa Etmek
Günümüz yazılım dünyasında, uygulamalar arası iletişimin büyük bir kısmı HTTP tabanlı Web API'leri üzerinden gerçekleşir. REST (Representational State Transfer), bu API'leri tasarlamak için en yaygın kullanılan mimari stildir. REST, bir dizi kısıtlama (constraint) ile sistemin ölçeklenebilirliğini, performansını ve bakım kolaylığını artırmayı hedefler. Bu yazıda, RESTful bir Web API'si tasarlamanın temel prensiplerini, HTTP metotlarının doğru kullanımını, URI tasarım kurallarını, güvenlik, versiyonlama, hata yönetimi ve dokümantasyon gibi kritik konuları .NET üzerinden örneklerle ele alacağız.
1. REST Nedir? Temel Prensipler
REST, Roy Fielding tarafından 2000 yılında doktora tezinde tanımlanmıştır. RESTful bir API, aşağıdaki altı temel kısıtlamayı (constraints) karşılamalıdır:
-
Client-Server (İstemci-Sunucu): İstemci ve sunucu birbirinden bağımsızdır. Sunucu, yalnızca API'yi sağlar; istemci ise bu API'yi tüketir. Bu, her iki tarafın da ayrı ayrı geliştirilmesine olanak tanır.
-
Stateless (Durumsuz): Sunucu, istemcinin durumunu (session) saklamaz. Her istek, kimlik doğrulama ve yetkilendirme için gerekli tüm bilgileri içermelidir. Bu, ölçeklenebilirliği artırır.
-
Cacheable (Önbelleklenebilir): Sunucu yanıtları, önbelleklenebilir olup olmadığını belirtmelidir. Bu, ağ trafiğini azaltır ve performansı artırır.
-
Uniform Interface (Tutarlı Arayüz): Tüm kaynaklara erişim aynı kurallarla yapılır (URI, HTTP metotları, medya tipleri). Bu, API'nin anlaşılabilirliğini ve kullanılabilirliğini artırır.
-
Layered System (Katmanlı Sistem): İstemci, API ile doğrudan iletişim kurduğunu düşünür; ancak arada güvenlik, yük dengeleme veya önbellekleme katmanları olabilir.
-
Code on Demand (Opsiyonel): Sunucu, istemciye çalıştırılabilir kod (ör. JavaScript) gönderebilir.
RESTful bir API, bu prensiplere uygun olarak kaynakları (resources) HTTP üzerinden sunar.
2. Kaynak (Resource) Odaklı Tasarım
REST'in merkezinde kaynaklar (resources) vardır. Kaynak, bir varlığı (entity) veya bir hizmeti temsil eder. Her kaynağın bir URI (Uniform Resource Identifier) ile tanımlanan bir adresi vardır.
Kaynak İsimlendirme Kuralları:
-
Çoğul isimler kullanın:
api/users,api/products,api/orders(Tekil kullanmayın:api/user). -
Alt kaynaklar (nested resources): İlişkili kaynakları URI içinde gösterin. Örn:
api/users/{userId}/orders. -
Hiyerarşiyi koruyun: Fazla derin iç içe URI'lerden kaçının. Maksimum 3 seviye önerilir.
-
Sürüm bilgisini URI'ye ekleyin:
api/v1/users,api/v2/products. -
Eylemleri (action) URI'de kullanmayın: URI'ler fiil değil, isim içermelidir.
Kötü URI Örnekleri:
-
api/getUsers -
api/createUser -
api/updateUser/123 -
api/deleteOrder/456
İyi URI Örnekleri:
-
api/users(GET) -
api/users(POST) -
api/users/123(PUT, PATCH, DELETE) -
api/users/123/orders(GET)
3. HTTP Metotları (Verbs) ve Anlamları
REST, HTTP metotlarını standart anlamlarıyla kullanır:
| HTTP Metodu | Açıklama | Idempotent? | Güvenli? | Örnek Kullanım |
|---|---|---|---|---|
| GET | Kaynağı okur (sorgular). | ✅ Evet | ✅ Evet | GET /api/users/123 |
| POST | Yeni bir kaynak oluşturur. | ❌ Hayır | ❌ Hayır | POST /api/users |
| PUT | Kaynağın tamamını günceller (veya yoksa oluşturur). | ✅ Evet | ❌ Hayır | PUT /api/users/123 |
| PATCH | Kaynağın belirli alanlarını günceller. | ❌ Hayır (genelde) | ❌ Hayır | PATCH /api/users/123 |
| DELETE | Kaynağı siler. | ✅ Evet | ❌ Hayır | DELETE /api/users/123 |
| HEAD | GET ile aynı, ancak yanıt gövdesi (body) yok. | ✅ Evet | ✅ Evet | Kaynak varlığını kontrol etmek |
| OPTIONS | Kaynak için desteklenen metotları döner. | ✅ Evet | ✅ Evet | CORS preflight |
Idempotent (Yinelenebilir): Aynı isteğin birden fazla kez gönderilmesinin, tek bir kez gönderilmesiyle aynı sonucu doğurmasıdır. GET, PUT, DELETE, HEAD, OPTIONS idempotenttir. POST ve PATCH genelde idempotent değildir.
Güvenli (Safe): Kaynağın durumunu değiştirmeyen metotlardır. GET, HEAD, OPTIONS güvenlidir.
4. Stateless (Durumsuz) İletişim
Sunucu, istemci durumunu (session) saklamaz. Her istek, gerekli tüm kimlik doğrulama bilgilerini (token, API key) içermelidir. Bu, sunucunun ölçeklenebilirliğini artırır.
JWT (JSON Web Token) ile Kimlik Doğrulama:
csharp
// JWT token üretimi
public string GenerateToken(string userId)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Secret"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, userId) }),
Expires = DateTime.UtcNow.AddHours(1),
Issuer = _configuration["Jwt:Issuer"],
Audience = _configuration["Jwt:Audience"],
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
// Her istekte token'ı doğrula (Middleware ile)
app.UseAuthentication();
app.UseAuthorization();
5. Versiyonlama (Versioning)
API'ler zamanla değişir. Versiyonlama, eski istemcilerin çalışmaya devam etmesini sağlar.
Yaygın Versiyonlama Stratejileri:
-
URI Path:
api/v1/usersveapi/v2/users. -
Query Parameter:
api/users?version=1. -
Header:
api-version: 1.0. -
Content Negotiation:
Accept: application/vnd.myapi.v1+json.
.NET'te URI Path Versiyonlama (En Yaygın):
csharp
// Program.cs
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
});
// Controller
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class UsersController : ControllerBase
{
[HttpGet("{id}")]
[MapToApiVersion("1.0")]
public IActionResult GetV1(int id) { ... }
[HttpGet("{id}")]
[MapToApiVersion("2.0")]
public IActionResult GetV2(int id) { ... }
}
6. Hata Yönetimi (Error Handling)
Hata durumlarında, anlamlı HTTP status kodları ve hata detayları döndürülmelidir.
Standart HTTP Status Kodları:
| Status Code | Anlamı | Açıklama |
|---|---|---|
| 200 OK | Başarılı | İstek başarıyla tamamlandı. |
| 201 Created | Oluşturuldu | POST başarılı, yeni kaynak oluşturuldu. |
| 204 No Content | İçerik yok | Silme işlemi başarılı, body yok. |
| 400 Bad Request | Geçersiz istek | İstek formatı veya veri doğrulama hatası. |
| 401 Unauthorized | Yetkisiz | Kimlik doğrulama gerekli veya başarısız. |
| 403 Forbidden | Yasak | Kimlik doğrulandı ancak yetkisi yok. |
| 404 Not Found | Bulunamadı | Kaynak mevcut değil. |
| 405 Method Not Allowed | Metot izin verilmiyor | HTTP metodu desteklenmiyor. |
| 409 Conflict | Çakışma | Kaynak zaten var (unique constraint). |
| 422 Unprocessable Entity | İşlenemez varlık | İsteğin formatı doğru ancak iş mantığı başarısız. |
| 500 Internal Server Error | Sunucu hatası | Beklenmedik hata. |
.NET'te Global Exception Handler ile Hata Yönetimi:
csharp
// Middleware
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Beklenmedik hata!");
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var response = new
{
status = "error",
message = exception.Message,
traceId = context.TraceIdentifier
};
context.Response.ContentType = "application/json";
context.Response.StatusCode = exception switch
{
NotFoundException => StatusCodes.Status404NotFound,
ValidationException => StatusCodes.Status400BadRequest,
UnauthorizedException => StatusCodes.Status401Unauthorized,
_ => StatusCodes.Status500InternalServerError
};
return context.Response.WriteAsync(JsonSerializer.Serialize(response));
}
}
7. Dokümantasyon (OpenAPI / Swagger)
API dokümantasyonu, hem geliştiriciler hem de otomasyon araçları (Postman, SDK üreteçleri) için hayati önem taşır. OpenAPI (eski adıyla Swagger) bu konuda standarttır.
.NET 8+ ile Swagger Entegrasyonu:
csharp
// Program.cs
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please enter token",
Name = "Authorization",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT",
Scheme = "bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{ new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, Array.Empty<string>() }
});
});
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"));
8. Güvenlik (Security)
API güvenliği, aşağıdaki katmanlarda sağlanır:
-
Kimlik Doğrulama (Authentication): Kullanıcının kim olduğunu doğrular. (JWT, API Key, OAuth2)
-
Yetkilendirme (Authorization): Kullanıcının hangi kaynaklara erişebileceğini belirler. (RBAC, Policy-based)
-
HTTPS: Tüm istekler HTTPS üzerinden yapılmalıdır.
-
Rate Limiting: Aşırı kullanımı önlemek için istek sınırlaması.
-
CORS: Sadece belirli kaynaklara (origins) erişim izni verin.
.NET'te Policy-based Yetkilendirme:
csharp
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
options.AddPolicy("UserOrAdmin", policy => policy.RequireClaim("permission", "can_read_users"));
});
[Authorize(Policy = "AdminOnly")]
[HttpGet("admin-data")]
public IActionResult GetAdminData() { ... }
9. Performans (Performance)
-
Pagination (Sayfalama): Büyük veri kümelerini sayfalara bölerek gönderin.
GET /api/products?page=1&pageSize=20 -
Filtering (Filtreleme): Belirli kriterlere göre veri döndürün.
GET /api/products?category=electronics&minPrice=100 -
Sorting (Sıralama): Sıralama seçeneği sunun.
GET /api/products?sort=price_desc -
Selecting (Alan Seçimi): Sadece istenen alanları döndürün.
GET /api/products?fields=id,name,price -
Caching (Önbellekleme): Sık değişmeyen verileri önbelleğe alın.
Cache-Control: public, max-age=300 -
GZIP/Deflate Sıkıştırma: Yanıt boyutunu küçültün.
.NET'te Pagination Örneği:
csharp
[HttpGet]
public async Task<IActionResult> GetProducts([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
{
var query = _context.Products.AsNoTracking();
var totalCount = await query.CountAsync();
var items = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
var response = new PagedResponse<Product>
{
Items = items,
Page = page,
PageSize = pageSize,
TotalCount = totalCount,
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
};
return Ok(response);
}
10. En İyi Pratikler ve Kaçınılması Gerekenler
| Pratik | Açıklama |
|---|---|
| Kaynaklara Uygun HTTP Metodu Kullanın | POST yerine PUT/PATCH, GET yerine POST kullanmayın. |
| Status Kodlarını Doğru Kullanın | 200, 201, 400, 404, 500... Anlamlarına uygun kullanın. |
| API Versiyonlamasını İhmal Etmeyin | Yeni değişiklikler eski istemcileri bozmasın. |
| Dokümantasyonu Güncel Tutun | Swagger/OpenAPI ile otomatik dokümantasyon sağlayın. |
| Validasyonu (Doğrulama) Yapın | Gelen veriyi her zaman doğrulayın. |
| Hata Mesajlarını Anlamlı Kılın | Sadece "Hata oluştu" demeyin, detay verin. |
| Loglama ve İzleme (Monitoring) Yapın | API'yi sürekli izleyin, hataları loglayın. |
| Güvenlik Açıklarını Kapatın | HTTPS, CORS, Rate Limiting, Input Validation. |
| Performansı Ölçün ve Optimize Edin | Yavaş sorguları tespit edip optimize edin. |
| API'nizi Tüketilebilir Hale Getirin | Açık, anlaşılır ve tutarlı bir tasarım yapın. |
Sonuç:
RESTful Web API tasarımı, disiplinli bir yaklaşım ve standartlara bağlı kalmayı gerektirir. Kaynak odaklı URI tasarımı, HTTP metotlarının doğru kullanımı, durumsuz iletişim, etkili hata yönetimi, versiyonlama, güvenlik ve performans optimizasyonu, başarılı bir API'nin temel yapı taşlarıdır.
.NET Core ve ASP.NET Core, bu prensipleri uygulamak için güçlü araçlar ve kütüphaneler sunar (Swagger, JWT, API Versioning, Global Exception Handling, Pagination). Doğru tasarlanmış bir API, hem geliştirme sürecini hızlandırır hem de uzun vadeli bakım maliyetini düşürür.
Unutmayın: İyi bir API, kullanıcılarının (geliştiricilerin) hayatını kolaylaştırır; kötü bir API ise onları başka çözümlere yönlendirir.