31 Ekim 2022 Pazartesi

Resilience - Esneklik - Dayanıklılık

Resilience, Mikroservislerimizin meydana gelebilecek hataları tolere edebilme kabiliyetidir. 

Mikroservislerimizde kısmı hatalar meydana gelebilir. Böyle hatalar meydana geldiğinde bunları tolere edebilmeliyiz.

Örn. 10 tane mikroservisimizin oluğunu ve bir tane mikroservisimizin 5 saniye boyunca down olduğunu düşünelim. Böyle bir senaryoda bir servisin down olması tüm sistemi etkilememeli ve  mikroservis mimarimizin bunu tolere edebilmesi gerekir. Yada benzer şekilde bir mikroservimiz veri tabanına bağlanmak istediğinde veri tabanı 5 sn. cevap vermiyorsa bu 5 saniyelik gecikmeyi mikroservis mimarimizin tolere edebilme kabiliyeti olması gerekiyor.

Resilience Amacı : Kısmi bir hata meydana geldikten sonra uygulamanın çalışmasına aynen devam edebilmesidir.

Failure Example: Bu kısmi hatalara örnek olarak Network dar bağazı verilebilir.

Resilience Nasıl Arttırılabilir ? 

  • Retry
  • Circuit Broker Pattern

  • Microservisimizin resiliencysinin yüksek olmasını istiyorsak senkron ve asenkron iletişime dikkat etmemiz gerekiyor. 
  • Yukarıdaki örnekte Clientın ilk iletişime geçtiği microservisler E ve C mikroservisleridir. Client ile bu mikroservisler arası iletişim senkron olmalı mikroservislerimizin kendi aralarındaki ilişkiler ise asenkron olmalıdır.
Synchronous Communication : C Servisinden B servisine bir request attığımızda bu requestin sonucunu bekliyorsak ve bu süre zarfında bloklanıyorsak bu bir senkron iletişimdir. örn. REST, gRPC
Not: B Servisi down olursa bu request kaybolur.Dezavantaj

Asynchronous Communication : C Servisinden B Servisine data gönderiyorsak ve B sersinin bu data ile ne yaptığının sonucunu beklemiyorsak bu iki servis arasındaki iletişim asenkrondur. Bu tarz iletişimlerde C servisiden B servisine REST isteği yapmak yerine, bu isteği kuyruğa göndeririz. Bu isteği işleyecek olan servis yani B Servisi bu isteği kuyruktan alır ve kendi işlemlerini yapar. B Servisi belli bir süre down olsa bile istekler kuyrukta beklediği için kaybolmazlar. B Servisi tekrar ayağa kalktığında kuyrukta duran istekleri alır ve işlemlerini yapmaya devam eder.

Dikkat: Kullanıcıya bir data göstermek istiyorsak orada Asenkron iletişim olmaz. Yukarıdaki örnekte Client Catalogları listelemek istiyorsa C Servisine senkron bir istek atar. Client C servisinden bu requeste cevap dönene kadar bekler ve bu cevap içerisinde dönmüş olan dataları gösterir.

Poly Library: Microservislerimizde kısmi hataları handle etmeye imkan veren bir kütüphanedir.

Retry Pattern: Bir request başarısız olduğunda bu requestin kaybolup gitmesi yerine  aynı işlemi ilgili birime tekrar göndermektir. E Servisi A servisine http ile bir istek atıyor olsun. A Servisinin kısa süreliğine down olduğunu ve tekrar ayağa kalktığını düşünürsek böyle bir senaryoda istek tekrar retry edilerek yani tekrar denenerek başarıyla ilgili servise ulaştırılır. Bu yöntemi isteği alacak servisin kısa süreli down olacağını düşündüğümüz senaryolarda kullanmalıyız.

TimeOut Pattern
Servisler arası http call yapıldığında bir servis timeouta düştüğünde requesti gönderen servisin çok fazla beklemesini önlemiş oluruz

Fallback
Call edilen servisin bir hata döndürmesi veya zaman aşımlarının meydana gelmesi durumunda döndürülebilecek değer veya gerçekleştirilebilecek eylem sağlamamıza olanak tanır.

Kaç kez yeniden denerseniz deneyin, başarısızlıkların olması kaçınılmazdır, bu nedenle başarısızlık durumunda ne yapılması gerektiğini planlamanız gerekir. Fallbackler  genellikle retry, circuit breaker  gibi  politikalarla birlikte kullanılır.

BulkHead
Uygulamamızın herhangi bir bölümünün tüketebileceği toplam kaynak miktarını sınırlamamıza izin verir.

Order Servisi, Product deteylarını elde etmek için product servisini call ederse ve herhangi bir nedenle Product servisi kullanılamıyorsa, requestler order sevisinde  yedeklenmeye başlar ve order servisininde  performansının düşmesine ve hatta order servisinin  çökmesine bile neden olabilir.

Bulkhead Isolation, uygulamanın bir bölümünü izole etmeye yardımcı olur ve Bellek, CPU, Soketler, thread vb.  kullanımını kontrol eder, böylece uygulamanızın bir bölümü düzgün çalışmıyorsa, bu politika bu bölümün tüm uygulamayı etkilemesini veya durdurmasını engeller.

Circuit Breaker Patern: 
Başarısız http request sayısı daha önceden belirlediğimiz  hata sayısı değerini aştığında belirlediğimiz süre boyunca request atılan servise istek atılmasını önlemiş olacağız.
E Servisi A servisine 3 saat gibi bir süre boyunca erişemezse gereksiz yere retry pattern ile E Servisinden A Servisine istek atıyor oluruz. Down olan bir servisin 3 saniye yada 5 saniye gibi  kısa bir sürede ayağa kalkacağını düşünüyorsak retry patterni uzun bir süre down olarak kalacağını düşünüyorsak circuit breaker pattern kullanmalıyız.


A ve B olmak üzere 2 adet  servisimiz ve Open, Close ve Half Open olmak üzere 3 adet durumumuz var. 
Closed Devre Durumu: A Servisi B Servisine istek atabiliyor. Bizim kuralımıza göre  A Servisi B Servisine 30 saniye boyunca 5 adet başarısız istek atarsa devre Open Statuye geçsin.

Open Devre Durumu: Devrenin statusu Open ise A Servisi B Servisine istek attığında Circuit Breaker Pattern araya giriyor ve B Servisine istek atmak yerine direkt Exception dönüyor. B Servisinin ayakta olmadığını bildiği için gereksiz yere istek göndermiyor. İkinci kuralımız ise 60 saniye bekle ve durumu Half Open Statuye geçir.

Half Open Devre Durumu: Half Open Statudeyken A Servisi B Servisine bir adet istek atıyor. Eğer B Servisi ayaktaysa sistem Close Devre Durumuna geçiyor ve düzgün şekilde işlemeye devam ediyor. Fakat B Ayakta değilse tekrar Open Devre Durumuna geçip Exception fırlatmaya devam ediyor.


Github Uygulama



BLOG REFERENCE




Micro Servisler Arası Senkron Ve Asenkron İletişim

 Servisler kendi aralarında aşağıdaki şekillerde haberleşir.
  • Synchronous  Request/Response-based Communication Mechanisms (HTTP-Based REST, gRPC)
  • Asynchronous Message-Based Communication Mechanisms(AMQP, STOMP)RabbitMQ,Kafka 

İnteraction Styles

One-to-one : Requestin  sade ve sadece bir tane service tarafında işlenmesi
Ono-to-many : Requestin birden çok servis tarafından işlenmesi
Synchronous : Client servisten yanıt dönene kadar bekler ve requeste cevap dönene kadar Clientın yaptığı işlem  bloklanır.
Asynchronous  : Client requestin yanıtını beklemez ve bloklanmaz.

SENKRON HABERLEŞME

REST Mimari

- REST mimaride resource kavramı vardır. Bu resource arka tarafta bir business objete karşılık gelir
Bir resource almak için bir request göndeririz ve bu requeste karşılık istek yapılan yerden bize  resource döner. Birden fazla resourceye ihtiyacımız olursa ne olacak ? Bu durum genelde microsevisler arası iletişimde REST mimariyi kullandığımızda dezavantaj olarak ortaya çıkar.
- REST ile http isteği attığımızda resource api çalışmadığı zaman o request boşa gider. Kaybolur.
- Clientlar resourceye ait URLleri bilmek zorundadır.


gRPC

gRPC, RPC(Remote Procedure Call) nin bir alt kolu gibi düşünebiliriz. A noktası ile B noktası arasında bir iletişim varsa bir request gönderiyoruz demektir. Bu iki nokta arasında haberleşme olabilmesi için aralarında ortak bir dil olmak zorundadır ve aynı zamanda bir mesajllaşma formatı olmalıdır. Default olarak gRPC'de HTTP/2 kullanılır. Message formatı olarakda Protol Bufferı kullanılır. Buda bir binary formattır. Ayrıca gRPC de tagleme formatı vardır. Bu tagleme formatıda şu anlama geliyor. Proto dosyasının içine baktığımızda fieldın adını, numarasını ve veri tipini falan anında görebiliyoruz. Buradaki fieldları bir, iki vs. diyerek tagliyoruz. Bu tagleme işleminin de mantığı veri kablo üzerinde giderken bize yardımcı oluyor.

Yukarıdaki REST ve gRPC gibi http requesti atılan bir resourse api servisi down olduğu durumda bu request kaybolur. Bu tarz durumlarla başa çıkabilmek için microservis mimarimizin resiliency(Dayanıklılık, Esneklik) yüksek olmalıdır.

ASENKRON HABERLEŞME

- Genelde bir message brrokera ihtiyaç duyulur. Client bu brokere isteği gönderir ve daha sonradan bunun cevabının kendine gönderileceğini bilir. Bu iletişimde mesajlar ve kanallar vardır. Gönderici mesajı kanal üzerinden gönderir daha sonra bu mesajı alan karşı taraf belli bir işlemi yaptıktan sonra Client tarafına bildirimde bulunmaya çalışır.

- Mesaj: Bir header ve datanın kendisin olduğu bodyden oluşur.

- Mesaj kuyruğu sayesinde, Consumer servis bir sebepten ötürü erişilemez durumdayken veri kuyrukta kalır ve tekrar erişilebilir olunca data kaybı yaşamadan kuyruğu tüketmeye devam eder.

Domain Event Pattern

Domain Event sistemde meydana gelen olayların kayıdıdır.

 Bir domain event geçmiş zamanda meydana gelmiş bir olayı temsil eder. Yani bu olay gerçekleşmiştir. Ve bu olayın gerçekleşmesiyle ilgilenen diğer ilgililer bilgilendirilir. Bu bilgilendirme işini yapmak içinde MediatR kütüphanesi kullanılabilir. 

Örneğin MemberCreatedDomainEvent, GatheringCreatedDomainEvent, InvitationAcepptedDomainEvent.

Domain Eventler immutabledır.



24 Ekim 2022 Pazartesi

Value Object

  • Value Object Id si yoktur.
  • immutable(değişemez)dir.
  • Property valueları eşit olan iki value object nesnesi eşit kabul edilir.(Phone, Address, Money, İsim)


Value Objectler sadece değerleriyle tanımlanan türlerdir. Değerleri aynıysa iki value object eşit kabul edilir. Value Objectler daha karmaşık yapıları temsil etmek için primitive tipler(int, string, boolean vs.) kullanırken ortaya çıkan sorunları çözmek için kullanılır.

FirstName alanı için databasede nvarchar tipinde bir alan tutmamız gerekiyor. Dolayısıyla da uygulama tarafında string tipinde saklamak durumunda kalıyoruz. Yada PhoneNumber alanı için aynı şekilde string tipini kullanıyoruz. Mesela string sınıfına ait  toLower() metodu için konuşursak bu metod phoneNumber için uygun mudur? Tabiki uygun değildir. Onun için PhoneNumberı value object olarak tanımlayabiliriz. Yada aynı şekilde firstName alanınıda value object olarak tanımlayabiliriz. Value objectler içerisinde kendisi ile ilgili bussineslara sahip olabilir.




Yukarıdaki member sınıfını ele aldığımız da member sınıfından nesne üretmek için

Member member1 = new Member(37,"Aytaç","demirci","aytac@gmail.com")

Member member2 = new Member(37,"aytac@gmail.com","Aytaç","Demirci")

Firstname yerine email, email yerine isim yazdığımızda bu bir hata vermez ve çalışır fakat doğru bir işlem yapmış olmayız. Ayrıca firstName için en az 50 karakter olsun. Empty olmasın gibi kısıtlar getirmek isteyebiliriz. İşte bunu sağlayabilmek için value objectleri kullanabiliriz.

namespace Gatherly.Domain.ValueObjects
{
    public sealed class FirstName : ValueObject
    {
        private FirstName() { }
        public const int MaxLength = 50;
        public string Value { get; }

        private FirstName(string value)
        {

            Value = value;
        }

        public override IEnumerable<object> GetAtomicValues()
        {
            yield return Value;   
            //Bu value objectin extra propertyleri olursa buraya eklenecek.
        }

        public static FirstName Create(string firstName)
        {
            if (string.IsNullOrWhiteSpace(firstName))
            {
                throw new ArgumentNullException($"{nameof(firstName)} is empty.");
            }

            if (firstName.Length > MaxLength)
            {
                throw new InvalidLenghtException($"{nameof(firstName)} is too long.");
            }
            
            return new FirstName(firstName);
        }
    }
}


Alttaki abstract GetAtomicValues metodu miras alınan sınıflarda override edilip buraya o sınıfın propertyleri yield return peropertyname şeklinde eklenecektir.


public abstract class ValueObject : IEquatable<ValueObject>
    {
        public abstract IEnumerable<object> GetAtomicValues();

        public override bool Equals(object? obj)
        {
            return obj is ValueObject other && ValuesAreEqual(other);  
        }
        public bool Equals(ValueObject? other)
        {
            return other is not null && ValuesAreEqual(other);
        }

        private bool ValuesAreEqual(ValueObject other)
        {
            return GetAtomicValues().SequenceEqual(other.GetAtomicValues());
        }
        public override int GetHashCode()
        {
            return GetAtomicValues()
                .Aggregate(default(int), HashCode.Combine);
        }
    }

kullanmak istediğimiz yerde aşağıdaki gibi value objecti oluşturup kullanabiliyoruz.





Domain Validation

 Domain Layer da Validationu nasıl uygularız ? 

Yukarıda bir Enum tipi olan Type göre Switch içerisinde 2 ayrı case var ve bu caselere göre ayrı ayrı validation kurallarımız bulunmaktadır. Bu validation kurallar sağlanmazsa base Exception sınıfını kullanarak throw exception yapmaktayız. Fakat bu yaklaşım çok tavsiye edilmez. Bu ilettiğimiz mesajdan başka bir anlam taşımamaktadır. Bunun yerine her validasyon kuralı için bir Custom Exception yazılabilir. 

I-) DomainException isminde Exception sınıfından  miras alan bir sınıf oluşturuyoruz.


II-) En yukarıda Switch içindeki ikinci case için oluşan Validation Rule Exceptionı için örnek exception yazımı aşağıdadır.


III-) Son olarak Switch içindeki Exceptionlar yazmış olduğumuz CustomExceptionlarla değiştirilir.



Entity

  • Entity, Pek çok nesne temelde attribütleriyle değilde, Uniq identity (Id)  ile tanımlanır. 
  • Uniq Identitysi ile tanımlanan nesneye Entity denir ve Entityler mutabledır.(Değişebilir).
  • Entityler compare edilirken Id'leri kontrol edilir. Eğer Idleri aynı ise eşit kabul edilirler. 



- 6. satır Entity sınıfını abstract olarak tanımlıyoruz. Hiç kimse bu Entity clasından bir nesne oluşturamasın.

- 12.satır Bir Entity yaratıldığında ve ona constructerda bir Id atandıktan sonra artık bu Id değerinin hiç bir zaman değiştirilememesini sağlamak için Id propertisinin set metodunu silip init yapıyoruz. Daha sonra private init yaparak bu Entitye Id ataması işlemini sadece bu Entity clası içerisinde yapılabileceğini garanti ediyoruz.


Entityler oluşturulurken zorunlu olan propertyler constructer üzerinden alınmalıdır. Diğer değerler metodlar aracılığıyla alınmalıdır. 



Yukarıdaki screen incelenirse eğer;

  • Id, FirstName ve LastName alanları requireddır. Required alan oldukları için bunları setlenme işlemi constructer üzerinden yapılmaktadır.
  • PhoneNumber ve CustomerId alanları value objecttir. CustomerId required iken PhoneNumber required değildir. Bu yüzden PhoneNumber alanını constructrdan almaya gerek yoktur. 
  • Constructer içerisinde FirstName ve LastName alanları için eğer nullsalar exception throw etmesi gerektiği kuralı yazılmıştır. Bu yüzden customer.FirstName = "aytac"; şeklinde bir set işlemi yapılmaması gerekmektedir. Eğer bu şekilde setleme işlemi yaparsak bu kuralı ihlal etmiş olacağız. Yani bu kuralı uygulamadan firstname yada lastname set etmiş olacağız. Bunu önlemek için set alanları private yapıyoruz. Artık bu alanların değerleri sadece bu customer sınıfı içerisinden yapıalabilecektir. Ve bu alanları update edebilmemizi sağlayacak UpdateNameAndLastName() metodu yazılmıştır. FirstName ve LastName alanları bu sınıf dışından  sadece bu metod kullanılarak update edilebilecektir.
  • Customer içerisinde PhoneNumber value objesini set etmek için bir metot yazmaya gerek yoktur. Çünkü PhoneNumbera ait gerekli validasyon PhoneNumbervalue object sınıfının içerisindeki static create metodu içerisinde zaten yazılmıştır.


PhoneNumber sınıfı içinde Constructer private yapıldığı için PhoneNumber nesnesini sadece kendisi içerisinde  newlenerek create edilebilir. Static ve geriye PhoneNumber dönen bir Create metoduyla PhoneNumber yaratılabilecektir ve bu Create metodu içerisinde PhoneNumber için gerekli bussiness rulelar yazılmıştır.

Customera ait phone number değerini set etmek için aşağıdaki gibi set etmek yeterli olacatır. Eğer Customer özelinde PhoneNumber için özel bussines rulelar uygulamamız gerekir o zaman PhoneNumberi set etmek için Customer sınıfı içerisinde bir metod yazabiliriz.





Domai Drive Desig Etitiy
  public abstract class Entity : IEquatable<Entity>
    {
        protected  Entity(Guid id)
        {
            Id = id;
        }
        protected Entity()
        {

        }
        public Guid Id { get; private init; }

        public override bool Equals(object? obj)
        {
            if(obj is null)
            {
                return false;
            }

            if(obj.GetType() != GetType())
            {
                return false;
            }

            if(obj is not Entity entity)
            {
                return false;
            }
            return entity.Id == Id;
        }

        public bool Equals(Entity? other)
        {
            if (other is null)
            {
                return false;
            }

            if (other.GetType() != GetType())
            {
                return false;
            }

            return other.Id == Id;
        }
        public override int GetHashCode()
        {
            return Id.GetHashCode();
        }

        public static bool operator ==(Entity? first, Entity? second)
        {
            return first is not null && second is not null && first.Equals(second); 
        }
        public static bool operator !=(Entity? first, Entity? second)
        {
            return !(first == second);
        }
    }

19 Ekim 2022 Çarşamba

Domain Model İçerisinde Collectionlar ile çalışmak

 


Yukarıda görüldüğü gibi Gathering clası içerisinde Invitations isminde bir List bulunmaktadır. Projenin herhangi bir yerinde bir gathering nesnesi oluştulup daha sonra bu nesnenin içindeki Invitation listesi üzerinde Remove ve Add işlemleri yapılabilir.



Böyle bir durumla başa çıkabilmek için ilk olarak Gathering sınıfındaki Invitation listesine eleman eklemek için Gathering sınıfı içerisinde geriye invitation dönen bir adet SendInvitation isminde metod oluşturacağız ve bu metodun görevi bir adet Invitation nesnesi oluşturmak ve bu nesneyi Gathering sınıfındaki invatation list collectionuna eklemek olacaktır.

Burada invation nesnesinin sadece Domain katmanında oluşturulmasını sağlamak içinde invaitation sınıfının  constructeri internal yapılır. Artık Application Layerda invitation nesnesi new() lenemez.



Gathering sınıfındaki List<Invitation> şeklinde kullanılan tanımlama  IReadOnlyCollection ile değiştirilir. IReadOnlyCollection içersinde Add ve Remove metodları yoktur. Böylece projenin herhangi bir yerinde bu liste üzerinde Remove yada Add işlemi yapılamaz.

Son olarak güncellememiz gerekirse, Gathering sınıfı içerisinde bir adet private readonly _invitations isminde invitation List oluşturuyoruz. Daha sonra IReadOnlyCollection invitation propertysinin resultına bunu ekliyoruz.




18 Ekim 2022 Salı

Anemic Domain Modeli Rich Domain Modele Dönüştürmek

 1 - Anemic model yapıda constructer ve metodlar olmaz. Bu class içerisinde sadece data propertyler bulunur.

- Bu anemic modele ilk uygulayacağımız işlem Gathering Classı içerisinde tüm propertyleri kabul eden bir constructer tanımlamak olacaktır. T
- Tüm propertylerin set metodunu private yaparak bu propertylere atanacak değerlerin sadece constructer üzerinden yapılabilmesini ve başka hiç bir  türlü de değiştirilememesini garanti etmiş olacağız.





Gathering sınıfından bir nesne oluşturmak istersek;
var gathering = new Gathering(Guid.NewGuid(), datetime.now); 

Başka bir yol olarakta constructeri private yapıp geriye Gathering dönen bir  static create metodu yardımıyla bu nesneyi oluşturabiliriz.



 Create Gathering


Constructeri private yaptığımız için projenin hiç bir yerinde new Gathering() yapamayız.

Propertylerin set metodunu private yaptıgımız içinde gathering.Name = "Aytaç" yapamayız

Clean Architecture

 

Clean Architecture Nedir ? 

    4 ayrı bileşeni vardır ve bunlara Clean Arthitecturenin layerları denir.
Clean Arthitecturenin merkezinde sistemin core kısmını temsil eden Domain Layer yer alır. Domain Layerın etrafında, Domain katmanını düzenleyen ve busines logici nasıl gerçekleştireceğini söyleyen Application katmanı yer alır. Mimarinin en dışında da Presentation ve Infrastructure katmanlarına sahibiz.

Celan Arthitecturede coredan başlayıp dışarıya doğru ilerleyeceğiz.

Domain Layer katmanında en önemli busines kurallarını içeren Domain Entities, Aggregates, Value Objects, Domain Events, Repository Interfaces, Factory Interfaces ve Domain Services gibi yapıları tanımlarız. Burası ayrıca Custom Exceptionları tanımlamak için uygun bir yerdir.
Domain layerda dikkat etmemiz gereken en büyük kural bu layer dış katmanlardan hiç birini reference almamalıdır. Fakat diğer layerlar domain layeri referance alabilir.

Application Layer bu katman sistemimiz için bir orchestrator(düzenleyici) görevi görür. Burada önemli Use Caseleri tanımlayacağız. Tipik olarak bu katman bir dizi application servisi olarak implemente edilir. Eğer CQRS uygulanıyorsa Command Ve Querylerde bu katmanda yer alır.

Infrastructure Layer burada External Systemle alakalı herşey yer alacak. Database Access, Message Queues(RabbitMQ or Kafka), Email and Notification Services, Storage Services.
Bu katman entegre etmemiz gereken tüm external sistemler için uygulama detaylarını gizlemekten sorumludur. 

Presentation Layer, Userların sistemimizle etkileşime girebilmesi için bir entry point(giriş noktası) tanımlamak için kullanılır.Genelde 





  • There will be not any reference to Domain Library
  • Application: Add reference of Domain project
  • Infrastructure: Add reference of application project
  • WebApi: Add reference of application and Infrastructure projects