Mevcut projede separation of concern uygulamak
Merhabalar herkese,
Yazılım ekibimizle üzerinde uzunca süredir çalıştığımız kodumuzda separation of concern’ü nasıl uyarladığımızdan ve nasıl daha iyi bir noktaya getirebildiğimizden bahsedeceğim. Bu yazıda temel olarak; bu prensibi neden benimsedik, pratikte ne işimize yaradı ve kafamızdaki noktaya doğru yürürken neler ile karşılaştığımız olacak.
İlk olarak separation of concern neydi ve pratikte bizim ne işimize yarar ona biraz bakalım.
Separation of concern (SoC)
Kısa tanım olarak; farklı işlere yarayan parçaların birbirinden ayrı olarak konumlandırılması diyebilirim. Bu pratikte şu demek; logging, business logic, helpers, renderer gibi yapıların farklı yerlerde bulunması ve yönetilmesi olayıdır.
Olayı biraz farklı bir perspektif getireyim. Mesela bu yazıyı okuyan herkes ya aktif olarak MVC yazıyor ya da biliyor. MVC’de separation of concern’den gelen bir yapı desem mesela? Şöyle ki; View, Controller, Model’lerin farklı noktalarda ele alınması zaten bu prensipten geliyor. E tabi gerçek hayattaki projeleri bu kadar basit şekilde kurtaramıyoruz. O noktada bu prensibin en temel lafı ile hareket etmek gerekiyor yani bölebildiğin en küçük parçaya kadar bölmemiz.
Ne fayda sağlar bu SoC?
SoC genel olarak; uygulamamızda geliştireceğimiz her yeni modül ya da varolan modül geliştirmelerinde, diğer yapıları bozmadan veya etkilemeden geliştirme yapabilmemizi sağlar. İşte hep duyduğumuz şu coupling yani bağlılık dediğimiz olayı minimumda tutarak bizim daha kolay ürün çıkartabilmemizi sağlar. Yani kusursuz uygulanan bir SoC ile sadece geliştirme yaptığınız işe odaklanabiliyoruz.
Bununla beraber yüksek kod kalitesi sağlamamıza yardımcı oluyor. Birbirinden bağımsız geliştirilen kod zaten genelde yüksek kalitede oluyor çok fazla kasmamıza da gerek kalmıyor diyebilirim.
Aslında burada çok önemli bir nokta var. Bir projede kod geliştirirken işin mimarisi ile ilgilenip derinden bir şeyleri çözmek istediğiniz zaman çok fazla teknik ve teknoloji karşınıza çıkıyor. Örneğin DDD, TDD, MVC, Patterns, AOP, SoC … bu liste uzar gider. Bir seri kısaltmalar listesi. Günün sonunda kullandığınız her teknik, her yapı bir şekilde separation of concern oluyor ya da bu prensibin destekçisi oluyor. Diğer bir taraftan ise tekniklere çok bağlı bir kod geliştirme neredeyse kimse yapamıyor. Çünkü iş gereği vb. şekilde illa ki bir yerlerde bu kurallar esniyor. TDD yaparken bir anda kod yazıp sonradan onları test etmeye başlayabiliyorsunuz, ya da çok acil bir şekilde bir bug’ı fix’lemeye çalışırken bu kurallar önemini yitirebiliyor. E bu noktada ne önem kazanıyor? Benim bugüne kadar gördüğüm önemli olan tek şey; geliştirilen modul/component vb. yapıların birbirinden bağımsız şekilde geliştirilmesi. Günün sonunda kolay test edilebilir, bug’ı kolay fix’lenebilir, kod kalitesi yüksek, geliştirme yapması kolay bir yapı elde ediyorsunuz.
Mevcut projede SoC uygulamak
Biz Hesapkurdu.com yazılım ekibi olarak devraldığımız kodu en iyi hale getirmek için kolları sıvadığımızda ilk yaptığımız şeylerden biri SoC’yu nasıl uyarlayacağımıza karar vermekti. İlk olarak kodu teşhis etmeliydik. Daha sonra bunu adımlara bölmeli ve sırayla uyarlamamız gerekliydi. Bir yandan da aldığımız ve alacağımız kararlar buna uygun olarak şekillenmeliydi.
Önce bunu yapacağımızda kendi içimizde bir kaç sorumuz vardı,
Ne kadar kod değiştirmemiz gerekiyor?
Mevcut kodda değişiklik yapmak ne kadar kolay?
Mevcut özelliği kırmadan neyi değiştirebiliriz?
Mevcut mimari modelin tekrar kullanılabilirliği (reusability) ne durumda?
Nereden başlayacağız? (En büyük sorumuzdu)
E tabii bunların cevapları kodu bir miktar tanıdıktan sonra oluştu. Kodu analizden geçirdik, tanıdık, test ettik, yük verdik ve en önemli olarak günlük kod geliştirme akışında en çok sıkıntı çekilen yerleri not aldık. İşe yarar güzel bir taktik bulduk. Sorun yaşadığımız şeyleri not aldık ve bir süre sonra yanında en fazla artı bulunan şey en büyük sorunumuzdu ve onu çözmeliyiz dedik. Bizim için bu Data Access Layer’dı.
Hepimizin çok iyi bildiği layer ve n-tier mimari yapısı. Evet onlar da separation of concern’den geliyor. Aslında gerçek hayatta öyle işliyor diyebilirim. Hastaneleri bu gözle incelerseniz ne demek istediğimi anlarsınız :)
Bu işe başlarken bizi zorlayan bir kaç noktalarımız şöyleydi,
Dağınık bir access layer ve repository kullanımı
Business logic’lerin dağınık olması
Birden fazla solution ve project yapısı ile birden fazla uygulamadan oluşan yapı
Birden fazla git repository’sinde bulunan ve aynı işlere yarayan kodlar
Bir mimari yapısına sadık kalınarak geliştirilmiş bir yapı yoktu
Namespace yapısı yoktu
Her şirketin her yapının zorlukları farklı tabii ama bizimkiler ilk etapta bunlardı.
Bunları tespit ederken sadece koda bakarak çıkarım yapmadık elbette, kod analizi vb. yapılardan da faydalandık. Onlarla alakalı bilgileri şu yazımızdan bulabilirsiniz.
Zor kararlar
Bunları yaparken eski .Net projeleri (net461) istediğimizi elde etmemizi uzatacağından .Net Core’a geçiş için adımlar atmamız gerekiyordu. O yüzden yapacağımız her şey .Net Core’a ve netStandard’a uygun olmalıydı. Her şey bunlara göre gerekiyorsa yeniden şekillenmeliydi.
Api’lerimiz Level 2 prensiplerine uygun olmalıydı. Daha iyi geliştirme yapmamız için bunlar şarttı. (Bu arada Level 3 tartışmalı bir konu. Ona başka zaman değiniriz)
Service layer’ındaki her şey test coverage altında olmalıydı. Çok seri refactorlardan geçmeliydik ve bunu yaparken taşınan yapılar bozulmamalıydı.
Çok fazla CI/CD yapısı gerekliydi. Bu da ekstra devops yatırımı yapmamız gerektiğine işaret ediyordu.
Database’deki ve koddaki çoğu hatalı şekilde kurgulanmış yapıyı tespit etmek için çoğu yere tek tek bakmamız gerekiyordu.
İlk adım, ilk hata
İlk adım en hızlı yapacağımızı düşündüğümüz adım namespace düzenlemesiydi. Hız ve verim olarak en fazla geri dönüş alacağımız iş bu olduğu için ilk olarak bunu yaptık. Burada Resharper’ın refactoring özelliklerinden baya bir faydalandık diyebilirim. Solution’daki tüm projeleri, library’leri vb. hepsini Hesapkurdu namespace’i altında topladık. Daha güzel temiz oldu dedik. Bu değişikliği yayına alınca gözle bulamayacağımız bazı hatalar oldu. İşte ilk hatamız, daha ilk adımla geldi. SoC dedik dedik bi baktık ki View’ların içinde helper, dto vb. kullanımlar yapılmış. E kod uzun olunca bunları göremeyebiliyoruz. Daha iyi analiz yapmamız gerektiğini anladık. Buradaki hatamız bize çok şey öğretti. Ben de dahil olmak üzere çalışan arkadaşlarımız büyük bir çok projede rol almıştı, tabii hiç kimse bu tip kullanımların olacağını düşünmüyordu. Yani bünyemiz temiz kod yazmaya o kadar alışmış ki bunların olabileceğini düşünmemiştik bile. Biz de o alışkanlıklardan kurtulup daha fazla derine inmemiz ve dikkatli olmamız gerektiğini anladık.
İlk katman
Data Access Layer’ımız en problemli olan her olarak tespit ettiğimizden bahsetmiştim. Bunu aksiyona dökecek bir plan hazırladık. Buradaki en önemli noktamız şuydu; sadece kod geliştirmek ya da mimari olarak değişikliğe gitmek yeterli değil, bunu analist, ürün yöneticisi vb. ekiplere de aktarmak gerekiyor. Bu değişikliklerin süreceği günlerde buna uygun şekilde takip ve geliştirme yapılması ciddi önem kazanıyor. Ben access layer’ı düzenleyeceğim dediğimizde buraya dokunmayan işler diğer arkadaşlar tarafından paylaşıldı ve geliştirmeye başladık.
Bizde access layer farklı git repolarına, aynı kod içinde farklı noktalara dağılmıştı. Bu aşamada şöyle bir karar aldık. Data access için ayrı bir proje açalım ve her proje bunu kullansın dedik. İşin detayına için SoC hiç düşünülmeden yapılmış birbirine çok fazla bağlı bir yapıda bunu yapmak kolay olmuyor. Çok fazla helper, enum, extension gibi yapıları da bir yere taşımamız gerekliydi. İlk olarak bunlar için bir proje açtık. Tüm projelerden bu kodları arındırdık ve yeni yaptığımız projeyi Nuget olarak sunduk. Tüm projelerimiz bu nuget paketleri ile helper’lara ulaşabilir hale geldi. Daha sonra asıl yapacağımız iş için oluşturduğumuz projede helper’lar için oluşturduğumuz nuget pakedini de buraya aldık ve tüm Data Access yapımızı buraya taşıdık. Yine tüm projelerdeki eski olan repository’leri, access’leri vb. tüm kullanımları temizledik.
Tabi buralarda da sıkıntılar oldu ama önceden edindiğimiz tecrübeyle bunu minimize ettik. Farklı noktalardan aynı database’e erişen yapılan farklı modellerle yapılınca, aynı tabloyu farklı şekilde map’leyerek işlem yapılmış olabiliyor. Dolayısıyla bu modelleri eşitlenmediği bir kaç durum gözümüzden kaçmıştı. Bunları erken safhada tespit ettiğimiz için herhangi bir sorun yaşamadan yolumuza devam ettik.
Eski projelerdeki bu modelleri nasıl karşılaştırdığımız merak eden arkadaşlara şunu diyeceğim, ciddi ciddi herşeyi klasör gruplarına koyduk, tek tek modelleri compare tool’ları(bkz. Beyond Compare) ile baka baka yaptık. Şunu diyebilirsiniz, ne gerek var database first ile generate edin. Bunu da denedik 🙂 Database yapısındaki hatalardan dolayı relation yapıları düzeltilmesi mümkün değildi o an için. E tabii onu da düzeltmemiz gerekli ama bir yerden başlayıp bunu adımlara bölmemiz gerekliydi. Burada temel olaylardan bir tanesi de en işe yarar hamleyi seçip sadece onu yapıp ilerlemek. Biz bunu seçtik, database işi listemizdeki daha sonraki adımlardandı.
Adım adım SoC
Büyük bir projeyi ya da bir projeyi SoC yapısına uygun hale getirmek için bizim uyguladığımız bir plan var. Yukarıda da aslında bahsettiğim mantığı sıralarsak şu şekilde bir işleyişimiz var.
İlk olarak ilgili kodun tamamını uygun olan api’ye taşıyoruz ve bu şekliyle devam ediyoruz bir süre. Arkasından bunu data access’i düzelterek, kullanımların üzerinden geçerek refactor ediyoruz. Her refactor işlemimizde unit test’ler ile destekleniyor ve bu şekilde hem test coverage altına almış oluyoruz hemde kodumuzu istediğimiz şekilde layer’lara dağıtıyoruz. Yani kısaca kesip taşıyoruz, sonra iteratif şekilde refactor ediyoruz.
Yukarıdaki yapıdaki paketler aynı bir solution halinde bulunuyor. Eski yapılara destek vererek gitmek her projenin olmazsa olmazı olduğu için bunun yapılması zorunlu bir durum. O paketlerinde yavaş yavaş yok olduğu, backend api’lerimizin farklı bir mimaride olduğu bir yapıya kayıyoruz. İşler bizim açımızdan daha da eğlenceli oldu tabii. Sonraki yazılarımdan birinde de bu backend api’lerden bahsedeceğim.
Kod içindeki durumlar
Kod geliştirme aşamasında durumlar da değişkenlik gösteriyor. Projede çok fazla library ve klasörlemeler oluyor. Kodun business logic’i ilgili layer’a, database’e gittiğiniz yer data access layer’da yazılmacak şekilde tasarlanıyor. Duplike kodların önüne geçmek, tekrar kullanılabilirliği (reusability) arttırmak aynı amaca hizmet eden yerleri tek bir library altında toplamak gerekli. Mesela validation logic’leri için bir library açıp bütün validation yapılarını burda toplayınca uygulamanın tamamında email validation logic ortak oluyor, bir değişiklik uygulamanın tamamında etkili oluyor.
Düzgün şekilde yapıyı böldüğümüz zaman yukarıdaki gibi bir yapı ile çalışıyorsunuz. Logging için yaptığımız bir library var ve bunu her yerde kullanıyoruz. Bu sayede hem sürekli aynı tipte log üretiyoruz, hemde tek bir noktadan logger’ı kontrol edebiliyoruz. Bir değişiklik olacağı zaman sadece logger module’de geliştirme yapmamız yeterli oluyor.
Yardımcı teknikler
Biz tabii bunları yaparken çok fazla refactoring yaptık. Çok fazla pattern kullandık, çok fazla yapı ve teknolojiyle çalıştık. Kısaca onlardan bahsetmemin baya önemli olduğunu düşünüyorum.
Farklı projelerde kod paylaşımı yapabilmek için Nuget kullandık, kendi nuget repo’muzu kurduk ve buradan Data Access Layer, Helper vb. yapıları şirket geneline dağıttık.
Çok fazla CI/CD yapılarından faydalandık. Yapılan her değişiklik sonrası pipeline’lar çalışırken biz işe devam ettik, hataları noktaları bize bildirdi ve bunları fix’leyerek devam ettik. Buradaki yolculuğumuz yazı dizinden ulaşabilirsiniz. Devops yaptık da noldu?
.Net bize sunduğu her şeyden faydalandık. En çok işimizi gören şey netstandard 2.0 ve getirdiği common runtime. AspNetCore tarafında ise middleware’ler bize baya bir hayat verdi.
Data access layer’ımızı Entity Framework’ün son versiyonuna güncelledik. Bize sunduğu database first ile bu yapıyı düzenledik. Şu an aktif olarak EF Core kullanıyoruz.
Çok fazla pattern kullandık demiştim. En fazla strategy ve builder pattern ile hayatımıza devam ettik. Hayatımızdan repository pattern, unit of work gibi yapıları tamamen çıkarttık. Neden derseniz şuraya bakabilirsiniz.
SoC’u logging, exception handling gibi yapılara uyarladık. Bunu Asp.Net Core’un getirdiği yenilikler ile yaptık. Bunun detayı da bu yazımızda mevcut. ELK, Serilog ve .Net Core ile merkezi loglama sistemi
Biz mevcut yapımızı separation of concern uygulayalım diye yola çıktık. Bu yolculuk esnasında gördüğümüz şeyler bunlar oldu. Niye sadece ilk adımlardan bahsettiğim diye soracak olursanız ise bunun cevabı sadece ilk adım önemlidir, diğerleri onu takip etmesi yeterlidir diyebilirim.
Separation of Concern aslında bir pattern vb. bir şey olmadığını daha net gördüğünüzü umuyorum. Tamamen bu mantıkla düşündüğümüzde biz uygulamalarımızı bile parçalamaya, hatta database’imizi bölmeye başladık. Bir baktık ki aslında microservices dediğimiz olay da bunun bir aşamasıymış. Bir sonraki yazımda separation of concern diye yola çıkan bizim microservices yapısına dönüşümüzden bahsedeceğim :)
Saygılarımla.