Controller mı, Minimal API mı? Hangisini ne zaman seçmeli?
İki farklı endpoint yazma yaklaşımını karşılaştırıyoruz: controller tabanlı yapı ve Minimal API. Hangisi ne zaman mantıklı, ikisi arasındaki fark neye dayanıyor, örneklerle netleştiriyoruz.
Bir önceki yazının sonunda küçük bir söz vermiştim: iki farklı endpoint yazma biçimini, yani controller tabanlı yapı ile Minimal API'yi karşılaştıracaktık. Bugün tam olarak bunu yapıyoruz. Ama baştan söyleyeyim, bu bir "kazanan-kaybeden" yazısı değil. İkisinin de haklı olduğu yerler var ve senior bir geliştirici olmanın büyük kısmı, "hangisi daha iyi?" sorusunu "burada hangisi daha uygun?" sorusuna çevirebilmekten geçiyor.
İkisi de Aynı Temelin Üstünde Duruyor
Önce zihnindeki en yaygın yanlış anlamayı düzeltelim. Minimal API, controller'ların "hafif" ya da "yarım" bir versiyonu değil. İkisi de aynı ASP.NET Core temelinin üzerine kurulu; aynı routing altyapısını, aynı dependency injection'ı, aynı middleware boru hattını kullanıyorlar. Aradaki fark motorun kendisinde değil, o motoru nasıl paketlediğinde.
Bunu şöyle düşün: aynı mutfak, aynı ocak, aynı malzemeler. Controller yaklaşımı, her yemeği ayrı bir tarif kartına yazıp düzenli bir kutuda saklamak gibi. Minimal API ise tarifi doğrudan tezgahın üstündeki deftere, elin altında dursun diye yazmak gibi. Yemek aynı yemek; değişen tek şey, tarifi nereye ve ne kadar törenle yazdığın.
Controller Tabanlı Yapı Nasıl Görünüyor?
İkinci yazıda kurduğumuz şablon zaten controller tabanlıydı. Bu yaklaşımda her endpoint grubu, kendi sınıfında yaşar. Örneğin kullanıcılarla ilgili işlemleri tutan bir sınıf şöyle görünür:
[ApiController]
[Route("users")]
public class UsersController : ControllerBase
{
[HttpGet]
public IActionResult GetAll()
{
return Ok(new[] { "Anna", "Tom" });
}
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
return Ok($"Kullanıcı {id}");
}
}
Burada birkaç şey dikkatini çekmiş olmalı. Sınıfın başındaki [Route("users")], bu sınıftaki tüm endpoint'lerin /users altında toplanacağını söylüyor. Metotların başındaki [HttpGet] ve [HttpGet("{id}")] ise hangi HTTP metoduna ve hangi alt adrese cevap vereceklerini belirliyor. [ApiController] ise .NET'e "burası bir API controller'ı, bana bazı kolaylıkları otomatik ver" demenin yolu; model doğrulama hatalarını otomatik yakalaması gibi güzellikler bununla geliyor.
Bu yapının en belirgin özelliği düzen. Her şeyin bir yeri var: kullanıcı işlemleri UsersController'da, ürün işlemleri ProductsController'da. Projeye yeni katılan bir geliştirici, dosya adına bakarak nerede ne olduğunu tahmin edebilir. Proje büyüdükçe bu tahmin edilebilirlik altın değerinde.
Minimal API Nasıl Görünüyor?
Şimdi aynı iki endpoint'i Minimal API ile yazalım. Burada ayrı bir sınıfa ihtiyacımız yok; endpoint'leri doğrudan Program.cs içinde tanımlıyoruz:
app.MapGet("/users", () =>
{
return new[] { "Anna", "Tom" };
});
app.MapGet("/users/{id}", (int id) =>
{
return $"Kullanıcı {id}";
});
Farkı hissettin mi? Ortada ne sınıf var, ne attribute, ne de tören. app.MapGet diyorsun, adresi veriyorsun, ne dönecekse onu yazıyorsun. Metot içine id parametresini koyduğunda .NET bunu adresteki {id} ile eşleştirmeyi kendisi hallediyor. Daha az satır, daha az dosya, konuya daha hızlı giriş.
Bu sadelik, özellikle küçük servislerde çok tatlı bir deneyim sunuyor. Tek bir işi olan, üç beş endpoint'ten ibaret bir mikroservis yazıyorsan, koca bir controller iskeleti kurmak gereksiz ağırlık gibi gelebilir. Minimal API tam da bu durumlar için var.
Peki Fark Sadece Görünüşte mi?
İlk bakışta "biri daha kısa yazılıyor" gibi görünüyor ama iş biraz daha derin. Asıl fark, kodun proje büyüdükçe nasıl davrandığında ortaya çıkıyor.
Düzen ve Ölçeklenme
Üç endpoint'in varken Minimal API harika. Ama endpoint sayısı otuza, elliye çıktığında Program.cs dosyası devasa bir listeye dönüşmeye başlar. Her şey tek dosyada üst üste yığıldığında okumak zorlaşır. Elbette Minimal API'yi de ayrı dosyalara bölmenin yolları var, ama bunu yapmaya başladığın anda aslında controller'ların sana bedavaya verdiği düzeni elle yeniden inşa ediyorsun demektir. Controller yaklaşımı bu düzeni doğası gereği dayatır; bu bazen fazladan tören gibi görünse de büyük projelerde bir sigortadır.
Tanıdık Gelen Yapılar
Controller'lar uzun yıllardır ASP.NET dünyasının bel kemiği. Bu da şu demek: internetteki örneklerin, StackOverflow cevaplarının, kurs içeriklerinin büyük çoğunluğu controller tabanlı. Yeni bir ekibe katıldığında karşına çıkacak kodun büyük ihtimalle controller olması, bu yaklaşımı öğrenmeyi pratik bir zorunluluk haline getiriyor. Minimal API görece daha yeni; hızla yaygınlaşıyor ama henüz aynı olgunlukta bir örnek denizine sahip değil.
Performans
Bir dönem "Minimal API daha hızlı" söylemi çok konuşuldu. Bunda bir gerçeklik payı vardı, çünkü Minimal API'de aradaki bazı katmanlar daha ince. Ama günümüzde bu fark, gerçek bir uygulamanın toplam performansı içinde çoğu zaman ölçülemeyecek kadar küçük. Yani "daha hızlı olsun diye Minimal API seçtim" cümlesi, çok özel senaryolar dışında pek sağlam bir gerekçe değil. Kararını okunabilirlik ve bakım kolaylığı üzerinden vermen çok daha isabetli olur.
O Zaman Ben Hangisini Seçeyim?
Kesin kurallar sevmesem de, işe yeni başlayan biri için pratik bir pusula verebilirim.
Öğrenme aşamasındaysan ve amacın .NET dünyasının genel geçer kalıplarını kavramaksa, controller ile başla. Çünkü karşına çıkacak kodun çoğu böyle olacak ve bu yapıyı okuyabilmek işe girdiğinde seni rahatlatacak. Bu seride de biz ağırlıklı olarak controller tabanlı ilerleyeceğiz; sebebi tam olarak bu.
Küçük, tek amaçlı bir servis yazıyorsan, mesela bir webhook alıcısı ya da üç endpoint'lik bir yardımcı API, Minimal API ile git. Gereksiz iskelet kurmadan işini hızlıca bitirirsin.
Büyük, birden çok ekibin dokunacağı, uzun ömürlü bir proje kuruyorsan, controller'ların dayattığı düzen sana uzun vadede huzur verir. Başta biraz fazla dosya gibi gelse de, altı ay sonra o düzene şükredersin.
Ve şunu da unutma: ikisini aynı projede yan yana kullanabilirsin. Ana iş mantığını controller'larda tutup, tek satırlık bir sağlık kontrolü endpoint'ini Minimal API ile yazmanda hiçbir sakınca yok. Bunlar birbirini dışlayan tercihler değil, aynı alet çantasındaki iki farklı alet.
Küçük Bir Deneme
Bir sonraki yazıya geçmeden önce şunu yap: İkinci yazıda kurduğun projeye, hem Minimal API tarzı bir /ping endpoint'i ekle, hem de küçük bir controller sınıfı oluşturup içine bir /status endpoint'i koy. İkisini de çalıştır, tarayıcıdan çağır. Aynı sonuca iki farklı yoldan ulaştığını kendi gözlerinle gör. Bu küçük deney, aradaki farkı herhangi bir paragraftan çok daha net oturtacak.
Bir sonraki yazıda ilk gerçek controller'ımızı ciddi ciddi yazmaya başlıyoruz: routing'in inceliklerini, attribute'ların ne işe yaradığını ve action metotlarının nasıl kurgulandığını adım adım işleyeceğiz. Bugün kavradığın bu "aynı temel, farklı üslup" fikrini bir kenara not et; oradan devam edeceğiz.