Share via


Etki alanı olayları: Tasarım ve uygulama

İpucu

Bu içerik, .NET Docs'ta veya çevrimdışı olarak okunabilen ücretsiz indirilebilir bir PDF olarak sağlanan Kapsayıcılı .NET Uygulamaları için .NET Mikro Hizmet Mimarisi e-Kitabı'ndan bir alıntıdır.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

Etki alanınızdaki değişikliklerin yan etkilerini açıkça uygulamak için etki alanı olaylarını kullanın. Başka bir deyişle ve DDD terminolojisini kullanarak etki alanı olaylarını kullanarak birden çok toplamada yan etkileri açıkça uygulayın. İsteğe bağlı olarak, veritabanı kilitlerinde daha iyi ölçeklenebilirlik ve daha az etki alanı için aynı etki alanındaki toplamalar arasında nihai tutarlılık kullanın.

Etki alanı olayı nedir?

Olay, geçmişte gerçekleşen bir şeydir. Etki alanı olayı, etki alanında gerçekleşen ve aynı etki alanının diğer bölümlerinin (işlem içi) farkında olmasını istediğiniz bir olaydır. Bildirilen parçalar genellikle olaylara bir şekilde tepki gösterir.

Etki alanı olaylarının önemli avantajlarından biri, yan etkilerin açıkça ifade edilebiliyor olmasıdır.

Örneğin, yalnızca Entity Framework kullanıyorsanız ve bazı olaylara karşı bir tepki olması gerekiyorsa, büyük olasılıkla olayı tetikleyene yakın ihtiyacınız olan her şeyi kodlarsınız. Bu nedenle kural örtük olarak kodla birleştirilir ve kuralın orada uygulandığını fark etmek için kodu incelemeniz gerekir.

Öte yandan, etki alanı olaylarının kullanılması kavramı açık hale getirir, çünkü en az bir DomainEventDomainEventHandler tane söz konusu olur.

Örneğin, eShop uygulamasında, bir sipariş oluşturulduğunda, kullanıcı alıcı olur, bu nedenle içinde bir OrderStartedDomainEvent yükseltilir ve işlenir ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler, bu nedenle temel kavram açıktır.

Kısacası etki alanı olayları, etki alanı uzmanları tarafından sağlanan yaygın dil temelinde etki alanı kurallarını açıkça ifade etmeye yardımcı olur. Etki alanı olayları ayrıca aynı etki alanındaki sınıflar arasında daha iyi bir ayrım sağlar.

Bir veritabanı işlemi gibi, bir etki alanı olayıyla ilgili tüm işlemlerin başarıyla tamamlanmasını veya hiçbirinin tamamlanmamasını sağlamak önemlidir.

Etki alanı olayları, önemli bir farkla mesajlaşma stili olaylara benzer. GERÇEK mesajlaşma, ileti kuyruğa alma, ileti aracıları veya AMQP kullanan bir hizmet veri yolu ile, bir ileti her zaman zaman uyumsuz olarak gönderilir ve işlemler ve makineler arasında iletişim kurar. Bu, birden çok Sınırlanmış Bağlamı, mikro hizmeti ve hatta farklı uygulamaları tümleştirmek için kullanışlıdır. Ancak, etki alanı olaylarıyla, şu anda çalıştırdığınız etki alanı işleminden bir olay tetiklersiniz, ancak yan etkilerin aynı etki alanında gerçekleşmesini istersiniz.

Etki alanı olayları ve yan etkileri (daha sonra olay işleyicileri tarafından yönetilen eylemler) hemen hemen, genellikle işlemde ve aynı etki alanında gerçekleşmelidir. Bu nedenle, etki alanı olayları zaman uyumlu veya zaman uyumsuz olabilir. Ancak tümleştirme olayları her zaman zaman zaman uyumsuz olmalıdır.

Etki alanı olayları ile tümleştirme olayları karşılaştırması

Anantolojik olarak etki alanı ve tümleştirme olayları aynı şeydir: az önce gerçekleşen bir şey hakkındaki bildirimler. Ancak, uygulamalarının farklı olması gerekir. Etki alanı olayları yalnızca bir etki alanı olay dağıtıcısına gönderilen ve IoC kapsayıcısına veya başka bir yönteme dayalı olarak bellek içi bir aracı olarak uygulanabilen iletilerdir.

Öte yandan tümleştirme olaylarının amacı, diğer mikro hizmetler, Sınırlanmış Bağlamlar ve hatta dış uygulamalar olsun, kaydedilmiş işlemleri ve güncelleştirmeleri ek alt sistemlere yaymaktır. Bu nedenle, yalnızca varlık başarıyla kalıcı hale gelirse gerçekleşmelidir, aksi takdirde tüm işlem hiç gerçekleşmemiş gibi olur.

Daha önce belirtildiği gibi, tümleştirme olayları birden çok mikro hizmet (diğer Sınırlanmış Bağlamlar) ve hatta dış sistemler/uygulamalar arasındaki zaman uyumsuz iletişimi temel almalıdır.

Bu nedenle, olay veri yolu arabirimi, olası uzak hizmetler arasında işlemler arası ve dağıtılmış iletişime izin veren bir altyapıya ihtiyaç duyar. Ticari hizmet veri yolunu, kuyrukları, posta kutusu olarak kullanılan paylaşılan veritabanını veya diğer dağıtılmış ve ideal olarak gönderme tabanlı mesajlaşma sistemini temel alabilir.

Etki alanı olayları, aynı etki alanı içindeki birden çok toplamada yan etkileri tetiklemenin tercih edilen bir yoludur

Bir toplama örneğiyle ilgili bir komutu yürütmek için bir veya daha fazla ek toplamada ek etki alanı kurallarının çalıştırılması gerekiyorsa, etki alanı olayları tarafından tetiklenecek bu yan etkileri tasarlamanız ve uygulamanız gerekir. Şekil 7-14'te gösterildiği gibi ve en önemli kullanım örneklerinden biri olarak, durum değişikliklerini aynı etki alanı modeli içindeki birden çok toplamaya yaymak için bir etki alanı olayı kullanılmalıdır.

Diagram showing a domain event controlling data to a Buyer aggregate.

Şekil 7-14. Aynı etki alanı içindeki birden çok toplama arasında tutarlılığı zorlamak için etki alanı olayları

Şekil 7-14'te, toplamalar arasındaki tutarlılığın etki alanı olayları tarafından nasıl elde edildiği gösterilmektedir. Kullanıcı bir sipariş başlattığında, Sipariş Toplaması bir OrderStarted etki alanı olayı gönderir. OrderStarted etki alanı olayı, kimlik mikro hizmetindeki özgün kullanıcı bilgilerine (CreateOrder komutunda sağlanan bilgilerle) bağlı olarak sipariş mikro hizmette bir Alıcı nesnesi oluşturmak için Alıcı Toplaması tarafından işlenir.

Alternatif olarak, toplama kökünün, toplamalarının üyeleri (alt varlıklar) tarafından tetiklenen olaylar için abone olması da olabilir. Örneğin, her OrderItem alt varlığı, madde fiyatı belirli bir tutardan yüksek olduğunda veya ürün öğesi miktarı çok yüksek olduğunda bir olay oluşturabilir. Toplama kökü daha sonra bu olayları alabilir ve genel bir hesaplama veya toplama gerçekleştirebilir.

Bu olay tabanlı iletişimin doğrudan toplamalar içinde uygulanmadığını anlamak önemlidir; etki alanı olay işleyicilerini uygulamanız gerekir.

Etki alanı olaylarını işlemek bir uygulama sorunudur. Etki alanı modeli katmanı yalnızca etki alanı mantığına odaklanmalıdır; depoları kullanan işleyiciler ve yan etki kalıcılığı eylemleri gibi uygulama altyapısı değil, etki alanı uzmanının anlayabileceği şeyler. Bu nedenle, uygulama katmanı düzeyi, bir etki alanı olayı tetiklendiğinde eylemleri tetikleyen etki alanı olay işleyicilerine sahip olmanız gereken yerdir.

Etki alanı olayları, herhangi bir sayıda uygulama eylemini tetikleme amacıyla da kullanılabilir ve daha da önemlisi, gelecekte bu sayıyı ayrıştırılmış bir şekilde artırmak için açık olması gerekir. Örneğin, sipariş başlatıldığında, bu bilgileri diğer toplamalara yaymak ve hatta bildirimler gibi uygulama eylemleri oluşturmak için bir etki alanı olayı yayımlamak isteyebilirsiniz.

Önemli nokta, bir etki alanı olayı gerçekleştiğinde yürütülecek açık eylem sayısıdır. Sonunda, etki alanı ve uygulamadaki eylemler ve kurallar büyür. Bir şey olduğunda karmaşıklık veya yan etki eylemlerinin sayısı artar, ancak kodunuz "tutkal" (ile belirli nesneler newoluşturma) ile birleştirilirse, yeni bir eylem eklemeniz gerektiğinde, çalışan ve test edilmiş kodu da değiştirmeniz gerekir.

Bu değişiklik yeni hatalara neden olabilir ve bu yaklaşım SOLID'ın Açık/Kapalı ilkesine de aykırıdır. Yalnızca bu değil, işlemleri düzenleyen özgün sınıf büyüyecek ve büyüyecek ve bu tek sorumluluk ilkesine (SRP) aykırı olacaktır.

Öte yandan, etki alanı olaylarını kullanıyorsanız, bu yaklaşımı kullanarak sorumlulukları ayırarak ayrıntılı ve ayrılmış bir uygulama oluşturabilirsiniz:

  1. Bir komut gönderin (örneğin, CreateOrder).
  2. Komutu bir komut işleyicisinde alın.
    • Tek bir toplama işlemini yürütür.
    • (İsteğe bağlı) Yan etkiler için etki alanı olaylarını tetikler (örneğin, OrderStartedDomainEvent).
  3. Birden çok toplama veya uygulama eyleminde açık sayıda yan etki yürütecek etki alanı olaylarını (geçerli işlem içinde) işleyin. Örneğin:
    • Alıcı ve ödeme yöntemini doğrulayın veya oluşturun.
    • Durumları mikro hizmetlere yaymak veya alıcıya e-posta gönderme gibi dış eylemleri tetikleme amacıyla olay veri yolu ile ilgili bir tümleştirme olayı oluşturun ve gönderin.
    • Diğer yan efektleri işleyebilir.

Şekil 7-15'te gösterildiği gibi, aynı etki alanı olayından başlayarak, etki alanındaki diğer toplamalarla ilgili birden çok eylemi veya tümleştirme olayları ve olay veri yolu ile bağlanan mikro hizmetler arasında gerçekleştirmeniz gereken ek uygulama eylemlerini işleyebilirsiniz.

Diagram showing a domain event passing data to several event handlers.

Şekil 7-15. Etki alanı başına birden çok eylemi işleme

Uygulama Katmanı'nda aynı etki alanı olayı için çeşitli işleyiciler olabilir, bir işleyici toplamalar arasındaki tutarlılığı çözebilir ve başka bir işleyici tümleştirme olayı yayımlayabilir, böylece diğer mikro hizmetler bu olayla bir şeyler yapabilir. Mikro hizmetin davranışı için depolar veya uygulama API'si gibi altyapı nesnelerini kullanacağınızdan, olay işleyicileri genellikle uygulama katmanındadır. Bu anlamda, olay işleyicileri komut işleyicilerine benzer, bu nedenle her ikisi de uygulama katmanının bir parçasıdır. Önemli fark, bir komutun yalnızca bir kez işlenmesi gerektiğidir. Her işleyici için farklı bir amaca sahip birden çok alıcı veya olay işleyicisi tarafından alınabildiğinden, bir etki alanı olayı sıfır veya n kez işlenebilir.

Etki alanı olayı başına açık sayıda işleyiciye sahip olmak, geçerli kodu etkilemeden gerektiği kadar etki alanı kuralı eklemenize olanak tanır. Örneğin, aşağıdaki iş kuralını uygulamak birkaç olay işleyicisi (hatta yalnızca bir tane) eklemek kadar kolay olabilir:

Mağazadan bir müşteri tarafından satın alınan toplam tutar, herhangi bir sayıda siparişte 6.000 TL'yi aştığında, her yeni siparişe %10 indirim uygulayın ve müşteriye gelecekteki siparişler için bu indirim hakkında bir e-posta gönderin.

Etki alanı olaylarını uygulama

C# dilinde bir etki alanı olayı, aşağıdaki örnekte gösterildiği gibi etki alanında gerçekleşenlerle ilgili tüm bilgileri içeren, DTO gibi veri tutan bir yapı veya sınıftır:

public class OrderStartedDomainEvent : INotification
{
    public string UserId { get; }
    public string UserName { get; }
    public int CardTypeId { get; }
    public string CardNumber { get; }
    public string CardSecurityNumber { get; }
    public string CardHolderName { get; }
    public DateTime CardExpiration { get; }
    public Order Order { get; }

    public OrderStartedDomainEvent(Order order, string userId, string userName,
                                   int cardTypeId, string cardNumber,
                                   string cardSecurityNumber, string cardHolderName,
                                   DateTime cardExpiration)
    {
        Order = order;
        UserId = userId;
        UserName = userName;
        CardTypeId = cardTypeId;
        CardNumber = cardNumber;
        CardSecurityNumber = cardSecurityNumber;
        CardHolderName = cardHolderName;
        CardExpiration = cardExpiration;
    }
}

Bu temelde OrderStarted olayıyla ilgili tüm verileri barındıran bir sınıftır.

Etki alanının yaygın dili açısından, bir olay geçmişte gerçekleşen bir şey olduğundan, olayın sınıf adı OrderStartedDomainEvent veya OrderShippedDomainEvent gibi geçmiş zamanlanmış bir fiil olarak temsil edilmelidir. Etki alanı olayı, eShop'taki mikro hizmeti sıralamada bu şekilde uygulanır.

Daha önce de belirtildiği gibi, olayların önemli bir özelliği, bir olayın geçmişte gerçekleşen bir şey olduğu için değişmemesi gerektiğidir. Bu nedenle, sabit bir sınıf olmalıdır. Önceki kodda özelliklerin salt okunur olduğunu görebilirsiniz. Nesneyi güncelleştirmenin hiçbir yolu yoktur, yalnızca oluşturduğunuzda değerleri ayarlayabilirsiniz.

Burada, etki alanı olaylarının zaman uyumsuz olarak işlenmesi durumunda olay nesnelerini seri hale getirme ve seri durumdan çıkarma gerektiren bir kuyruk kullanılarak özelliklerin salt okunur yerine "özel küme" olması gerektiğini, dolayısıyla seri durumdan çıkarıcının değerleri sıralama sırasında atayabilmesini sağlamak önemlidir. Etki alanı olayı pub/sub, MediatR kullanılarak zaman uyumlu bir şekilde uygulandığından, bu sipariş mikro hizmetindeki bir sorun değildir.

Etki alanı olaylarını tetikle

Bir sonraki soru, ilgili olay işleyicilerine ulaşması için bir etki alanı olayının nasıl yükseltilmesidir. Birden çok yaklaşım kullanabilirsiniz.

Udi Dahan başlangıçta olayları yönetmek ve yükseltmek için statik bir sınıf kullanarak (örneğin, Etki Alanı Olayları – 2'yi Al gibi çeşitli ilgili gönderilerde) önerdi. Bu, gibi DomainEvents.Raise(Event myEvent)söz dizimi kullanarak etki alanı olaylarını çağrıldığında hemen tetikleyen DomainEvents adlı statik bir sınıf içerebilir. Jimmy Bogard, benzer bir yaklaşım öneren bir blog gönderisi (Etki alanınızı güçlendirme: Etki Alanı Olayları) yazdı.

Ancak, etki alanı olayları sınıfı statik olduğunda, işleyicilere de hemen gönderilir. Yan etki mantığına sahip olay işleyicileri olay oluştuktan hemen sonra yürütülür çünkü bu, test ve hata ayıklamayı daha zor hale getirir. Test ve hata ayıklama sırasında yalnızca geçerli toplama sınıflarında neler olduğuna odaklanmak istersiniz; diğer toplamalar veya uygulama mantığıyla ilgili yan etkiler için aniden diğer olay işleyicilerine yönlendirilmesini istemezsiniz. Bu nedenle, sonraki bölümde açıklandığı gibi diğer yaklaşımlar gelişti.

Olayları tetikleyip göndermek için ertelenen yaklaşım

Etki alanı olay işleyicisine hemen göndermek yerine, etki alanı olaylarını bir koleksiyona eklemek ve ardından bu etki alanı olaylarını işlemi işledikten hemen önce veya sonra göndermek daha iyi bir yaklaşımdır (EF'te SaveChanges'te olduğu gibi). (Bu yaklaşım Jimmy Bogard tarafından bu gönderide açıklanmıştırDaha iyi bir etki alanı olayları düzeni.)

Etki alanı olaylarını işlemi işlemeden hemen önce mi yoksa hemen sonra mı gönderdiğinize karar vermek önemlidir, çünkü yan etkileri aynı işlemin bir parçası olarak mı yoksa farklı işlemlere mi dahil edeceğinizi belirler. İkinci durumda, birden çok toplamada nihai tutarlılık ile ilgilenmeniz gerekir. Bu konu bir sonraki bölümde ele alınmıştır.

Ertelenmiş yaklaşım, eShop'un kullandığı yaklaşımdır. İlk olarak varlıklarınızda gerçekleşen olayları varlık başına olay koleksiyonuna veya listesine eklersiniz. Bu liste, Varlık temel sınıfının aşağıdaki örneğinde gösterildiği gibi varlık nesnesinin bir parçası, hatta daha da iyisi temel varlık sınıfınızın bir parçası olmalıdır:

public abstract class Entity
{
     //...
     private List<INotification> _domainEvents;
     public List<INotification> DomainEvents => _domainEvents;

     public void AddDomainEvent(INotification eventItem)
     {
         _domainEvents = _domainEvents ?? new List<INotification>();
         _domainEvents.Add(eventItem);
     }

     public void RemoveDomainEvent(INotification eventItem)
     {
         _domainEvents?.Remove(eventItem);
     }
     //... Additional code
}

Bir olay tetiklemesini istediğinizde, bunu toplama kök varlığının herhangi bir yöntemindeki koddan olay koleksiyonuna eklemeniz gerekir.

eShop'taki Order toplama kökünün bir parçası olan aşağıdaki kod bir örnek gösterir:

var orderStartedDomainEvent = new OrderStartedDomainEvent(this, //Order object
                                                          cardTypeId, cardNumber,
                                                          cardSecurityNumber,
                                                          cardHolderName,
                                                          cardExpiration);
this.AddDomainEvent(orderStartedDomainEvent);

AddDomainEvent yönteminin yaptığı tek şeyin listeye bir olay eklemek olduğuna dikkat edin. Henüz hiçbir olay gönderilmedi ve henüz hiçbir olay işleyicisi çağrılmadı.

Aslında, işlemi veritabanına işlerken olayları daha sonra göndermek istiyorsunuz. Entity Framework Core kullanıyorsanız, bu, aşağıdaki kodda olduğu gibi EF DbContext'inizin SaveChanges yönteminde olduğu anlamına gelir:

// EF Core DbContext
public class OrderingContext : DbContext, IUnitOfWork
{
    // ...
    public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        // Dispatch Domain Events collection.
        // Choices:
        // A) Right BEFORE committing data (EF SaveChanges) into the DB. This makes
        // a single transaction including side effects from the domain event
        // handlers that are using the same DbContext with Scope lifetime
        // B) Right AFTER committing data (EF SaveChanges) into the DB. This makes
        // multiple transactions. You will need to handle eventual consistency and
        // compensatory actions in case of failures.
        await _mediator.DispatchDomainEventsAsync(this);

        // After this line runs, all the changes (from the Command Handler and Domain
        // event handlers) performed through the DbContext will be committed
        var result = await base.SaveChangesAsync();
    }
}

Bu kodla varlık olaylarını ilgili olay işleyicilerine dağıtırsınız.

Genel sonuç, bir etki alanı olayının (bellekteki bir listeye basit bir ekleme) oluşturulmasını olay işleyicisine göndermekten ayırmanızdır. Ayrıca, ne tür bir dağıtıcı kullandığınıza bağlı olarak, olayları zaman uyumlu veya zaman uyumsuz olarak gönderebilirsiniz.

İşlem sınırlarının burada önemli bir rol oynadığını unutmayın. İş ve işlem biriminiz birden fazla toplamaya yayılabiliyorsa (EF Core ve ilişkisel veritabanı kullanılırken olduğu gibi), bu iyi sonuç verebilir. Ancak işlem toplamaya yayılamıyorsa tutarlılık elde etmek için ek adımlar uygulamanız gerekir. Bu, kalıcılık cehaletlerinin evrensel olmamasının bir diğer nedenidir; kullandığınız depolama sistemine bağlıdır.

Toplamalar arasında tek işlem ve toplamlar arasında nihai tutarlılık

Toplamalar arasında tek bir işlem gerçekleştirip gerçekleştirmemek ve bu toplamalar arasında nihai tutarlılığa güvenmek tartışmalı bir sorudur. Eric Evans ve Vaughn Vernon gibi birçok DDD yazarı, tek işlem = bir toplama kuralını savunur ve bu nedenle toplamlar arasında nihai tutarlılık iddia eder. Örneğin, Domain-Driven Design adlı kitabında Eric Evans şunu söylüyor:

Toplamları kapsayan hiçbir kuralın her zaman güncel olması beklenmez. Olay işleme, toplu işleme veya diğer güncelleştirme mekanizmaları aracılığıyla, diğer bağımlılıklar belirli bir süre içinde çözülebilir. (sayfa 128)

Vaughn Vernon, Etkili Toplama Tasarımı'nda aşağıdakileri söyler. Bölüm II: Toplamaların Birlikte Çalışmasını Sağlama:

Bu nedenle, bir toplama örneğinde komut yürütmek için bir veya daha fazla toplamada ek iş kurallarının yürütülmesi gerekiyorsa nihai tutarlılığı kullanın [...] DDD modelinde nihai tutarlılığı desteklemenin pratik bir yolu vardır. Toplama yöntemi, bir veya daha fazla zaman uyumsuz aboneye zamanında teslim edilen bir etki alanı olayı yayımlar.

Bu gerekçe, birçok toplamayı veya varlığı kapsayan işlemler yerine ayrıntılı işlemlerin benimsilmesine dayanır. İkinci durumda veritabanı kilitlerinin sayısının yüksek ölçeklenebilirlik gereksinimleri olan büyük ölçekli uygulamalarda önemli ölçüde olacağı fikridir. Yüksek oranda ölçeklenebilir uygulamaların birden çok toplama arasında anlık işlem tutarlılığı gerekmediği gerçeğini benimsemek, nihai tutarlılık kavramını kabul etmenize yardımcı olur. Atomik değişiklikler genellikle işletme tarafından gerekli değildir ve her durumda belirli işlemlerin atomik işlemlere gerek olup olmadığını söylemek etki alanı uzmanlarının sorumluluğundadır. Bir işlemin her zaman birden çok toplama arasında atomik bir işleme ihtiyacı varsa, toplamanızın daha büyük olması mı yoksa doğru tasarlanmaması mı gerektiğini sorabilirsiniz.

Ancak, Jimmy Bogard gibi diğer geliştiriciler ve mimarlar, yalnızca bu ek toplamalar aynı özgün komutun yan etkileriyle ilgili olduğunda, birkaç toplamada tek bir işlem yayma konusunda sorun olmaz. Örneğin, Daha iyi bir etki alanı olayları düzeninde Bogard şunu söyler:

Genellikle, etki alanı olayının yan etkilerinin aynı mantıksal işlem içinde gerçekleşmesini istiyorum, ancak aynı etki alanı olayını oluşturma kapsamında [...] İşlemimizi gerçekleştirmeden hemen önce olaylarımızı ilgili işleyicilerine gönderiyoruz.

Etki alanı olaylarını özgün işlemi işlemeden hemen önce gönderirseniz, bunun nedeni bu olayların yan etkilerinin aynı işleme dahil olmasını istemenizdir. Örneğin, EF DbContext SaveChanges yöntemi başarısız olursa, işlem ilgili etki alanı olay işleyicileri tarafından uygulanan yan etki işlemlerinin sonucu da dahil olmak üzere tüm değişiklikleri geri alır. Bunun nedeni DbContext yaşam kapsamının varsayılan olarak "kapsamlı" olarak tanımlanmasıdır. Bu nedenle, DbContext nesnesi aynı kapsam veya nesne grafı içinde örneklenen birden çok depo nesnesi arasında paylaşılır. Bu, Web API'sini veya MVC uygulamalarını geliştirirken HttpRequest kapsamıyla çakışır.

Aslında her iki yaklaşım da (tek atomik işlem ve nihai tutarlılık) doğru olabilir. Bu gerçekten etki alanınıza veya iş gereksinimlerinize ve etki alanı uzmanlarının size söylediklerine bağlıdır. Ayrıca hizmetin ne kadar ölçeklenebilir olması gerektiği de bağlıdır (veritabanı kilitleri açısından daha ayrıntılı işlemlerin daha az etkisi vardır). Nihai tutarlılık, toplamalar arasında olası tutarsızlıkları ve telafi eylemleri uygulama gereksinimini algılamak için daha karmaşık bir kod gerektirdiğinden, kodunuzda ne kadar yatırım yapmak istediğinize bağlıdır. Değişiklikleri özgün toplamada işlerseniz ve daha sonra olaylar gönderilirken bir sorun varsa ve olay işleyicileri yan etkilerini işleyemiyorsa, toplamalar arasında tutarsızlıklar olur.

Telafi eylemlerine izin vermenin bir yolu, etki alanı olaylarını özgün işlemin parçası olabilmeleri için ek veritabanı tablolarında depolamaktır. Daha sonra, olayların listesini toplamaların geçerli durumuyla karşılaştırarak tutarsızlıkları algılayan ve telafi eylemleri çalıştıran bir toplu işlem gerçekleştirebilirsiniz. Telafi eylemleri, iş kullanıcısı ve etki alanı uzmanlarıyla tartışmak da dahil olmak üzere sizin tarafınızdan derin analiz gerektiren karmaşık bir konunun parçasıdır.

Her durumda, ihtiyacınız olan yaklaşımı seçebilirsiniz. Ancak ilk ertelenen yaklaşım (işlemeden önce olayları yükseltmek, böylece tek bir işlem kullanmak), EF Core ve ilişkisel veritabanı kullanırken en basit yaklaşımdır. Birçok iş örneğinde uygulanması ve geçerli olması daha kolaydır. Aynı zamanda eShop'ta mikro hizmet sipariş etmede kullanılan yaklaşımdır.

Peki bu olayları ilgili olay işleyicilerine nasıl gönderebilirsiniz? _mediator Önceki örnekte gördüğünüz nesne nedir? Olaylar ve olay işleyicileri arasında eşlemek için kullandığınız teknikler ve yapıtlarla ilgili olmalıdır.

Etki alanı olay dağıtıcısı: olaylardan olay işleyicilerine eşleme

Olayları gönderebildiğiniz veya yayımlayabildiğiniz zaman, ilgili her işleyicinin olayı alabilmesi ve bu olaya dayalı yan etkileri işleyebilmesi için olayı yayımlayacak bir tür yapıt gerekir.

Bir yaklaşım gerçek bir mesajlaşma sistemi, hatta muhtemelen bellek içi olaylar yerine bir hizmet veri yolunu temel alan bir olay veri yoludur. Ancak ilk durumda, gerçek mesajlaşma etki alanı olaylarını işlemek için aşırıya kaçacak, çünkü bu olayları aynı işlem içinde (aynı etki alanı ve uygulama katmanı içinde) işlemeniz yeterli olacaktır.

Etki alanı olaylarına abone olma

MediatR kullandığınızda, her olay işleyicisi aşağıdaki kodda görebileceğiniz gibi arabirimin INotificationHandler genel parametresinde sağlanan bir olay türünü kullanmalıdır:

public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler
  : INotificationHandler<OrderStartedDomainEvent>

MediatR yapıtı, abonelik olarak kabul edilebilen olay ve olay işleyicisi arasındaki ilişkiye bağlı olarak her olay için tüm olay işleyicilerini bulabilir ve bu olay işleyicilerinin her birini tetikleyebilir.

Etki alanı olaylarını işleme

Son olarak, olay işleyicisi genellikle gerekli ek toplamaları almak ve yan etki alanı mantığını yürütmek için altyapı depolarını kullanan uygulama katmanı kodunu uygular. eShop'taki aşağıdaki etki alanı olay işleyici kodu bir uygulama örneği gösterir.

public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler
    : INotificationHandler<OrderStartedDomainEvent>
{
    private readonly ILogger _logger;
    private readonly IBuyerRepository _buyerRepository;
    private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;

    public ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler(
        ILogger<ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler> logger,
        IBuyerRepository buyerRepository,
        IOrderingIntegrationEventService orderingIntegrationEventService)
    {
        _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository));
        _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task Handle(
        OrderStartedDomainEvent domainEvent, CancellationToken cancellationToken)
    {
        var cardTypeId = domainEvent.CardTypeId != 0 ? domainEvent.CardTypeId : 1;
        var buyer = await _buyerRepository.FindAsync(domainEvent.UserId);
        var buyerExisted = buyer is not null;

        if (!buyerExisted)
        {
            buyer = new Buyer(domainEvent.UserId, domainEvent.UserName);
        }

        buyer.VerifyOrAddPaymentMethod(
            cardTypeId,
            $"Payment Method on {DateTime.UtcNow}",
            domainEvent.CardNumber,
            domainEvent.CardSecurityNumber,
            domainEvent.CardHolderName,
            domainEvent.CardExpiration,
            domainEvent.Order.Id);

        var buyerUpdated = buyerExisted ?
            _buyerRepository.Update(buyer) :
            _buyerRepository.Add(buyer);

        await _buyerRepository.UnitOfWork
            .SaveEntitiesAsync(cancellationToken);

        var integrationEvent = new OrderStatusChangedToSubmittedIntegrationEvent(
            domainEvent.Order.Id, domainEvent.Order.OrderStatus.Name, buyer.Name);
        await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent);

        OrderingApiTrace.LogOrderBuyerAndPaymentValidatedOrUpdated(
            _logger, buyerUpdated.Id, domainEvent.Order.Id);
    }
}

Önceki etki alanı olay işleyici kodu, altyapı kalıcılık katmanının sonraki bölümünde açıklandığı gibi altyapı depolarını kullandığından uygulama katmanı kodu olarak kabul edilir. Olay işleyicileri diğer altyapı bileşenlerini de kullanabilir.

Etki alanı olayları, mikro hizmet sınırlarının dışında yayımlanacak tümleştirme olayları oluşturabilir

Son olarak, bazen olayları birden çok mikro hizmete yaymak isteyebileceğinizi belirtmek önemlidir. Bu yayma bir tümleştirme olayıdır ve herhangi bir etki alanı olay işleyicisinden bir olay veri yolu aracılığıyla yayımlanabilir.

Etki alanı olaylarıyla ilgili sonuçlar

Belirtildiği gibi, etki alanınızdaki değişikliklerin yan etkilerini açıkça uygulamak için etki alanı olaylarını kullanın. DDD terminolojisini kullanmak için etki alanı olaylarını kullanarak bir veya birden çok toplamada yan etkileri açıkça uygulayın. Ayrıca, daha iyi ölçeklenebilirlik ve veritabanı kilitleri üzerinde daha az etki sağlamak için aynı etki alanındaki toplamalar arasında nihai tutarlılığı kullanın.

Başvuru uygulaması, etki alanı olaylarını tek bir işlem içinde toplamalar arasında zaman uyumlu olarak yaymak için MediatR kullanır. Ancak, son tutarlılığı kullanarak etki alanı olaylarını zaman uyumsuz olarak yaymak için RabbitMQ veya Azure Service Bus gibi bazı AMQP uygulamalarını da kullanabilirsiniz, ancak yukarıda belirtildiği gibi, hata durumunda telafi eylemleri gereksinimini göz önünde bulundurmanız gerekir.

Ek kaynaklar