Bulut Uygulamaları İçin Dayanıklılık (Resiliency) Tasarım Desenleri

Dayanıklılık, zor durumlar ile başa çıkabilme, ayağa kalkabilme ve tekrar devam edebilmektir. Yazılım dünyasında dayanıklılık ise bir uygulamanın hata durumlarına düştüğünde durumu yönetebilmesi, tekrar çalışır hale gelebilmesi ve sorunlu durumdan tamamen kullanılmaz hale gelmeden kurtulabilmesi diyebiliriz. Uygulamanın devamlılığını sağlayabilmesi, geliştiricileri olarak bizlerin sorumluluğudur. Ayrıca bu tarz durumlarda yine sorunu çözebilmek için gecesi gündüzüne karışanlar gene biz yazılım geliştiriciler oluyoruz. Dolayısıyla uygulama geliştirirken dayanıklılık konusu da tasarımımızın bir parçası olmalı, gerekli yerlerde mimarimize bu tarz sorunlu durumlarda hayatımızı kolaylaştıracak müdahaleler yapmalıyız. MSDN'in bu linkteki makaleler zincirini okurken kendi adıma aldığım kısa notlar olarak yazdığım bu yazıdaki çoğu görsel kaynak da yine aynı makalelerden alınmıştır. Bulkhead Tasarım Deseni Bulkhead, gemilerde kullanılan, geminin gövdesini odalara ayırarak tehlikeli durumlara karşı gemiyi koruma yöntemidir. Bu sayede ayrık odalardan birinde meydana gelecek kaza ve su sızıntısı, geminin tamamının su almasını engeller. Uygulama içerisinde özellikle yoğun kullanılan servisleri ayrı uygulama havuzları (pool) ile çalıştırarak kaynakların paylaşılması sağlanır. Bir serviste oluşacak sistem yoğunluğu ya da hatalar silsilesi, sadece bir havuzu etkileyeceğinden, uygulama altındaki diğer servisler etkilenmeden çalışmaya devam edebilirler. Bazı senaryolarda uygulamanın mimarisinin anlaşılmasının zorlaştırabilir, bu gibi durumlarda faydası ile karşılaştırarak tercih edilebilir.       Devre Kesici (Circuit Breaker) Tasarım Deseni Uygulamalarımız bazen hata durumuna düşer ve bu hata durumlarından çıkması, müdahale süresi vakit alabilir. Bu tarz bir durumda, uygulama gelen istekleri karşılayamaz ve sürekli hatalar verir. Belki arka planda yarım işlemler bırakır. Ya da uygulama zaman aşımına düşme durumlarında istemci uygulamaya hata mesajının dönmesi vakit alabilir. Biriken yeni talepler gitgide daha fazla soruna sebep olabilir. İşte bu tarz sorunlu anlarda uygulamanın bu durumu, tamamıyla hata durumuna düşmeden, kolay bir şekilde atlatabilmesi için tasarlanmış devre kesici - circuit breaker tasarım desenini kullanabilir. Bu tasarım desenine göre uygulamamız üç ayrı durumda bulunabilir. Kapalı Durum (Closed) : Kapalı elektrik devresi gibi düşünebiliriz. Uygulamamız gelen isteklere cevap verebilir durumdadır ve gelen istekleri işler, cevap döner. Hata alınması durumunda bir sayaç değeri arttırılarak tutulur. Yarı Açık Durum (Half-Open) : Uygulama gelen isteklerin bazılarını işler, bazılarına ise hızlıca hata mesajı döner. İşlenen istekler hata alırsa sayaç arttırılmaya devam eder.  Açık durum (Open) : Uygulama stabil durumda değildir. Gelen isteklere belli bir zaman aşımı süresine kadar hata döner ve istekleri işlemeye çalışmaz. Bu tasarım deseni dış kaynaklara erişilen durumlarda tercih edilebilir. Uygulama içi kaynakların kullanıldığı durumlarda ek bir mekanizma olacağından yük getirebilir.     Telafi İşlem Tasarım Deseni Başlattığımız bir işlemin hata alması durumunda işlemin rollback edilmesi geliştirme sırasında sıklıkla çalıştığımız bir senaryodur. Eğer çalıştığımız sistem, birbirine bağlı microservice veya entegre uygulamalardan oluşuyor ise rollback mekanizması sıkı kurallara sahip ve tutarlı olamayabilir. Her yapılan işlemin tersini veya hata durumunda yapılacak işlemleri içeren bir metodunun olması gerekir. Bazen parçalı işlemlerden bir kısmı gerçekleştirilip, bir aşamasında hata alındığında, uygulamaya bağlı olarak, her zaman işlemler tersine çevrilmeyebilir. Yerine alternatif işlemler yapıldığı durumlar da olabilir. Mesela birden fazla noktaya uçuş planladığımız bir uçuş planının bir kısmı gerçekleştirilirken, bir aşamasında hata alındığını düşünelim. Bu hata alınan aşamada alınmış olan biletleri iptal etmek yerine belki kullanıcı onayı ile farklı bir tarihteki uçuş ile veya farklı bir sağlayıcıdan girilecek bir tarife ile alternatif bir çözüm bulunabilir. Bu mekanizma geliştirilen uygulama özelinde bir senaryo tasarlanarak incelenmelidir. Bu tasarımlar sırasında rollback veya telafi işlemin gerçekleştirilmesi sırasında alınabilecek hatalar da göz önünde bulundurulmalı.  Aslında benim fikrim bu konu dayanıklılık konusu dışında zaten tasarım aşamasında düşünülmesi gereken senaryolardan biri. Sistem hataları olması durumunda uygulamanın dayanıklılığından ziyade müşteri deneyimini iyileştirmeye yönelik bir çalışma. Ayrıca hatalı ya da yarım kalıp tamamlanamayacak işlemler konusunda nasıl aksiyon alınacağının yönetimi yine bu tarz çalışmalarda düşünülmesi gereken başlıklardan.   Uygulama Sağlığı İzleme Noktaları (Health Monitoring Endpoints) Uygulamalarımız arka planda bir çok kaynağı kullanıyor ve bu kaynaklara erişimde sunucu bazlı sorunlarla karşılaşıyor olabiliriz. Uygulamamız bulutta veya sunucuda çalıştığı durumlarda her an elimizin altında olmadığından sunucularda olabilecek sorunları sorun iletilmeden fark edebilmek çok önemli. Bu tarz sistem kaynaklarına erişim ve network seviyesindeki gecikmeleri izleyebilmek için uygulamalara bu kontrolleri yapan birer erişim noktası tanımlanıp, belli aralıklarla uygulamanın sağlığı ve gelen isteklere cevap süreleri kontrol edilir. Hatta uygulamanın başka servislere entegrasyon noktaları ve cevap süreleri de kontroller içerisine alınıp, tüm erişim ve performans takibi yapılabilir. Kısaca şu kaynakların erişimi ve cevap sürelerini izlemek faydalı olacaktır :  Var ise disk erişimi Var ise ağ dizini erişimi Veritabanı erişimi CDN'de tutulan script veya css gibi kaynaklar var ise bunlara erişim Dış entegrasyon servislerine erişim Sertifika validasyonları     Lider Seçim Deseni Uygulamalarımızda toplu olarak ve birbiri ile koordineli olarak çalışması gereken işlemler yer aldığında bir thread veya instance tarafından işlenen işin bir diğer thread veya instance tarafından ezilmesini engellemek ve eldeki tüm kaynakları koordine edecek bir lidere ihtiyaç duyarız. Bu liderin kaynaklar içerisinden seçilip, liderin bir sorun yaşaması halinde ya da lidere erişilemez hale geldiği bir durumda yeni bir liderin seçilebilmesini sağlayan bir tasarıma ihtiyaç olur. Liderin diğer kaynaklar gibi işi yapması gerekmeyebilir. Liderin tek işi koordinasyon ve diğer kaynakların birbiri ile çakışmaması olabilir. Liderin sistem için bir darboğaz oluşturmaması gerekir. Eğer tüm kaynaklar aynı kodu çalıştırıyor ise tüm kaynaklar lider olabilme potansiyeline sahip demektir, bu durumda birden fazla lider seçimi yapılmaması dikkat edilecek önemli noktalardan bir tanesidir. Aşağıdaki örneğe bakarsak, işlemleri yapan üç ayrı instance görüyoruz. Üçü de aynı liderlik metodunu çağırıyor, 1 nolu instance metodu ilk çağırdığı için liderlik rolünü alıyor ve metodu locklıyor. 2 ve 3 nolu instance'lar liderlik rolünü alamıyor, ama belli aralıklarla liderlik rolünü metodunu çağırmaya devam ediyorlar. Liderliği elde eden 1 nolu instance elindeki işleri diğer 2 ve 3 nolu instance'lara paylaştırıyor. Bu arada liderin sağlıklı olduğunu ve işini yapabildiğini kontrol edebilmek için lider belli zaman aralığında liderlik vasfını yeniliyor. Eğer liderde bir sorun var ise lock kalkıyor, yapılan işlemler geri alınıyor. Diğer instancelar'dan biri lider olup koordinasyonu sağlıyor. Mesaj Kuyruğu Tabanlı Yük Dengeleme Enterprise uygulamalarda yük yönetimi ve servisler arası bağımlılığı azaltma amaçlı olarak sıklıkla kuyruk tabanlı çözümler kullanırız. Uygulamamıza gelen mesaj sayısı zamana göre değişebiliyor ve zaman zaman uygulamanın karşılayabileceği seviyelerin üzerine çıkıyor ise kuyruk çözümü derdimize çare olacaktır. İşlenmek üzere uygulamamıza gelen mesajlar mesaj kuyruğunda, uygulamamız müsait oldukça uygulamamıza beslenerek yük kontrolü sağlanabilir. Yada mesajlar, kuyruk tarafından tüketici uygulamalarımıza yük durumlarına göre paylaştırılabilir. Özellikle veritabanı gibi tek bir kaynağı işleyen fonksiyonlarda aynı anda birden fazla işlem kaynağa erişimde düğüm oluşturup uygulamamızı cevap veremez hale getirebilir. Bu tarz durumlar için çözüm işlenecek mesajları kuyrukta biriktirip, peyderpey alıp işlemektir.       Tekrar Deneme Uygulamamızın çalışması sırasında sıklıkla hatalar ile karşılaşırız. Kimi hatalar ağ kesintileri veya servis yoğunluklarından kaynaklı olup, geçici olabildiği gibi kimileri ise uygulamada bulunan mantık hatalarından kaynaklı kalıcı hatalar olabilir. Hatalara, tipine göre tekrar deneme mekanizmasının kurulması uygulamanın dayanıklılığını arttırıcı önlemlerden biridir. Hata durumlarında alınabilecek aksiyonları temelde aşağıdaki gibi düşünebiliriz. İptal : Oluşan hata kalıcı bir hatadır, aralıklarla tekrar tekrar işlemin denenmesi işlemi başarılı yapmaz. Bu tarz durumlarda işlem iptal edilip, loglanarak raporlanmalıdır. Tekrar Deneme : Oluşan hata, nadir oluşan geçici bir sistem aksaklığından dolayı oluşmuştur. Bu tarz hataları tekrar denemek işlemi başarılı sonlandırabilir. Ertelemeli Tekrar Deneme : Oluşan hata, yine nadir oluşan geçici bir sistem aksaklığından dolayı oluşmuştur. Fakat tekrar deneme işlemi art arda denemelerde tekrar tekrar hata alınıyor ise bekleme süresi arttırılarak tekrar deneme yapılır, hatta belli sayıda denemeden sonra limit aşımından işlemler iptal edilir. Böylece sistemde hata alan işlemlerden dolayı bir yük yaratılmaz. Tekrar deneme tasarımları yapılırken, agresif ertelemeler vermek sistemi negatif yönde etkileyebilir. O yüzden uygun bekleme sürelerinin belirlenmesi çok kritiktir. Ayrıca yukarıda bahsettiğimiz Devre Kesici Tasarım Deseni mekanizma içerisine dahil edilerek daha kontrollü bir tekrar deneme mekanizması tasarlanabilir. Yeniden denenecek işlemlerin her defasında yarım kalmış kayıtlar yaratma olasılığı olduğundan dolayı denemeler atomik ve hata durumunda kendi kendini geri alabiliyor olmalı (rollback). İşlem her yeni denemede sanki sıfırdan sisteme giriyor gibi çalışabilmeli ve hata durumunda sistemde kalıntı bırakmamalı. Tekrar deneme mekanizmasını kurgularken, tekrar denenecek hataların iyi analiz edilmesi gerekir. Her hatanın tekrar denemeye sokulması sistemde beklenmedik sonuçlar doğurabilir. Hata logları iyi analiz edildikten sonra tekrar deneme mekanizması kurulup, mekanizma için güvenli, yani tekrar denemeye müsait hatalar tespit edildikten sonra kullanıma alınması daha uygun olacaktır.   Örneğin yukarıdaki servis çağrısı, ilk denemede HTTP 500 hatası alıyor. Belli bir süre beklenip mekanizma tarafından tekrar deneneniyor, tekrar HTTP 500 hatası alıyor. Önceki beklemeden daha uzun bir süre beklenip, tekrar deneniyor ve bu sefer HTTP 200 cevabı alıp işlemi başarıyla tamamlıyor.   Scheduler, Agent, Supervisor Tasarım Deseni Yukarıda öğrendiğimiz tasarım desenlerinin topluca birlikte kullanıldığı bir desen olan Scheduler Agent Supervisor tasarım deseni, bir işlemi gerçekleştirmek için gerekli olan birbirine bağlı işlerden oluşan iş akışının yönetimine odaklanıyor. Bu tasarım deseni isminden de anlaşılacağı üzere desen üç parçadan oluşuyor. Scheduler : İş akışı içerisindeki işleri organize edip çalıştıran, aralarındaki orkestrasyonu yapan yapı. Agent : Her bir işi çalıştıran yapı, scheduler tarafından yönlendirilen yapı aynı zamanda. İş eğer hata alırsa, yarım işlemleri geri alan yapıyı da içeriyor olmalı. Supervisor : Scheduler tarafından çalıştırılan işlerin durumlarını takip eden, gerekli durumlarda tekrar çalıştırılması için scheduler'ı tetikleyen yapı.         Agent'ların hata alması veya timeout olması durumunda supervisor'lar devreye girip işi geri alıyor. Fakat supervisor'ın hata alması veya cevap vermemesi durumunda scheduler, bu işi komple hatalı duruma çekip iptal etmelidir. İptale çekilen iş için yeni bir supervisor ile iş tekrar denenmesi halinde, önceki supervisor agent'a veya scheduler'a tekrar müdahale edememelidir. Lider seçimi tasarım deseni burada supervisor seçimi ve çalışması için uygun yöntem. Karmaşık bir yapıya sahip bir sistem olduğu için yapının tüm hata senaryolarını içeren bir testi yapılmalı. 

Building Evolutionary Architectures: Support Constant Change - Kitap İncelemesi

  Güncel hayatta günlük iş yaşamımızda sıklıkla, halihazırda var olan uygulamalara ek özellikler geliştiriyoruz. Bazen karşılaştığımız yazılımların mimarileri yeni geliştirilen özelliklerin adaptasyonunda çok zorluk çıkarıyor, bazen ise çok daha kolay bir şekilde adapte edebiliyoruz yeni özellikleri. İşte evrimleşebilir yazılım mimarisi tam da bu konu ile ilgileniyor. Yani günlük yaşamımızda sıklıkla karşımıza çıkan evrimleşen yazılımları, yeni gelecek özelliklere adaptasonunu kolaylaştırma, yazılımın hedeflediği mimari özellikleri riske atmadan yeni özellik geliştirebilmeyi, güncel teknolojiler ve güncel yazılım geliştirme pratiklerini kullanarak aktarmaya çalışıyor. Yazılım mimarisi üzerine çok güncel ve ilginizi kitabın sonuna dek üst seviyede tutacak bir kitaba ne dersiniz ? Son yıllarda sık sık duyduğumuz ve hatta hayatımıza girmeye başlayan bu yeni trendlerin ve pratiklerin oldukça açık ve anlaşılır bir şekilde anlatıldığı bu kitabı şiddetle tavsiye ediyorum. Aşağıda kitabı bölüm bölüm içerikleri ve benim ilgimi çeken kısımları ile kısaca özetlemeye çalıştım, umarım faydası dokunur. 1. Bölüm : Software Architecture Bu bölüm konuya giriş mahiyetinde yazılmış, genel bilgiler vererek neden yazılım mimarileri değişikliğe uğrar, nasıl hazırlıklar yapılmalıdır ve zamanla karşılaşılacak değişikliklerin ne kadar normal ihtiyaçlar olduğu üzerine nispeten kısa bir bölüm. Yazılım geliştirirken yazılım mimarlarının, yazılım ile alakalı taleplere göre çıkardıkları karakteristik mimari özelliklerin nasıl belirlendiği ne nasıl korunduğu üzerine başlıklar içeriyor. 2. Bölüm : Fitness Functions Benim için yeni olan bu terimi bu kitap sayesinde öğrendim. İlk bölümde bahsedilen yazılımın karakteristik özelliklerini, yazılıma zamanla yapılan değişikliklerle korunup korunmadığını test eden fonksiyonlara verilen isim fitness functions. Yazılım geliştirme süreci içerisinde continuous integration içerisine dahil edilen fonksiyonlar her değişiklikte karakteristik özelliklere yapılan etkiyi test ediyor.  Kısacası geliştirilen mimarinin, talep edilen ya da iddia edilen mimari özelliklere sahip olup olmadığını kontrol eden testlere fitness function deniyor.  Fitness functionları açıklayan bölüm sonrasında atomik, yani tek özellik üzerine yoğunlaşan fonksiyonları ve holistik yani birden fazla özelliğe yoğunlaşıp birbirleri ile ilişkilerini test eden fonksiyonları açıklıyor. Statik / dinamik, otomatik çalışan / manuel tetiklenen ve geçici fonksiyonlar gibi pek çok kategoriye ayrılan fitness fonksiyon çeşidini açıklayarak devam ediyor bölüm. 3. Bölüm : Engineering Incremental Change Continuous Delivery kavramını açıklayarak başlayan bölüm bu kavramın evrimleşebilir mimaride nasıl yer bulduğunu açıklıyor. Yapılancak değişikliklerin agile pratiklere de uygun olarak olabildiğince ufak yapılması ve otomatize edilmesi üzerinde duruyor. Sağlanan bir serviste yapılacak değişikliğin geriye dönük desteği nasıl sağlayabileceğini örneklendirerek çok anlaşılır bir şekilde aktarıyor. Github'ın, sisteminde gerçekleştirdiği günde 60 kurulumu nasıl yaptığı üzerine yazılmış bölüm ve Github ekiplerinin geliştirdiği Scientist kütüphanesini tanıtan bölümler gayet güncel ve ilgi çekici. Scientist kütüphanesi, sistemde yapılan refactoring'lerin eski kodda ve yeni yazılan kodda nasıl sonuçlar verdiğini karşılaştırıp güvenle değişiklik yapmayı sağlıyor. Kendime not olarak da aldığım Scientist'in .NET port'u olan Scientist.NET gayet güzel örneklerle dökümante edilmiş açık kaynak bir kütüphane. Ayrıca Hypothesis and Data Driven Development ve 2013 yılbaşında Facebook'un karşılaştığı inanılmaz sayıdaki fotoğraf yüklenesi ve bunların büyük bir çoğunun saldırgan olarak işaretlenmesi ile ilgili vaka, ve bunun altyapıda yarattığı yoğunluk üzerinde duruluyor. İnsanların kendi "hoş gözükmeyen" fotoğraflarını saldırgan olarak işaretlemesi eğilimi üzerine facebook ekiplerinin yaptığı çalışmalar ve bu probleme çözümleri anlatılıyor. 4. Bölüm : Architectural Coupling Mimari açıdan uygulama modüllerinin birbirlerine referansları her zaman problem olmuştur. Modüllerin ne kadar bağlı olması gerektiği üzerine çalışmak mimarları zorlayan konulardan biri. Bu konuya ayrılmış olan 4. bölüm modülerlik ve mimari quantum'dan bahsederek konuyu monolith uygulamalara, plugin tabanlı uygulamalara, SOA, sunucusuz uygulamalara, BaaS ve FaaS ile microservislere bağlıyor. Birçok modern mimarinin anlatıldığı bölüm uzun ama oldukça zevkli ve kolay anlaşılır bir dille yazılmış. Bu noktada yine kendime not olarak aldığım bir Channel 9'da yayınlanmış güncel bir videoyu paylaşmak istiyorum : Microservice Architecture with ASP.NET Core 5. Bölüm : Evolutionary Data Uygulamaların olmazsa olmaz parçası veri kaynakları üzerine ayrılmış bölüm temelde veritabanlarında yapılacak değişikliklerin nasıl yönetileceği üzerine yoğunlaşmış. Veri kaynağı olarak veritabanlarını kullanan ortalama seviyedeki bir yazılım geliştiricinin sıklıkla kullandığı yöntemler üzerinde duruluyor.  6. Bölüm : Building Evolvable Architectures Bu bölümde eski bir uygulama nasıl evrimleşen hale getirilir, yeni geliştirilmeye başlanan bir uygulamada evrimleşen bir mimari kurabilmek için uygulanması gerekenler üzerinde duruluyor. Farklı tip uygulama mimarileri ve bunların evrimleşebilir hale getirilmesi ya da evrimleşebilirliğe ne kadar uygun oldukları açıklamalı anlatılıyor. Farklı tip mimarileri tanımak için bile okumaya değer bir bölüm. Ayrıca referans olarak eklenen kütüphanelerin evrimleşebilir bir mimari üzerindeki olası etkileri, eklenen kütüphanelere ya da bağımlı olunan frameworklere nasıl yaklaşılması gerektiği konusu inceleniyor. 7. Bölüm : Evolutionary Architecture Pitfalls and Antipatterns Bu bölümde evrimleşebilir bir mimari tasarlarken karşılaşılabilecek tuzaklar ve kötü tasarım desenleri inceleniyor. Mesela genellikle düşülen tuzaklardan biri, geliştirilen yeni bir uygulama eğer temelde bir başka ürüne dayanıyor ise mimarinin bu ürüne göre şekillenmesinin yanlışlıklarından ve etkilerinden bahsediliyor. Ayrıca kitap genelinde sürekli referans konan The Last 10% tuzağı bu bölümde inceleniyor sonunda. Uygulama geliştirirken genellikle seçilen kütüphaneleri uygulamanın 80% - 90%'sini hızlandıracak, ya da geliştirmesini kolaylaştıracak şekilde seçiyoruz. Ama bazen kalan 10%'lik kısmın geliştirmesi ya da etkileri o kadar büyük olabiliyor ki uygulamaların başarısızlıkla sonuçlanmasına bile sebep olabiliyor. Bu tuzak, gerçek bir örnek anlatılarak inceleniyor. Code Reuse ile alakalı oldukça aykırı fikirlerin yeraldığı bir bölüm var, benim oldukça ilgimi çekti. Üniveristeden beri şartlandığımız code reuse'un gerçek hayatta işleri ne kadar zorlanştırdığı üzerinde duruluyor. Loglama ve monitoring gibi temel mimari özellikler dışındaki kısımların kod kopyalanarak ya da yeniden yazılarak uygulamaların olabildiğince birbirinden ayrık tasarlanması üzerinde duruluyor. Hatta yanlışlıkla yapılabilecek kod reuse'ları ve bunun sonucu oluşan bağımlıklıkları engellemek için her ayrık servis için farklı teknoloji seçimi bile yapılabileceği öneriliyor.  8. Bölüm : Putting Evolutionary Architecture into Practice Kitabın son bölümü gerçek hayatta evrimleşebilir bir yazılım mimarisinin nasıl hayata geçirilebileceği üzerine eğiliyor. Yazılım geliştirecek ekiplerin nasıl yapılandırılması gerektiğinden başlayıp süreçlere kadar eğiliyor.