Fonksiyonel olmayan gereksinimler

Mehmet Utku Tatlıdede
LCW Digital
Published in
8 min readJun 1, 2023

Foksiyonel olmayan gereksinimler (nonfunctional requirements NFR) sistemin iş amacını gerçekleştirirken zaten sağlaması gereken sistem özellikleridir. NFRlar küçük bir sistemi geliştirmeye başlarken çok daha kolay karşılandığı için ilk başlarda sürekli gündemde olan konular değildir. Ancak 6 milyon aktif kullanıcısı olan anlık 250K müşterisi olan lcwaikiki.com için her birini tek tek sorgulamamız ve hem mevcut özellikleri hem de yeni ekleyeceğimiz özellikleri dikkatlice tasarlamamız gerekiyor. Lcwaikiki.com dan gündelik örneklerlerle bu kavramları ele almak istedim.

(Image by Love Sharma, daha büyük bir resim için tıklayınız)

1. Availability: Erişilebilirlik günümüz web dünyasının en temel gereksinimi. Ne olursa olsun sisteminizin erişilebilir olmasını isteriz. Dan Olsen’in değerli kitabı The Lean Product Playbook dan Maslow’un ihtiyaçlar hierarşisine atıfla oluşturduğu uygulama kullanıcıların ihtiyaçları da bu şekilde oluşturulmuştur.

Dan Olsen’s Hierarchy of Web User Needs.

Öncelikle sisteminiz erişilebilir olacak, makul sürelerde cevap verecek, vaad ettiği fonksiyonları hatasız şekilde yerine getirecek ve kullanıcının ihtiyaçlarını karşılayacak. Ancak bunların sonrasında yani sürtünmesiz bir deneyim sağladıktan sonra çok değerli olan kullanım kolaylığı anlam kazanabiliyor.

Sistemin erişilebilirliğini etkileyecek en önemli sorun sistem bileşenleri içinde tek bir makina olarak kurgulanmış single point of failure (SPOF) olarak alandırılan en ufak bir hatada sistemi kesinti ile baş başa bırakan bileşenlerin varlığıdır. Bu arada sadece sorun olması da gerekmez böyle bir bileşen var ise gün içinde kesinti olacağı için deployment da yapamazsınız. e-ticaret sistemimizde ne yazık ki bazı çok kritik bileşenler SPOF olarak kurgulanmıştı. En ünlüsü inventory servis olan bu servis sebebi ile yaşattığımız kesintiler yeterince kötü değilmiş gibi sürümleri şimdikinin aksine gece 0400 de yapmak zorunda kalıyorduk. Neyse ki bu iki güçlü motivasyon sayesinde bu dertten kurtulduk :) (Product Servis Modernizasyonu makalesinde inventorynin içinden çıkan product servisin yapısını inceleyebilirsiniz)

Özetle Availability i arttırmak için her servisin en az iki sunucuda çalışması, state tutacak ise cache kullanması, database i ayrılabiliyor ise ayrılması presiplerini belirliyoruz. Servislerimizi yakında DR sitemızda da aktif olarak çalıştırma hedefimiz bulunuyor.

2. Scalibility: Ölçeklenebilirlik sisteme yük geldiğinde mevcut donanıma ek ram ve cpu takviyesi mi (vertical scaling) yapacağız yoksa clustera benzer özellikte yeni bir makina daha mı ekleyeceğiz (horizontal scaling) sorusudur. Tercihimiz, mimarimiz ve kodlamamız horizontal scalinge uygun olacak şeklide yapılmalıdır. Eğer vertical scaling yapmak zorunda olduğumuz bir bileşenmiz var ise başımız kolayca dertde girebilir çünkü makinaların CPU RAM Network socket sayısı gibi belli bir seviyeden sonra istesek de aşamayacağımız sınırları vardır. Özellikle state tutan servisler ve relational veri tabanları vertical scaling yapısına uygundurlar. Bu sebeple öncelikle state tutan servisiniz olmamalı ve noktada databaseleri de ayrıştırmanız gerekeceği gerçeğini kabullenmelisiniz. Şu an eticaret altyapımızda, MS SQL, Couchbase, Postgre SQL, Mongo DB veri tabanı olarak kullanılmakta.

  • Trafik patterni : Uygulamalar hizmet ettikileri domainlere göre farklı gün ve saatlerde farklı trafik alırlar. Örnegin aybaşı ve sonunda bankalar; Kasım ayı, bayramlar, özel günler gibi de eticaret sistemleri ortalamanın çok üstünde trafik alır. Eskiden kampanya dönemleri öncesinde yeni makina kurup sonra kaldırmak gibi bir işimiz vardı. :)
  • Elasticity : Elastikiyet ya da esneklik yüke göre sistemin otomatik tepki vermesi anlamına gelir. Kubernetes ve Horizontan Pod Autoscaler (HPA) bunu sağlamak için bize çok iyi bir araç sağlıyor. Müşterilerimize push atıldığında podlar artıyor ve yük azaldığında tekrar azalıyor. eticaret domaini için hem sistem hem uygulama altyapısı olarak elastik olmak kesinlikle sağlamanız gereken bir gereksinim.
Mobil uygulamanın açılması eventi ve precheckout pod sayıları
  • Latency: Gecikme süresi uygulamanın yük altında ne kadar yavaşladığını gösterir. Sadece yükü almak değil aynı zamanda müşteri deneyimini etkilemeden hizmet vermeye devam edebilmek gerekir. İyi scale olmamızın ölçüsünü latency ile de takip ediyor sürekli kendi loglama altyapılarımız ve apm tooları ile takip ediyor performans testleri ile gelişmeleri teyit ediyoruz.

Yükle beraber latencynin artığı eticarette kullandığımız bir third party servisin request ve response time grafiklerini isim vermeden paylaşmak istedim. Yük arttığında %10 — %20 belki yavaşlayabilir ama bu örnekte 2 kattan fazla arttığını görebiliyoruz. Bu servis batch olarak tüketildiği için neyseki müşteri deneyimini etkilemiyor.

3. Extensibility: Sistemimize yeni bir özellik eklemek için gereken çaba genişletilebilirlik olarak adlandırılır. Bizim ne kadar hızlı bir şeklide yeni özellik eklediğimiz maliyet rekabet pazarda ilk olmak gibi bir çok açıdan önemlidir. Spagetti ye dönmüş big ball of mud olarak adlandırılan bir mimariniz var ise testleri tamamlayıp kodunuzu üretim ortamına almanız muhtemelen kod yazmanızdan daha uzun sürecektir. Agility kavramı ile de yakından ilişklidir.

4. Consistency: Tutarlılık veri doğruluğunun sağlanması demektir. Dağıtık yapılarda servisler ya da mesaj sistemleri üzerinden haberleşme olduğu için çağrının yapılıp cevabının alınamadığı ya da hiç çağırılamadığı durumlar istemsekde yaşanabilir. Mesaj sistemleri için inbox outbox deadletter queue implementasyonumuz Pars.Core ile standart olarak geliyor. Bu sayede hem mesajların transactional olarak üretilmesini (outbox), hem birden fazla kez işlenmemesini (inbox) sağlıyoruz. mesaj işlenirken oluşan hatalı kaytıları da dead letterda toplayıp exponential backoff ile tekrar deniyoruz.

5. Resiliency: Sistemin bir sorun anında ayakta kalması beklenen cevapları vermeye devam etmesi yani dayanıklı olması demektir.

  • Recoverability: Beklenmedik bir durumda sistemin tekrar çalışır hale gelmesi anlamına gelir. Eğer servislerimiz High Available prensiplerine uygun olarak gerekli redundancy ile planlanmış ise bir hatta bir kaç makina kaybetmek sorun olmayacaktır. En önemli recovery afet anında yapılacak kurtarmadır. Mevcut sistemizde şu an aktif pasif datacenter ile çalışıyoruz ama 2024 e kadar aktif aktif bir modele geçmeyi planlıyoruz. Bu sayede hem recover süresini düşürüp hem de kaynakları daha verimli kullanmayı hedefliyoruz.
Bulkhead
  • Bulkhead: Gemilerde gövde de bir kaçak olaması ve su alması durumunda tüm geminin su almaması için gemi bir birinden ayrılabilen küçük bölmeler halinde yapılır. Kaçağı izole ederek sadece belli bölmelerin su almasına izin vererek batmanın önüne geçilebilir. Bunun yazılım dünyasına uyarlanmış hali ise müşteriye hizmet eden servisler ile farklı amaçlara özellikle batch/yoğun işlemlere hizmet eden yükleri bir birinden ayrımak böylece arka plan işlerinin yoğunluğunun müşterileri etkilemesine engel olmak şeklindedir. Örneğin depo işlemleri yapan servislerin kullandığı müşteri sipariş gibi servisleri farklı bir deployment ile yöneterek müşterilerin etkilenmesini minimuma çekebiliyoruz. (Bulkehead pattern için bakınız)
  • Circuit Breaker: Sigorta uygulaması yoğun şekilde tüketilen bir kaynağın sorun yaşaması halinde onu çağıran istemcinin de sorun yaşamaya başlamasını engeller. Örneğin http olarak yaptığımız bir servis çağrımız var ve normalde 1–2 sn suruyor (uzun ama neyse). bu sebeple de 10 sn lik bir timeout değeri belirledik. Eğer çağırdığımız servis bir sebepten cevap veremez hale gelir ise bizim servisimize gelen her isteği 10 sn boyunca bekletip sonrasında hata vereceğiz demektir. Yoğun yük alan sistemelerde gelen tüm istekleri 10 sn beklemek demek genelde sistem kaynaklarının tükenmesi ve kendi servisimizin de cevap veremez hale gelmesi demektir. Eğer bu çağrıları sigorta ile kurgular ise sigorta devre kestiginde (örnegin arka arkaya 5 timeout hatası aldığında) artık sigorta süresi boyunca diyelim ki 60 sn gelen istekler otomatik olarak hata alır ve sistemi yormadan hata ile sonuçlanmış olur. (Circuit Breaker Pattern için ve Polly için bakınız)

6. Usability: Sistemin kolay kullanılabilirliğinin ölçütüdür. Sistemin kolay erişilmesi, kolay öğrenilebilmesi ve API kontratlarının kolay anlaşılması kullanılabilirliği arttırmaktadır. Servis katmanında yaptığımız WCF den Rest Api ye geçiş hem daha kolay öğrenilmesini ve entegre edilmesini hem de tüm rest apilerimizde swagger kullanımını standart hale getirdiğimiz için test edilmesini kolaylaştırdık. Yaşadığımız en büyük sıkıntılardan biri de wcf deki kontrat farklarının giderilmesi konusu. Bunların bir kısmı compile time da bir kısmı ise runtime da beklenmedik şeklilerde ortaya çıkarak operasyonel sorunlarada yol açan durumlarla karşılaştık.

7. Observability: Sistemin içinde bulunduğu durum, bileşenler arası iletişim ve bileşenlerin kendi durumlarının takibinin yapılmasıdır. Bizim buradaki temel yaklaşımımız Pars.Core ile grafanada takipe edilecek metriklerin oluşturulması, kibana/elastik de console ve error loglarının takibi, jaeger ya da farklı bir APM aracı ile detay performans metriklerinin takibi şeklinde. Granfana üzerinden de hem sistem hataları hem de çeşitli business metricleri için alarm tanımlayıp datacenter 7x24 ekibimiz tarafından takip edilmesini sağladık. Bu sayede herhangi bir sebeple artan sistem hatalarından ya da sepete eklenen ürünlerin azalması, sipariş azalması gibi işi etkileyen durumlardan hızlıca haberimiz oluyor. artık oldukça azalsa da hataların neredeyse tamamı için müşterilerimizden şikayet gelmeye başlamadan haberdar olup kriz masasını toplamış oluyoruz.

Pars Core 3 kibana dashboard 1/3
Pars Core 3 kibana dashboard 2/3
Pars Core 3 kibana dashboard 3/3

Bu dash board sayesinde hem rest hem de kafka mesajlarının nasıl sonuclandıgını ve ne sürede tamamlandığını rahatlıkla takip edebiliyoruz.

Bu sayede hata alan işlemlerin ya da uzun süren işlemlerin loglarına da kolayca erişebildiğimiz için hem hataları gidermek hem de işlem sürelerini kısaltmak için sürekli iyileştirme çalışmalarını kolayca yapabiliyoruz.

Çağrılar ve mesajlar içinde TraceId ve SpanId değişkenlerini geçiriyoruz. bu sayede farklı servislerin takibi de yapılabiliyor.

8. Agility : Çeviklik Agile Manifesto yayınlamdığından beri giderek artan bir şeklide hayatımıza girdi. Sadece yazılım üretiminde değil bir çok alanda çevik prensipler kullanılıyor. Yazılım üreten ekipler için scrum son on yıldır defacto standart oldu diyebiliriz. Yanlız burada unutulmaması gereken konu takımınız scrum koşabilir ancak bizim uzun zamandır yaşadğımız sorun tek kod base olması bazı bileşenlerin birden fazla takım tarafından değiştirilmesi sebebi ile kod tamamlanıp test ya da üretim ortamına alınmak istendiğinde bir çok farklı ekibin bir araya gelerek yapılan değişikliklerini kontrol etmeleri gerekiyordu. Bu durum bizi hem geliştirim sırasında hem de deployment yaparken yavaşlatıyordu. microservis dönüşümünün development team özelinde belki de en önemli katkısı kodların domain bazlı ayrılarak çok daha yönetilebilir bileşenlerin çok daha hızlı seklide gelişitirilip canlıya alınmasının sağlanması oldu.

Son deployability konusundan bahsetmek istiyorum. Daha önce gece 0400 de planlı olarak sürüm yaptığımızdan bahsetmiştim. Bunun sebebi bir birini etkileme ihtimali olan birçok bileşeni aynı anda çıkmamızın zorunlu olmasının yanında servisler deploy olurken kesinti yaratmama durumunun oluşması idi. Mevcut dönüşüm sonrasında kubernetes deki rollout update özelliği sayesinde major bir database değişikliği olmadığı sürece ki bu çok nadiren oluyor gün içinde isteğimiz zaman kesinti oluşturmadan çıkabiliyoruz. Bir değişikliğin hızlı bir şeklide üretim ortamına alınması gerçekten agility ile hedeflenen çıktının kendisidir. Zaten HPA kullanımı gün içinde podların yüke göre eklenip çıkarılmasını gerektiriyor. Bu işlemlerin sorunsuz olmasını nasıl sağladığımızı Kubernetes Pod Lifecycle yönetimi başlığında aktarmıştık.

Deploy olan podlar trafik alıp metric üretmeye başlayıncaki görüntü
Deploy olan podların metric üretmeye başlayıncaki pod count grafiğine yansıması

Agility kavramını, hızlı takım ile yavaş takımı, devops u etkin kullanan ve kullanmayan takımı ayırt etmeye yarayan en iyi metrikler DORA metrikleri olarak adlandırılıyor. Bu metrikleri aktif olarak kullanıyoruz ve bu konuda da bir yazı yazmak istediğimden detay aktarmayacağım ama linki incelemenizi ve öneririm.

9. Security : eticaret uygulamamız için Pars.Core geçişi ile birlikte dış dünyaya açtığımız servislerimiz için JWT token kullanımına başladık. LC Waikiki de ortak servislerin suduğu hizmet olan STS ve KeyCloak provider olarak kullanılıyor. Yakın zamanda lcwaikiki.com için de kendi providerımızı geliştiriyor olacağız. Şu an marketplace ara yüzümüz seller.lcwaikiki.com ve yeni back office’imiz bo.lcwaikik.com (internal) da benzer yapıları hayata geçirdik ve güvenlik konusunda oldukça iyi bir noktaya geldik.

Sonuç olarak bu maddeler hakkında bahsederken eticaret sistemimiz için hedef mimari olarak belirledigimiz cloud native, multi data cetner bir micro servis uygulama olma perspektifinden yola çıkarak bahsettim. Microservisler ya da Kubernetes olmadan önce de sistemler bu NFR ları karşılamak zorunda idi bir gün microservisler yerine başka mimariler de popülerlik kazandığında onlarda bu NFR ları karşılamak zorunda olacak. Elbette ölçek sürekli değişiyor ve gerçekten size does matter. :)

Yazıyı hazırlamak için faydalandığım ve ilham aldığım makalenin linkini de aşağıya bırakıyorum.

https://www.redhat.com/architect/nonfunctional-requirements-architecture

--

--