- Design Pattern
Design Pattern Kullanmak Her Zaman İyi Bir Fikir mi?
Design Pattern'ler kodu otomatik olarak daha iyi hale getirmez. Asıl önemli olan hangi problemi çözdüğünü, ne zaman kullanılması gerektiğini ve ne zaman kullanılmaması gerektiğini bilmektir.
Zeynep Baş6 dk okuma
Design Pattern Nedir?
Tekrar eden yazılım problemlerine yönelik kanıtlanmış çözüm yaklaşımları
Design Pattern, belirli bir yazılım problemini çözmek için kullanılan tekrar edilebilir bir tasarım yaklaşımıdır. Pattern doğrudan kopyalanacak bir kod parçası değil, probleme nasıl yaklaşılması gerektiğini anlatan bir rehberdir.
Tekrarlayan problemlere çözüm
Kod organizasyonu
Bağımlılıkların yönetimi
Değişime açık mimari
Sorumlulukların ayrıştırılması
Bir pattern kullanmak tek başına kodun daha kaliteli olduğu anlamına gelmez. Pattern'in gerçekten bir problemi çözüp çözmediği ve sisteme eklediği karmaşıklığın buna değip değmediği değerlendirilmelidir.
Problem varsa pattern düşünülmeli
Gereksiz abstraction oluşturulmamalı
Kod okunabilirliği korunmalı
Bakım maliyeti değerlendirilmelidir
Pattern Kullanmak Her Zaman İyi Bir Fikir mi?
Hayır. Pattern bir amaç değil, araçtır.
Bir geliştiricinin bildiği her pattern'i projede kullanmaya çalışması genellikle daha iyi bir architecture oluşturmaz. Aksine gereksiz abstraction katmanları oluşturarak kodun anlaşılmasını ve bakımını zorlaştırabilir.
Her problem Strategy gerektirmez
Her API Repository gerektirmez
Her state değişimi Observer gerektirmez
Her HTTP client Singleton olmak zorunda değildir
Senior seviyede önemli olan kaç pattern bildiğiniz değil, bir problemin pattern gerektirip gerektirmediğini değerlendirebilmektir.
Problem odaklı düşünmek
Trade-off değerlendirmek
Karmaşıklığı kontrol etmek
Gelecekteki değişiklikleri öngörmek
Overengineering Nedir?
Basit bir problemi gereğinden fazla karmaşık hale getirmek
Overengineering, henüz ihtiyaç olmayan bir esnekliği veya abstraction'ı sisteme erkenden eklemektir. Kod teorik olarak daha esnek hale gelirken pratikte okunması ve değiştirilmesi daha zor olabilir.
Gereksiz abstraction
Fazla interface
Gereksiz provider katmanları
Fazla dependency
Karmaşık klasör yapısı
Örneğin yalnızca iki farklı davranış bulunan basit bir component için Strategy Pattern oluşturmak teknik olarak mümkün olsa da mevcut problem bunu gerektirmiyorsa gereksiz bir karmaşıklık oluşturabilir.
Önce basit çözüm
Değişim ihtiyacını gözlemle
Tekrarlayan yapı oluşursa abstraction düşün
Pattern'i ihtiyaç ortaya çıktığında uygula
Strategy Pattern
Değişebilen algoritmaları birbirinden ayırmak
Strategy Pattern, aynı işi farklı yöntemlerle gerçekleştiren algoritmaları birbirinden ayırmayı sağlar. Böylece ana business logic, hangi algoritmanın kullanılacağına bağlı olmaktan çıkar.
Filtreleme stratejileri
Sıralama stratejileri
Validation stratejileri
Ödeme yöntemleri
Farklı hesaplama yöntemleri
React uygulamalarında farklı filtreleme veya sıralama davranışları zamanla artıyorsa Strategy Pattern tekrar eden koşulların ve büyük if-else bloklarının önüne geçebilir.
Strategy seçimi
Business logic ayrıştırma
Kolay test edilebilirlik
Yeni davranış ekleme kolaylığı
Provider Pattern
Bağımlılıkları component ağacında yönetmek
Provider Pattern, belirli bir değeri veya servisi component ağacındaki alt component'lere ulaştırmak için kullanılır. React Context API bu yaklaşımın en bilinen örneklerinden biridir.
Authentication
Theme
Configuration
Feature Flags
Global state
Provider kullanırken dikkat edilmesi gereken nokta, her state'i global hale getirmemektir. Gereksiz provider'lar component ağacını karmaşıklaştırabilir ve state yönetimini zorlaştırabilir.
Global olması gereken state'i seç
Local state'i local tut
Provider sayısını kontrol et
Dependency scope'unu düşün
Repository Pattern
Data access ile business logic'i birbirinden ayırmak
Repository Pattern, veri kaynağına erişimi uygulamanın geri kalanından soyutlar. Component veya business logic doğrudan fetch, Axios veya başka bir data source ile konuşmak yerine repository üzerinden veri alabilir.
API abstraction
Data access
Mock data
Test edilebilirlik
Backend değişikliklerine karşı izolasyon
Örneğin PDOS gibi bir uygulamada TaskRepository üzerinden task verilerine erişmek, component'lerin API detaylarını bilmesini engeller.
TaskRepository
DocumentRepository
GoalRepository
ProfileRepository
Observer Pattern
Bir değişiklik olduğunda ilgili yapıları haberdar etmek
Observer Pattern, bir nesnede meydana gelen değişikliğin ona bağlı diğer yapılara bildirilmesini sağlar. Event-driven sistemlerde ve reactive yapılarda sıkça karşılaşılan bir yaklaşımdır.
Event sistemleri
Notification
WebSocket
Realtime updates
State değişiklikleri
Frontend tarafında Observer yaklaşımını event listener yapılarında, browser API'lerinde veya realtime veri akışlarında görebiliriz.
subscribe
unsubscribe
event listener
WebSocket events
Adapter Pattern
Uyumsuz veri yapılarını birbirine adapte etmek
Adapter Pattern, bir sistemden gelen veriyi uygulamanın ihtiyaç duyduğu formata dönüştürmek için kullanılır. Özellikle backend response formatı ile frontend domain modelinin birbirinden farklı olduğu durumlarda oldukça kullanışlıdır.
API response mapping
Legacy API
Third-party services
Domain model dönüşümü
Backend'in döndürdüğü alanları doğrudan component içerisinde dönüştürmek yerine adapter katmanında normalize etmek UI tarafının daha sade kalmasını sağlar.
API model
Adapter
Domain model
UI model
Singleton Pattern
Uygulama içerisinde tek bir instance üzerinden çalışmak
Singleton Pattern, belirli bir servisin veya nesnenin uygulama içerisinde tek bir instance ile kullanılmasını amaçlar. Frontend uygulamalarında API client veya configuration gibi yapılarda karşımıza çıkabilir.
API Client
Axios instance
Configuration
Logger
Ancak Singleton kullanımı da otomatik olarak doğru değildir. Global mutable state oluşturmak test edilebilirlik ve bağımlılık yönetimi açısından sorunlara yol açabilir.
Global state riski
Test izolasyonu
Dependency yönetimi
Instance lifecycle
React ve Next.js'te Pattern'ler
Pattern'ler framework'ten bağımsızdır ancak kullanım şekli değişebilir
React ve Next.js uygulamalarında Design Pattern'ler genellikle component, service, data access ve state management katmanlarında karşımıza çıkar.
Component architecture
Service layer
Repository layer
State management
Data fetching
Next.js App Router ile Server Component ve Client Component ayrımı da architecture kararlarını doğrudan etkiler. Bu nedenle pattern seçimi yalnızca teorik olarak değil, framework'ün çalışma modelini dikkate alarak yapılmalıdır.
Server Components
Client Components
Server Actions
Caching
Data Fetching
Route Handlers
Gerçek Bir Next.js Senaryosu
PDOS gibi büyüyen bir uygulamada pattern seçimi
Bir task management uygulamasında başlangıçta component içerisinden doğrudan API çağrısı yapmak yeterli olabilir. Ancak uygulama büyüdükçe aynı data access logic'i farklı feature'larda tekrar etmeye başlayabilir.
Tasks
Goals
Documents
Calendar
Analytics
Bu noktada Repository Pattern ile data access katmanı ayrılabilir. Farklı task işlemleri Strategy Pattern ile yönetilebilir ve API response'ları Adapter katmanında frontend modeline dönüştürülebilir.
Repository
Strategy
Adapter
Service
UI
Ancak uygulama küçükken tüm bu katmanları oluşturmak yerine basit bir servis yapısıyla başlamak daha doğru olabilir. Architecture ihtiyaç ortaya çıktıkça evrilmelidir.
YAGNI
Incremental architecture
Low coupling
High cohesion
Maintainability
Pattern Kullanmamak Ne Zaman Daha Doğru?
Basitlik de bir architecture kararıdır
Eğer problem basitse, çözümün de basit olması genellikle daha sağlıklıdır. Tek bir API çağrısı için Repository, tek bir davranış için Strategy veya tek bir component için Facade oluşturmak gereksiz olabilir.
Küçük component
Tek API çağrısı
Tek davranış
Düşük değişim ihtiyacı
Düşük domain complexity
Pattern kullanmadan önce şu soruyu sormak gerekir: Bu abstraction bugün gerçekten bir problemi çözüyor mu, yoksa gelecekte belki lazım olur diye mi oluşturuluyor?
Bugünkü problemi tanımla
Değişim ihtimalini değerlendir
Complexity maliyetini hesapla
Gerekirse daha sonra abstraction ekle
Pattern Seçerken Sorulması Gereken Sorular
Pattern uygulamadan önce problemi doğru tanımla
Bir pattern uygulamadan önce yalnızca 'Bu pattern'i kullanabilir miyim?' sorusu yerine problemin gerçekten abstraction gerektirip gerektirmediği değerlendirilmelidir.
Hangi problem çözülüyor?
Bu problem tekrar ediyor mu?
Kod değişmeye açık mı?
Yeni davranışlar eklenecek mi?
Test edilebilirlik etkileniyor mu?
Sonuç
Pattern değil, problem odaklı düşünmek
Design Pattern'ler frontend architecture'ın önemli araçlarından biridir. Ancak pattern kullanmak başlı başına bir kalite göstergesi değildir. Önemli olan hangi problemin çözüldüğünü ve seçilen abstraction'ın gerçekten değer üretip üretmediğini bilmektir.
Pattern bir araçtır
Problem önce gelir
Basitlik değerlidir
Overengineering'den kaçınılmalıdır
Architecture zamanla gelişebilir
React ve Next.js projelerinde Strategy, Provider, Repository, Observer, Adapter, Facade ve Singleton gibi pattern'ler doğru problemlerle karşılaşıldığında oldukça güçlü çözümler sunabilir. Fakat iyi bir frontend architecture, pattern sayısıyla değil, doğru abstraction ve doğru trade-off'larla oluşturulur.
Doğru abstraction
Doğru trade-off
Maintainability
Scalability
Developer experience
Kısacası senior yaklaşım, 'Hangi pattern'i kullanmalıyım?' sorusundan önce 'Buradaki gerçek problem ne?' sorusunu sormaktır.
Önce problemi anla
Sonra çözümü tasarla
Pattern gerekiyorsa uygula
Gerekmiyorsa basit bırak