Altyapı kalıcılık katmanını tasarlama

İ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.

Veri kalıcılığı bileşenleri, bir mikro hizmetin (yani bir mikro hizmetin veritabanı) sınırları içinde barındırılan verilere erişim sağlar. Bunlar, özel Entity Framework (EF) DbContext nesneleri gibi depolar ve İş Birimi sınıfları gibi bileşenlerin gerçek uygulamasını içerir. EF DbContext hem Depo hem de İş Birimi desenlerini uygular.

Depo düzeni

Depo düzeni, kalıcılık endişelerini sistemin etki alanı modelinin dışında tutmaya yönelik bir Etki Alanı Odaklı Tasarım desenidir. Bir veya daha fazla kalıcılık soyutlama (arabirimler) etki alanı modelinde tanımlanır ve bu soyutlamalar, uygulamanın başka bir yerinde tanımlanan kalıcılığa özgü bağdaştırıcılar biçiminde uygulamalar içerir.

Depo uygulamaları, veri kaynaklarına erişmek için gereken mantığı kapsülleyen sınıflardır. Ortak veri erişim işlevselliğini merkezileştirerek daha iyi bakım sağlar ve etki alanı modelinden veritabanlarına erişmek için kullanılan altyapıyı veya teknolojiyi ayırır. Entity Framework gibi bir Nesne İlişkisel Eşleyici (ORM) kullanıyorsanız LINQ ve güçlü yazma özelliği sayesinde uygulanması gereken kod basitleştirilmiştir. Bu, veri erişimi tesisatı yerine veri kalıcılığı mantığına odaklanmanızı sağlar.

Depo düzeni, veri kaynağıyla çalışmanın iyi belgelenmiş bir yoludur. Martin Fowler, Kurumsal Uygulama Mimarisi Desenleri kitabında bir depoyu şu şekilde açıklar:

Depo, etki alanı modeli katmanları ve veri eşlemesi arasındaki bir aracının görevlerini gerçekleştirir ve bellekteki etki alanı nesneleri kümesine benzer şekilde çalışır. İstemci nesneleri bildirimli olarak sorgular oluşturur ve yanıtları için depolara gönderir. Kavramsal olarak, depo veritabanında depolanan bir nesne kümesini ve bunlar üzerinde gerçekleştirilebilecek işlemleri kapsülleyerek kalıcılık katmanına daha yakın bir yol sağlar. Depolar ayrıca, iş etki alanı ile veri ayırma veya eşleme arasındaki bağımlılığı açıkça ve tek yönde ayırma amacını da destekler.

Toplama başına bir depo tanımlama

Her toplama veya toplama kökü için bir depo sınıfı oluşturmanız gerekir. Korumanız gereken toplam somut sınıf sayısını azaltmak için C# Genel Öğeleri'ni kullanabilirsiniz (bu bölümün ilerleyen bölümlerinde gösterildiği gibi). Etki Alanı Odaklı Tasarım (DDD) desenlerini temel alan bir mikro hizmette, veritabanını güncelleştirmek için kullanmanız gereken tek kanal depolar olmalıdır. Bunun nedeni, toplama kökünü içeren ve toplamanın sabitlerini ve işlem tutarlılığını denetleyen bire bir ilişkisi olmasıdır. Sorgular veritabanının durumunu değiştirmediğinden, veritabanını diğer kanallar aracılığıyla sorgulamak sorun olmaz (CQRS yaklaşımını izleyerek yapabileceğiniz gibi). Ancak işlem alanı (yani güncelleştirmeler) her zaman depolar ve toplama kökleri tarafından denetlenmelidir.

Temel olarak, bir depo, veritabanından gelen verileri etki alanı varlıkları biçiminde doldurmanıza olanak tanır. Varlıklar belleğe eklendikten sonra değiştirilebilir ve işlemler aracılığıyla veritabanına geri kalıcı hale getirilebilir.

Daha önce belirtildiği gibi, CQS/CQRS mimari desenini kullanıyorsanız, ilk sorgular etki alanı modeli dışında yan yana gerçekleştirilir ve Dapper kullanılarak basit SQL deyimleri tarafından gerçekleştirilir. Bu yaklaşım depolardan çok daha esnektir çünkü ihtiyacınız olan tabloları sorgulayabilir ve birleştirebilirsiniz ve bu sorgular toplamalardaki kurallarla kısıtlanmaz. Bu veriler sunu katmanına veya istemci uygulamasına gider.

Kullanıcı değişiklik yaparsa, güncelleştirilecek veriler istemci uygulamasından veya sunu katmanından uygulama katmanına (Web API hizmeti gibi) gelir. Komut işleyicisinde bir komut aldığınızda, veritabanından güncelleştirmek istediğiniz verileri almak için depoları kullanırsınız. Komutlarla geçirilen verilerle bellekte güncelleştirirsiniz ve ardından veritabanındaki verileri (etki alanı varlıkları) bir işlem aracılığıyla ekler veya güncelleştirirsiniz.

Şekil 7-17'de gösterildiği gibi her toplama kökü için yalnızca bir depo tanımlamanız gerektiğini tekrar vurgulamalısınız. Toplama kökünün, toplama içindeki tüm nesneler arasında işlem tutarlılığını koruma hedefine ulaşmak için, veritabanındaki her tablo için hiçbir zaman bir depo oluşturmamalısınız.

Diagram showing relationships of domain and other infrastructure.

Şekil 7-17. Depolar, toplamalar ve veritabanı tabloları arasındaki ilişki

Yukarıdaki diyagramda Etki Alanı ve Altyapı katmanları arasındaki ilişkiler gösterilmektedir: Alıcı Toplaması IBuyerRepository'e, Sipariş Toplama ise IOrderRepository arabirimlerine bağlıdır; bu arabirimler Altyapı katmanında, burada da uygulanan ve Veri katmanındaki tablolara erişen UnitOfWork'e bağlı olan ilgili depolar tarafından uygulanır.

Depo başına bir toplama kökünü zorunlu kılma

Depo tasarımınızı, yalnızca toplama köklerinin depolara sahip olması gereken kuralı zorunlu kılacak şekilde uygulamak değerli olabilir. İşaretçi arabirimine sahip IAggregateRoot olduklarından emin olmak için birlikte çalıştığı varlıkların türünü kısıtlayan genel veya temel bir depo türü oluşturabilirsiniz.

Bu nedenle, altyapı katmanında uygulanan her depo sınıfı, aşağıdaki kodda gösterildiği gibi kendi sözleşmesini veya arabirimini uygular:

namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{
    public class OrderRepository : IOrderRepository
    {
      // ...
    }
}

Her bir depo arabirimi genel IRepository arabirimini uygular:

public interface IOrderRepository : IRepository<Order>
{
    Order Add(Order order);
    // ...
}

Ancak, kodun her deponun tek bir toplamayla ilgili olduğu kuralını zorlamasının daha iyi bir yolu genel bir depo türü uygulamaktır. Bu şekilde, belirli bir toplamayı hedeflemek için bir depo kullandığınız açıkça belirtilir. Bu, aşağıdaki kodda olduğu gibi genel IRepository bir temel arabirim uygulanarak kolayca yapılabilir:

public interface IRepository<T> where T : IAggregateRoot
{
    //....
}

Depo düzeni, uygulama mantığınızı test etmenizi kolaylaştırır

Depo düzeni, uygulamanızı birim testleriyle kolayca test etmenizi sağlar. Birim testlerinin altyapıyı değil yalnızca kodunuzu test ettiğini, dolayısıyla depo soyutlamalarının bu hedefe ulaşmayı kolaylaştırdığını unutmayın.

Önceki bölümlerde belirtildiği gibi, depo arabirimlerini tanımlamanız ve etki alanı modeli katmanına yerleştirmeniz önerilir; böylece Web API mikro hizmetiniz gibi uygulama katmanı, gerçek depo sınıflarını uyguladığınız altyapı katmanına doğrudan bağımlı olmaz. Bunu yaparak ve Web API'nizin denetleyicilerinde Bağımlılık Ekleme'yi kullanarak, veritabanındaki veriler yerine sahte veri döndüren sahte depolar uygulayabilirsiniz. Bu ayrılmış yaklaşım, veritabanı bağlantısına gerek kalmadan uygulamanızın mantığına odaklanan birim testleri oluşturmanıza ve çalıştırmanıza olanak tanır.

Veritabanlarına yönelik Bağlan başarısız olabilir ve daha da önemlisi, bir veritabanında yüzlerce test çalıştırmak iki nedenden dolayı kötüdür. İlk olarak, çok sayıda test nedeniyle uzun sürebilir. İkincisi, veritabanı kayıtları, özellikle de testleriniz paralel çalışıyorsa, tutarlı olmaması için testlerinizin sonuçlarını değiştirebilir ve etkileyebilir. Birim testleri genellikle paralel olarak çalıştırılabilir; tümleştirme testleri, uygulamalarına bağlı olarak paralel yürütmeyi desteklemeyebilir. Veritabanında test etme bir birim testi değil, tümleştirme testidir. Çok sayıda birim testinin hızlı çalışması gerekir, ancak veritabanlarında daha az tümleştirme testi gerekir.

Birim testleriyle ilgili endişelerin ayrılması açısından mantığınız bellekteki etki alanı varlıkları üzerinde çalışır. Depo sınıfının bunları teslimdiğini varsayar. Mantığınız etki alanı varlıklarını değiştirdikten sonra depo sınıfının bunları doğru şekilde depolayacağını varsayar. Burada önemli nokta, etki alanı modelinize ve etki alanı mantığına göre birim testleri oluşturmaktır. Toplam kökler, DDD'deki ana tutarlılık sınırlarıdır.

eShopOnContainers'da uygulanan depolar, değişiklik izleyicisini kullanarak EF Core'un Repository and Unit of Work desenlerinin DbContext uygulamasını kullanır, böylece bu işlevselliği yinelemez.

Depo deseni ile eski Veri Erişimi sınıfı (DAL sınıfı) deseni arasındaki fark

Tipik bir DAL nesnesi, genellikle tek bir tablo ve satır düzeyinde depolamaya karşı doğrudan veri erişimi ve kalıcılık işlemleri gerçekleştirir. Bir DAL sınıfları kümesiyle uygulanan basit CRUD işlemleri genellikle işlemleri desteklemez (ancak her zaman böyle değildir). DAL sınıfı yaklaşımlarının çoğu soyutlamalardan en az düzeyde yararlanır ve bu da DAL nesnelerini çağıran uygulama veya İş Mantığı Katmanı (BLL) sınıfları arasında sıkı bir birleşime neden olur.

Depo kullanılırken, kalıcılığın uygulama ayrıntıları etki alanı modelinden uzakta kapsüllenir. Soyutlama kullanımı, Dekoratörler veya Proxy'ler gibi desenler aracılığıyla davranışı genişletme kolaylığı sağlar. Örneğin, önbelleğe alma, günlüğe kaydetme ve hata işleme gibi çapraz kesme sorunlarının tümü, veri erişim kodunda sabit kodlanmış yerine bu desenler kullanılarak uygulanabilir. Yerel geliştirmeden paylaşılan hazırlama ortamlarına ve üretim ortamına kadar farklı ortamlarda kullanılabilecek birden çok depo bağdaştırıcısını desteklemek de önemsizdir.

çalışma birimini uygulama

Çalışma birimi, birden çok ekleme, güncelleştirme veya silme işlemi içeren tek bir işleme başvurur. Basit bir deyişle, bir web sitesindeki kayıt gibi belirli bir kullanıcı eylemi için tüm ekleme, güncelleştirme ve silme işlemlerinin tek bir işlemde işlenmesi anlamına gelir. Bu, birden çok veritabanı işlemlerini daha sohbetçi bir şekilde işlemekten daha verimlidir.

Bu birden çok kalıcılık işlemi daha sonra uygulama katmanındaki kodunuz bunu komutladığında tek bir eylemde gerçekleştirilir. Bellek içi değişiklikleri gerçek veritabanı depolama alanına uygulama kararı genellikle İş Birimi düzenine bağlıdır. EF'te, çalışma birimi düzeni tarafından DbContext uygulanır ve çağrısı yapıldığında SaveChangesyürütülür.

Çoğu durumda, bu desen veya depolamaya işlem uygulama yöntemi, uygulama performansını artırabilir ve tutarsızlık olasılığını azaltabilir. Hedeflenen tüm işlemler tek bir işlemin parçası olarak işlendiği için veritabanı tablolarında işlem engellemeyi de azaltır. Bu, veritabanında birçok yalıtılmış işlemi yürütmeye kıyasla daha verimlidir. Bu nedenle, seçilen ORM, birçok küçük ve ayrı işlem yürütmesinin aksine, aynı işlem içindeki birkaç güncelleştirme eylemini gruplandırarak yürütmeyi veritabanına göre iyileştirebilir.

İş Birimi düzeni, Depo deseni kullanılarak veya kullanılmadan uygulanabilir.

Depolar zorunlu olmamalıdır

Özel depolar, daha önce belirtilen nedenlerle yararlıdır ve bu, eShopOnContainers'da mikro hizmet sipariş etme yaklaşımıdır. Ancak, bir DDD tasarımında ve hatta genel .NET geliştirmesinde uygulamak temel bir desen değildir.

Örneğin, Jimmy Bogard bu kılavuz için doğrudan geri bildirim sağlarken şunları söyledi:

Bu muhtemelen en büyük geri bildirimim olacak. Depoları pek sevmem çünkü temeldeki kalıcılık mekanizmasının önemli ayrıntılarını gizlerler. Bu yüzden ben de komutlar için MediatR'ye gidiyorum. Kalıcılık katmanının tüm gücünü kullanabilir ve tüm bu etki alanı davranışlarını toplama köklerime gönderebiliyorum. Depolarım ile alay etmek istemem. Yine de gerçek olanla tümleştirme testi yapmak istiyorum. CQRS'ye gitmek artık depolara ihtiyacımız olmadığı anlamına geliyordu.

Depolar yararlı olabilir, ancak DDD tasarımınız için Toplama deseni ve zengin etki alanı modeli gibi kritik değildir. Bu nedenle, uygun gördüğünüz şekilde Depo desenini kullanın veya kullanmayın.

Ek kaynaklar

Depo düzeni

çalışma birimi düzeni