Aracılığıyla paylaş


CQRS mikro hizmetinde okuma/sorgu 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.

Okumalar/sorgular için, eShopOnContainers başvuru uygulamasından mikro hizmet sıralama işlemi sorguları DDD modelinden ve işlem alanından bağımsız olarak uygular. Bu uygulama öncelikli olarak sorgular ve işlemlere yönelik talepler büyük ölçüde farklı olduğundan yapıldı. Yazma işlemleri, etki alanı mantığıyla uyumlu olması gereken işlemleri yürütür. Öte yandan sorgular bir kez etkili olur ve etki alanı kurallarından ayrıştırılabilir.

Şekil 7-3'te gösterildiği gibi yaklaşım basittir. API arabirimi, Web API denetleyicileri tarafından Dapper gibi bir mikro Nesne İlişkisel Eşleyicisi (ORM) gibi herhangi bir altyapı kullanılarak ve kullanıcı arabirimi uygulamalarının gereksinimlerine bağlı olarak dinamik ViewModel'ler döndürerek uygulanır.

Diagram showing high-level queries-side in simplified CQRS.

Şekil 7-3. CQRS mikro hizmetinde sorgular için en basit yaklaşım

Basitleştirilmiş bir CQRS yaklaşımında sorgu tarafı için en basit yaklaşım, veritabanını Dapper gibi bir Micro-ORM ile sorgulayarak ve dinamik ViewModel'ler döndürerek uygulanabilir. Sorgu tanımları veritabanını sorgular ve her sorgu için anında oluşturulan dinamik bir ViewModel döndürür. Sorgular bir kez etkili olduğundan, sorguyu kaç kez çalıştırdığınızdan bağımsız olarak verileri değiştirmezler. Bu nedenle, toplamalar ve diğer desenler gibi işlem tarafında kullanılan herhangi bir DDD deseni ile kısıtlanmanız gerekmez ve bu nedenle sorgular işlem alanından ayrılır. Kullanıcı arabiriminin ihtiyaç duyduğu veriler için veritabanını sorgular ve SQL deyimleri dışında herhangi bir yerde (ViewModel'ler için sınıf olmadan) statik olarak tanımlanması gerekmeyen dinamik bir ViewModel döndürürsiniz.

Bu yaklaşım basit olduğundan, sorgular tarafı için gereken kod (Dapper gibi bir mikro ORM kullanan kod gibi) aynı Web API projesi içinde uygulanabilir. Şekil 7-4'de bu yaklaşım gösterilmektedir. Sorgular, eShopOnContainers çözümündeki Ordering.API mikro hizmet projesinde tanımlanır.

Screenshot of the Ordering.API project's Queries folder.

Şekil 7-4. eShopOnContainers'da Sipariş mikro hizmetindeki sorgular

Etki alanı modeli kısıtlamalarından bağımsız olarak istemci uygulamaları için özel olarak yapılmış ViewModel'leri kullanma

sorgular, istemci uygulamaları tarafından gereken verileri almak için gerçekleştirildiğinden, döndürülen tür sorgular tarafından döndürülen verilere göre istemciler için özel olarak oluşturulabilir. Bu modeller veya Veri Aktarım Nesneleri (DTO'lar), ViewModels olarak adlandırılır.

Döndürülen veriler (ViewModel), veritabanındaki birden çok varlık veya tablodan, hatta işlem alanı için etki alanı modelinde tanımlanan birden çok toplamadan veri birleştirmenin sonucu olabilir. Bu durumda, etki alanı modelinden bağımsız sorgular oluşturduğunuz için, toplama sınırları ve kısıtlamaları yoksayılır ve ihtiyacınız olabilecek herhangi bir tablo ve sütunu sorgulayabilirsiniz. Bu yaklaşım, sorguları oluşturan veya güncelleştiren geliştiriciler için büyük esneklik ve üretkenlik sağlar.

ViewModel'ler sınıflarda tanımlanan statik türler olabilir (sipariş mikro hizmette uygulandığı gibi). Ya da geliştiriciler için çevik olan gerçekleştirilen sorgular temelinde dinamik olarak oluşturulabilirler.

Sorgular gerçekleştirmek için Dapper'ı mikro ORM olarak kullanma

Sorgulama için herhangi bir mikro ORM, Entity Framework Core veya düz ADO.NET kullanabilirsiniz. Örnek uygulamada, popüler bir mikro ORM örneği olarak eShopOnContainers'da mikro hizmet siparişi için Dapper seçildi. Basit bir çerçeve olduğundan, düz SQL sorgularını harika bir performansla çalıştırabilir. Dapper'ı kullanarak birden çok tabloya erişebilen ve bunları birleştirebilen bir SQL sorgusu yazabilirsiniz.

Dapper, açık kaynak bir projedir (orijinali Sam Saffron tarafından oluşturulmuştur) ve Stack Overflow'da kullanılan yapı taşlarının bir parçasıdır. Dapper'ı kullanmak için, aşağıdaki şekilde gösterildiği gibi Dapper NuGet paketi aracılığıyla yüklemeniz yeterlidir:

Screenshot of the Dapper package in the NuGet packages view.

Kodunuzun Dapper uzantısı yöntemlerine erişebilmesi için bir using yönerge de eklemeniz gerekir.

Kodunuzda Dapper kullandığınızda, doğrudan ad alanında Microsoft.Data.SqlClient kullanılabilir sınıfını kullanırsınızSqlConnection. QueryAsync yöntemi ve sınıfını genişleten SqlConnection diğer uzantı yöntemleri aracılığıyla sorguları basit ve performanslı bir şekilde çalıştırabilirsiniz.

Dinamik ve statik ViewModel'ler

ViewModel'leri sunucu tarafındaki istemci uygulamalarına döndürürken, ViewModel'ler verileri istemci uygulamasının ihtiyaç duyduğu şekilde barındırdığından, bu ViewModel'leri varlık modelinizin iç etki alanı varlıklarından farklı olabilecek DTO'lar (Veri Aktarım Nesneleri) olarak düşünebilirsiniz. Bu nedenle çoğu durumda, birden çok etki alanı varlığından gelen verileri toplayabilir ve ViewModel'leri istemci uygulamasının bu verilere nasıl ihtiyaç duyduğuna göre tam olarak oluşturabilirsiniz.

Bu ViewModel'ler veya DTO'lar, daha sonraki kod parçacığında gösterilen sınıf gibi OrderSummary açıkça (veri sahibi sınıfları olarak) tanımlanabilir. Alternatif olarak, sorgularınız tarafından dinamik tür olarak döndürülen özniteliklere göre dinamik ViewModel veya dinamik DTO'lar döndürebilirsiniz.

Dinamik tür olarak ViewModel

Aşağıdaki kodda gösterildiği gibi, ViewModel yalnızca bir sorgu tarafından döndürülen öznitelikleri temel alan dinamik bir tür döndürülerek doğrudan sorgular tarafından döndürülebilir. Bu, döndürülecek özniteliklerin alt kümesinin sorgunun kendisini temel alarak olduğu anlamına gelir. Bu nedenle, sorguya veya birleştirmeye yeni bir sütun eklerseniz, bu veriler döndürülen ViewModelöğesine dinamik olarak eklenir.

using Dapper;
using Microsoft.Extensions.Configuration;
using System.Data.SqlClient;
using System.Threading.Tasks;
using System.Dynamic;
using System.Collections.Generic;

public class OrderQueries : IOrderQueries
{
    public async Task<IEnumerable<dynamic>> GetOrdersAsync()
    {
        using (var connection = new SqlConnection(_connectionString))
        {
            connection.Open();
            return await connection.QueryAsync<dynamic>(
                @"SELECT o.[Id] as ordernumber,
                o.[OrderDate] as [date],os.[Name] as [status],
                SUM(oi.units*oi.unitprice) as total
                FROM [ordering].[Orders] o
                LEFT JOIN[ordering].[orderitems] oi ON o.Id = oi.orderid
                LEFT JOIN[ordering].[orderstatus] os on o.OrderStatusId = os.Id
                GROUP BY o.[Id], o.[OrderDate], os.[Name]");
        }
    }
}

Önemli nokta, dinamik bir tür kullanılarak döndürülen veri koleksiyonunun ViewModel olarak dinamik olarak birleştirilmesidir.

Artılar: Bu yaklaşım, bir sorgunun SQL cümlesini her güncelleştirdiğinizde statik ViewModel sınıflarını değiştirme gereksinimini azaltır ve bu tasarım yaklaşımını kodlarken çevik hale getirir, gelecekteki değişikliklerle ilgili olarak basit ve hızlı bir şekilde gelişir.

Dezavantajları: Uzun vadede dinamik türler, hizmetin netliğini ve istemci uygulamalarıyla uyumluluğunu olumsuz etkileyebilir. Ayrıca, Swashbuckle gibi ara yazılım yazılımları, dinamik türler kullanılıyorsa döndürülen türler hakkında aynı düzeyde belge sağlayamaz.

Önceden tanımlanmış DTO sınıfları olarak ViewModel

Artılar: Açık DTO sınıflarını temel alan "sözleşmeler" gibi statik, önceden tanımlanmış ViewModel sınıflarına sahip olmak, yalnızca aynı uygulama tarafından kullanılıyor olsalar bile genel API'ler için değil, uzun vadeli mikro hizmetler için de kesinlikle daha iyidir.

Swagger için yanıt türlerini belirtmek istiyorsanız, dönüş türü olarak açık DTO sınıflarını kullanmanız gerekir. Bu nedenle, önceden tanımlanmış DTO sınıfları Swagger'dan daha zengin bilgiler sunmanızı sağlar. Bu, API'yi kullanırken API belgelerini ve uyumluluğunu geliştirir.

Dezavantajları: Daha önce de belirtildiği gibi, kodu güncelleştirirken DTO sınıflarını güncelleştirmek için bazı daha fazla adım gerekir.

Deneyimimize dayanarak ipucu: eShopOnContainers'da Sipariş mikro hizmetinde uygulanan sorgularda, geliştirme aşamalarında basit ve çevik olduğu için dinamik ViewModel'leri kullanarak geliştirmeye başladık. Ancak geliştirme kararlı hale geldikten sonra API'leri yeniden düzenlemeyi ve ViewModel'ler için statik veya önceden tanımlanmış DTO'ları kullanmayı seçtik çünkü mikro hizmetin tüketicilerinin açık DTO türlerini bilmesi daha açık ve "sözleşmeler" olarak kullanılıyor.

Aşağıdaki örnekte, açık bir ViewModel DTO sınıfı kullanarak sorgunun verileri nasıl döndüreceğini görebilirsiniz: OrderSummary sınıfı.

using Dapper;
using Microsoft.Extensions.Configuration;
using System.Data.SqlClient;
using System.Threading.Tasks;
using System.Dynamic;
using System.Collections.Generic;

public class OrderQueries : IOrderQueries
{
  public async Task<IEnumerable<OrderSummary>> GetOrdersAsync()
    {
        using (var connection = new SqlConnection(_connectionString))
        {
            connection.Open();
            return await connection.QueryAsync<OrderSummary>(
                  @"SELECT o.[Id] as ordernumber,
                  o.[OrderDate] as [date],os.[Name] as [status],
                  SUM(oi.units*oi.unitprice) as total
                  FROM [ordering].[Orders] o
                  LEFT JOIN[ordering].[orderitems] oi ON  o.Id = oi.orderid
                  LEFT JOIN[ordering].[orderstatus] os on o.OrderStatusId = os.Id
                  GROUP BY o.[Id], o.[OrderDate], os.[Name]
                  ORDER BY o.[Id]");
        }
    }
}

Web API'lerinin yanıt türlerini açıklama

Web API'lerini ve mikro hizmetleri kullanan geliştiriciler, özellikle yanıt türleri ve hata kodları (standart değilse) olmak üzere döndürülen öğelerle en çok ilgilenir. Yanıt türleri XML açıklamalarında ve veri ek açıklamalarında işlenir.

Swagger kullanıcı arabiriminde uygun belgeler olmadan, tüketici hangi türlerin döndürülmekte olduğu veya hangi HTTP kodlarının döndürülebileceği konusunda bilgi sahibi değildir. Bu sorun, aşağıdaki kodda Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttributegösterildiği gibi Swashbuckle'ın API dönüş modeli ve değerleri hakkında daha zengin bilgiler oluşturabilmesi için eklenerek düzeltilir:

namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
{
    [Route("api/v1/[controller]")]
    [Authorize]
    public class OrdersController : Controller
    {
        //Additional code...
        [Route("")]
        [HttpGet]
        [ProducesResponseType(typeof(IEnumerable<OrderSummary>),
            (int)HttpStatusCode.OK)]
        public async Task<IActionResult> GetOrders()
        {
            var userid = _identityService.GetUserIdentity();
            var orders = await _orderQueries
                .GetOrdersFromUserAsync(Guid.Parse(userid));
            return Ok(orders);
        }
    }
}

Ancak, ProducesResponseType özniteliği tür olarak dinamik kullanamaz, ancak aşağıdaki örnekte gösterilen ViewModel DTO gibi OrderSummary açık türlerin kullanılmasını gerektirir:

public class OrderSummary
{
    public int ordernumber { get; set; }
    public DateTime date { get; set; }
    public string status { get; set; }
    public double total { get; set; }
}
// or using C# 8 record types:
public record OrderSummary(int ordernumber, DateTime date, string status, double total);

Bu, açık döndürülen türlerin uzun vadede dinamik türlerden daha iyi olmasının bir diğer nedenidir. özniteliğini ProducesResponseType kullanırken, 200, 400 gibi olası HTTP hataları/kodlarıyla ilgili beklenen sonucun ne olduğunu da belirtebilirsiniz.

Aşağıdaki görüntüde, Swagger kullanıcı arabiriminin ResponseType bilgilerini nasıl gösterdiğini görebilirsiniz.

Screenshot of the Swagger UI page for the Ordering API.

Şekil 7-5. Bir Web API'sinden yanıt türlerini ve olası HTTP durum kodlarını gösteren Swagger kullanıcı arabirimi

Görüntüde ViewModel türlerine ve döndürülebilecek olası HTTP durum kodlarına göre bazı örnek değerler gösterilir.

Ek kaynaklar