DDD etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
DDD etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

31 Ekim 2022 Pazartesi

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