<

Flutter'da Feature Bazlı Klasör Yapısı: Büyüyen Uygulamada Bağımlılık Yönü

Flutter projesi ilk haftalarda neredeyse her klasör düzeniyle yönetilebilir. Sorun, uygulama kırk ekrana ulaştığında başlar: screens, models, services ve widgets klasörleri şişer, bir özelliği değiştirmek için beş farklı dizinde dolaşmak gerekir. Büyüyen uygulamalarda bizim için işe yarayan yaklaşım, katman yerine özelliğe göre gruplamak.

Katman bazlı yapının sınırı

Katman bazlı düzende "sepet" özelliğine ait kod dört klasöre dağılır. Özelliği silmek, taşımak ya da başka bir uygulamada yeniden kullanmak zorlaşır. Ayrıca her şey herkesi import edebildiği için bağımlılıklar kontrolsüzce çoğalır; bir ekranın model dosyasından başka bir özelliğin servisine uzanan zincirler fark edilmeden oluşur.

Feature bazlı temel düzen

Her özellik kendi klasöründe yaşar ve içinde kendi katmanlarını taşır. Tipik bir yapı şöyle görünür:

  • lib/features/cart/data: API istemcisi, DTO'lar ve repository uygulaması.
  • lib/features/cart/domain: saf Dart modelleri ve repository arayüzleri.
  • lib/features/cart/presentation: sayfalar, widget'lar ve durum yönetimi (bloc, riverpod veya tercih ettiğiniz çözüm).
  • lib/core: tema, HTTP altyapısı, hata sınıfları, yönlendirme gibi özellik bilmeyen ortak kod.
  • lib/shared: birden fazla özelliğin kullandığı, iş kuralı içermeyen widget'lar.

Asıl mesele: bağımlılık yönü

Klasör adlarından daha önemlisi kimin kimi import edebildiği. Bizim kuralımız şu: presentation, domain'e bağımlıdır; data, domain'e bağımlıdır; domain ise hiçbir şeye bağımlı değildir. Domain katmanında package:flutter veya package:dio import'u görüyorsanız yön bozulmuştur.

Özellikler arası bağımlılıkta da benzer bir disiplin gerekir. Bir özellik başka bir özelliğin data klasörüne doğrudan uzanmamalı; ihtiyaç duyduğu şeyi o özelliğin domain arayüzü üzerinden almalı. Sepet ekranının kullanıcı bilgisine ihtiyacı varsa, auth özelliğinin repository arayüzünü kullanır, HTTP çağrısını kendisi yazmaz.

Bu kuralları otomatik denetlemek

Kurallar yalnızca dokümanda kalırsa üç sprint sonra çiğnenir. Dart'ta bunu ucuza denetlemenin yolu, bir test içinde lib/features/*/domain altındaki dosyaları tarayıp yasak import'ları arayan küçük bir betik yazmak. dart_code_metrics gibi araçlar ya da import kısıtlayan lint paketleri de aynı işi görür. Önemli olan ihlalin CI'da yakalanması, kod incelemesinde birinin dikkatine bırakılmaması.

Yönlendirme ve bağımlılık enjeksiyonu

Özellikler birbirini doğrudan bilmeden gezinebilsin diye rota tanımlarını merkezi bir yerde, yalnızca yol adlarıyla tutuyoruz. go_router kullanan projelerde her özellik kendi GoRoute listesini dışarı açar, core/router bunları birleştirir. Bağımlılık enjeksiyonunda da aynı mantık geçerli: her özellik kendi kayıt fonksiyonunu sağlar, uygulama açılışında hepsi sırayla çağrılır. Böylece bir özelliği çıkarmak, bir kayıt satırını silmek kadar basit olur.

Üç beş ekranlı bir kurum içi uygulama için domain katmanı ve arayüz soyutlamaları fazla ağır olabilir. Bu durumda yalnızca feature klasörlerini koruyup içini data ve presentation diye ikiye bölmek yeterli. Yapıyı uygulamanın büyüklüğüne göre seçin, sonra gerektiğinde domain katmanını ekleyin; klasör düzeni taşınabilir, ama bağımlılık karmaşası taşınmaz.

Mevcut bir uygulamayı tek seferde yeniden düzenlemeye çalışmak genellikle yarım kalır. Biz yeni yazılan her özelliği yeni yapıda açıyor, eski kodu ise dokunduğumuz ölçüde taşıyoruz. Önce en az bağımlılığı olan özellik, örneğin ayarlar ya da profil, taşınır; ortak kod core ve shared altına ayrıldıkça sonraki taşımalar kolaylaşır. Her taşıma küçük bir pull request olursa geri almak da kolay olur.

Mevcut Flutter uygulamanız büyüdükçe yavaşlıyor ya da yeni özellik eklemek her seferinde bir şeyleri bozuyorsa, iletişim sayfamızdan bize yazın. Kod tabanınıza bakıp hangi yapıyı hangi sırayla toparlayacağınızı birlikte planlayalım.

📅 Yayınlanma:  ·  Yakup Zengin