ASP.NET uygulaması geliştirirken CQRS / MediatR buna değer mi?


17

Son zamanlarda CQRS / MediatR'ı araştırdım. Ama daha fazla ayrıntıya inersem daha az hoşuma gider. Belki de bir şeyi / her şeyi yanlış anladım.

Bu yüzden kontrol cihazınızı buna indirgeyerek iddia ederek harika başlar

public async Task<ActionResult> Edit(Edit.Query query)
{
    var model = await _mediator.SendAsync(query);

    return View(model);
}

İnce kontrolör kılavuzuna mükemmel uyum sağlar. Ancak, hata işleme - oldukça önemli bazı ayrıntılar bırakır.

LoginYeni bir MVC projesinden varsayılan eyleme bakalım

public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
    ViewData["ReturnUrl"] = returnUrl;
    if (ModelState.IsValid)
    {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, set lockoutOnFailure: true
        var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
        if (result.Succeeded)
        {
            _logger.LogInformation(1, "User logged in.");
            return RedirectToLocal(returnUrl);
        }
        if (result.RequiresTwoFactor)
        {
            return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
        }
        if (result.IsLockedOut)
        {
            _logger.LogWarning(2, "User account locked out.");
            return View("Lockout");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return View(model);
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Dönüştürme bize bir sürü gerçek dünya problemi sunuyor. Unutmayın, amaç

public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
    var model = await _mediator.SendAsync(command);

    return View(model);
}

Bunun olası bir çözümü bir CommandResult<T>yerine bir döndürmek modelve sonra CommandResultbir eylem sonrası filtre işlemek için . As tartışılan burada .

Bunun bir uygulaması şöyle CommandResultolabilir

public interface ICommandResult  
{
    bool IsSuccess { get; }
    bool IsFailure { get; }
    object Result { get; set; }
}

kaynak

Ancak bu, Logineylemdeki sorunumuzu gerçekten çözmez , çünkü birden fazla başarısızlık durumu vardır. Bu ekstra hata durumlarınıICommandResult ancak bu çok şişmiş bir sınıf / arayüz için harika bir başlangıçtır. Tek Sorumluluk (SRP) ile uyumlu olmadığını söyleyebiliriz.

Başka bir sorun returnUrl. Bu return RedirectToLocal(returnUrl);kod parçamız var. Her nasılsa, komutun başarı durumuna dayalı koşullu argümanları ele almamız gerekir. Ben bu yapılabilirdi düşünüyorum olsa da (ModelBinder FromBody ve FromQuery ( returnUrlFromQuery) bağımsız değişkenleri tek bir model eşleyebilirsiniz emin değilim ). Sadece ne tür çılgın senaryoların yoldan aşağıya gelebileceğini merak edebiliriz.

Model doğrulaması, geri dönen hata mesajlarıyla birlikte daha karmaşık hale gelmiştir. Bunu örnek olarak alalım

else
{
    ModelState.AddModelError(string.Empty, "Invalid login attempt.");
    return View(model);
}

Modelle birlikte bir hata mesajı ekliyoruz. Bu tür bir şey Exception( burada önerildiği gibi ) bir strateji kullanılarak yapılamaz çünkü modele ihtiyacımız var. Belki modeli alabilirsinizRequest ama çok ilgili bir süreç olurdu.

Sonuçta bu "basit" eylemi dönüştürmekte zorlanıyorum.

Girdi arıyorum. Burada tamamen yanlış mıyım?


6
İlgili endişeleri zaten çok iyi anlıyormuşsunuz gibi geliyor. Orada, kullanışlılıklarını kanıtlayan oyuncak örneklerine sahip olan, ancak gerçek, gerçek hayattaki bir uygulamanın gerçekliği ile sıkıldıklarında kaçınılmaz olarak düşen çok sayıda "gümüş mermi" var.
Robert Harvey

MediatR Davranışlarına göz atın. Temelde kesişen endişeleri çözmenize izin veren bir boru hattı.
fml

Yanıtlar:


14

Sanırım kullandığınız kalıptan çok fazla şey bekliyorsunuz. CQRS özellikle veritabanına sorgu ve komutlar arasındaki model farkını ele almak için tasarlanmıştır ve MediatR sadece süreç içi mesajlaşma kütüphanesidir. CQRS, beklediğiniz gibi iş mantığı ihtiyacını ortadan kaldırdığını iddia etmez. CQRS veri erişimi için bir modeldir, ancak sorunlarınız sunum katmanındadır yönlendirmeler, görünümler, denetleyiciler.

Kimlik doğrulamasına CQRS modelini yanlış uygulıyor olabileceğinizi düşünüyorum. Girişimle birlikte o olamaz bir CQRS bir komut olarak modellenmiş çünkü

Komutlar: Sistemin durumunu değiştirin ancak bir değer döndürmeyin
- Martin Fowler CommandQuerySeparation

Kanımca kimlik doğrulaması CQRS için zayıf bir alan. Kimlik doğrulama ile güçlü bir şekilde tutarlı, eşzamanlı istek-yanıt akışına ihtiyacınız vardır, böylece 1. kullanıcının kimlik bilgilerini kontrol edin 2. kullanıcı için bir oturum oluşturun 3. tanımladığınız çeşitli kenar durumlardan herhangi birini ele alın 4. anında kullanıcı verin veya reddedin cevap olarak.

ASP.NET uygulaması geliştirirken CQRS / MediatR buna değer mi?

CQRS çok özel kullanımları olan bir modeldir. Amacı, CRUD'da kullanılan kayıtlar için bir model oluşturmak yerine sorguları ve komutları modellemektir. Sistemler daha karmaşık hale geldikçe, görüntüleme talepleri genellikle tek bir kayıt veya bir avuç kayıt göstermekten daha karmaşıktır ve bir sorgu uygulamanın ihtiyaçlarını daha iyi modelleyebilir. Benzer şekilde komutlar, tek kayıtları değiştirdiğiniz CRUD yerine birçok kayıttaki değişiklikleri temsil edebilir. Martin Fowler uyardı

Herhangi bir model gibi, CQRS bazı yerlerde yararlıdır, ancak bazılarında yararlı değildir. Birçok sistem bir CRUD zihinsel modele uyar ve bu tarzda yapılmalıdır. CQRS, ilgili herkes için önemli bir zihinsel sıçramadır, bu nedenle fayda atlamaya değmezse ele alınmamalıdır. CQRS'in başarılı kullanımlarıyla karşılaşmış olmama rağmen, şimdiye kadar karşılaştığım vakaların çoğu o kadar iyi değildi, CQRS bir yazılım sistemini ciddi zorluklara sokmak için önemli bir güç olarak görülüyor.
- Martin Fowler CQRS

Bu yüzden sorunuzu cevaplamak için, CRUD uygun olduğunda bir uygulama tasarlarken CQRS ilk çözüm olmamalıdır. Sorunuzdaki hiçbir şey bana CQRS kullanmak için bir nedeniniz olduğunu göstermedi.

MediatR'ye gelince, süreç içi bir mesajlaşma kütüphanesidir, istekleri istek işlemeden ayırmayı amaçlamaktadır. Bu kütüphaneyi kullanmak için tasarımınızı geliştirip geliştirmeyeceğine tekrar karar vermelisiniz. Ben şahsen süreç içi mesajlaşmanın savunucusu değilim. Gevşek bağlantı mesajlaşmadan daha basit yollarla elde edilebilir ve oradan başlamanızı tavsiye ederim.


1
% 100 katılıyorum. CQRS sadece biraz sinirli, bu yüzden "onlar" benim yapmadığım bir şey gördüm düşündüm. Çünkü CRUD web uygulamalarında CQRS'nin avantajlarını görmekte zorlanıyorum. Şimdiye kadar tek senaryo benim için anlamlı olan CQRS + ES.
Snæbjørn

Yeni işimdeki bazı kişiler MediatR'ı yeni ASP.Net sistemine bir mimari olarak iddia ederek koymaya karar verdi. Yaptığı uygulama DDD, SOLID, DRY veya KISS değil. YAGNI ile dolu küçük bir sistemdir. Sizinki gibi sizin de dahil olmak üzere bazı yorumlardan çok sonra başladı. Nasıl yavaş yavaş mimarisini uyarlamak için kodu reddetmek anlamaya çalışıyorum. Bir iş katmanı dışında CQRS hakkında aynı görüş vardı ve bu şekilde düşünen birkaç tecrübeli geliştirici olduğunu sevindim.
MFedatto

CQRS / MediatR'ı dahil etme fikrinin, aslında Havuz paternini şişirip zorlayarak YAGNI'yi teşvik ettiğinde, YAGNI ve bir KISS eksikliğiyle ilişkili olabileceğini teyit etmek biraz ironiktir. Bu tür arabirimleri uygulamak isteyen tüm kök toplamalarında çok sayıda CRUD işlemi belirtmek için arabirimler oluşturur ve genellikle bu yöntemleri kullanılmaz veya "uygulanmadı" özel durumlarıyla doldurur. CQRS bu genellemeleri kullanmadığından, sadece gerekli olanları uygulayabilir.
Lesair Valmont

@LesairValmont Deposu'nun yalnızca CRUD olması gerekiyor. "çok fazla CRUD işlemi belirtin" yalnızca 4 (veya "list" ile 5) olmalıdır. Daha spesifik sorgu erişim kalıplarınız varsa, bunlar depo arayüzünüzde olmamalıdır. Hiç kullanılmamış depo yöntemleri ile karşılaşmadım. Bir örnek verebilir misin?
Samuel

@Samuel: Bence depo deseni bazı senaryolar için mükemmel, tıpkı CQRS gibi. Aslında, büyük bir uygulamada, en uygun olanı depo deseni olacak bazı kısımlar ve CQRS tarafından daha fazla faydalanacak kısımlar olacaktır. Uygulamanın o bölümünde izlenen felsefe (ör. Göreve dayalı (CQRS) vs. CRUD (repo)), kullanılan ORM (varsa), etki alanının modellenmesi (örneğin) gibi birçok farklı faktöre bağlıdır. örneğin DDD). Basit CRUD katalogları için CQRS kesinlikle aşırıya kaçıyor ve bazı gerçek zamanlı işbirliği özellikleri (sohbet gibi) hiçbirini kullanmıyor.
Lesair Valmont

10

CQRS, bir veri yönetiminden ziyade bir veri yönetim şeyidir ve bir uygulama katmanına (veya isterseniz DDD sistemlerinde sıklıkla kullanılma eğiliminde olduğundan, tercih ederseniz Etki Alanına) çok fazla kanama eğilimi göstermez. MVC uygulamanız ise bir sunum katmanı uygulamasıdır ve CQRS'nin sorgu / kalıcılık çekirdeğinden oldukça iyi ayrılmalıdır.

Kayda değer başka bir şey (varsayılan ile karşılaştırmanız Login yöntem ve ince denetleyicileri için arzu ): Ben tam olarak varsayılan ASP.NET şablonları / boilerplate kodu en iyi uygulamalar için endişe gerekir bir şey olarak takip olmaz.

İnce denetleyicileri de seviyorum, çünkü okunması çok kolay. Genellikle her denetleyicinin, denetleyicinin gerektirdiği mantığı işleyen bir "hizmet" nesnesi vardır:

public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) {

    var result = _service.Login(model);
    switch (result) {
        case result.lockout: return View("Lockout");
        case result.ok: return RedirectToLocal(returnUrl);
        default: return View("GeneralError");
    }
}

Hala yeterince ince, ancak kodun nasıl çalıştığını gerçekten değiştirmedik, sadece işlemeyi, denetleyici eylemlerinin sindirilmesini kolaylaştırmaktan başka hiçbir amaca hizmet etmeyen hizmet yöntemine delege edin.

Unutmayın, bu hizmet sınıfı hala mantığı gerektiği gibi modele / uygulamaya devretmekten sorumludur, gerçekten kodu düzenli tutmak için kontrolörün hafif bir uzantısıdır. Servis yöntemleri de genellikle oldukça kısadır.

Arabulucunun kavramsal olarak bundan farklı bir şey yapacağından emin değilim: bazı temel denetleyici mantığını denetleyiciden ve işlenecek başka bir yere taşımak.

(Bu MediatR'ı daha önce duymamıştım ve github sayfasına hızlı bir bakış, bunun çığır açan bir şey olduğunu göstermiyor gibi görünüyor - kesinlikle CQRS gibi bir şey değil - aslında, sadece başka bir soyutlama katmanı gibi görünüyor. daha basit görünmesini sağlayarak kodu karmaşık hale getirebilir, ancak bu sadece benim ilk bakışım)


5

Http isteklerini modelleme konusundaki yaklaşımı hakkında Jimmy Bogard'ın NDC sunumunu görüntülemenizi şiddetle tavsiye ederim. Https://www.youtube.com/watch?v=SUiWfhAhgQw

Daha sonra Mediatr'ın ne için kullanıldığına dair net bir fikir edineceksiniz.

Jimmy'nin kalıplara ve soyutlamalara kör bir bağlılığı yoktur. Çok pragmatik. Mediatr, denetleyici eylemlerini temizler. İstisna işleme gelince, bunu Execute gibi bir ebeveyn sınıfına itiyorum. Böylece çok temiz bir kontrolör eylemi elde edersiniz.

Gibi bir şey:

public bool Execute<T>(Func<T> messageFunction)
{
    try
    {
        messageFunction();

        return true;
    }
    catch (ValidationException exception)
    {
        Errors = string.Join(Environment.NewLine, exception.Errors.Select(e => e.ErrorMessage));
        Logger.LogException(exception, "ValidationException caught in SiteController");
    }
    catch (SiteException exception)
    {
        Errors = exception.Message;
        Logger.LogException(exception);
    }
    catch (DbEntityValidationException dbEntityValidationException)
    {
        // Retrieve the error messages as a list of strings.
        var errorMessages = dbEntityValidationException.EntityValidationErrors
                .SelectMany(x => x.ValidationErrors)
                .Select(x => x.ErrorMessage);

        // Join the list to a single string.
        var fullErrorMessage = string.Join("; ", errorMessages);

        // Combine the original exception message with the new one.
        var exceptionMessage = string.Concat(dbEntityValidationException.Message, " The validation errors are: ", fullErrorMessage);

        Logger.LogError(exceptionMessage);

        // Throw a new DbEntityValidationException with the improved exception message.
        throw new DbEntityValidationException(exceptionMessage, dbEntityValidationException.EntityValidationErrors);                
    }
    catch (Exception exception)
    {
        Errors = "An error has occurred.";
        Logger.LogException(exception, "Exception caught in SiteController.");
    }

    // used to indicate that any transaction which may be in progress needs to be rolled back for this request.
    HttpContext.Items[UiConstants.Error] = true;

    Response.StatusCode = (int)HttpStatusCode.InternalServerError; // fail

    return false;
}

Kullanımı biraz şöyle görünüyor:

[Route("api/licence")]
public IHttpActionResult Post(LicenceEditModel licenceEditModel)
{
    var updateLicenceCommand = new UpdateLicenceCommand { LicenceEditModel = licenceEditModel };
    int licenceId = -1;

    if (Execute(() => _mediator.Send(updateLicenceCommand)))
    {
        return JsonSuccess(licenceEditModel);
    }

    return JsonError(Errors);
}

Umarım yardımcı olur.


4

Birçok kişi (ben de yaptım) deseni bir kütüphane ile karıştırıyor. CQRS bir kalıptır, ancak MediatR bu kalıbı uygulamak için kullanabileceğiniz bir kütüphanedir

MediatR olmadan CQRS veya herhangi bir işlem içi mesajlaşma kütüphanesi kullanabilirsiniz ve CQRS olmadan MediatR kullanabilirsiniz:

public interface IProductsWriteService
{
    void CreateProduct(CreateProductCommand createProductCommand);
}

public interface IProductsReadService
{
    ProductDto QueryProduct(Guid guid);
}

CQS şöyle görünecektir:

public interface IProductsService
{
    void CreateProduct(CreateProductCommand createProductCommand);
    ProductDto QueryProduct(Guid guid);
}

Aslında, giriş modellerinizi yukarıdaki gibi "Komutlar" olarak adlandırmanız gerekmez CreateProductCommand. Ve sorgu "Sorgular" girdi. Komut ve sorgular model değil yöntemdir.

CQRS sorumluluk ayrımı ile ilgilidir (okuma yöntemleri yazma yöntemlerinden ayrı bir yerde olmalıdır - yalıtılmış). CQS'nin bir uzantısıdır, ancak fark CQS'de bu yöntemleri 1 sınıfa koyabilirsiniz. (sorumluluk ayrımı yok, sadece komut-sorgu ayırma). Ayırma ve ayırmaya bakın

Gönderen https://martinfowler.com/bliki/CQRS.html :

Kalbinde, bilgiyi güncellemek için bilgileri okumak için kullandığınız modelden farklı bir model kullanabileceğiniz düşüncesidir.

Söylediği şeyde karışıklık var, bu girdi ve çıktı için ayrı bir modele sahip olmakla değil, sorumluluk ayrılığıyla ilgili.

CQRS ve kimlik oluşturma sınırlaması

CQRS veya CQS kullanırken karşılaşacağınız bir sınırlama vardır

Teknik olarak orijinal açıklama komutlarında aptal bulduğum herhangi bir değeri (void) döndürmemelidir, çünkü yeni oluşturulan bir nesneden kimlik oluşturmanın kolay bir yolu yoktur: /programming/4361889/how-to- cqrs uygularken yarat-id-in-create-id .

bu yüzden veritabanını izin vermek yerine her seferinde kendiniz bir kimlik oluşturmanız gerekir.


Daha fazla bilgi edinmek istiyorsanız: https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf


1
Yeni veritabanında oluşturulan bir kimliği döndüremeyen bir veritabanındaki yeni verileri devam ettirmek için bir CQRS komutunun "aptal" olduğunu doğrulamanıza meydan okuyorum. Bunun felsefi bir mesele olduğunu düşünüyorum. DDD ve CQRS'nin çoğunun veri değişmezliği ile ilgili olduğunu unutmayın. Bunu iki kez düşündüğünüzde, sadece verinin kalıcı olmasının bir veri mutasyon işlemi olduğunu anlamaya başlıyorsunuz. Ve bu sadece yeni kimliklerle ilgili değil, aynı zamanda varsayılan verilerle dolu alanlar, tetikleyiciler ve verilerinizi de değiştirebilecek depolanmış proclar da olabilir.
Lesair Valmont

Elbette argüman olarak yeni bir öğeyle "ItemCreated" gibi bir olay gönderebilirsiniz. Sadece istek-yanıt protokolü ile uğraşıyorsanız ve "gerçek" CQRS kullanıyorsanız, o zaman id açıkça bilinmelidir, böylece ayrı bir sorgu fonksiyonuna geçebilirsiniz - bu konuda kesinlikle yanlış bir şey yok. Çoğu durumda, CQRS aşırı derecede doludur. Onsuz yaşayabilirsiniz. Bu, kodunuzu yapılandırmanın bir yolu değildir ve çoğunlukla hangi protokolleri kullandığınıza bağlıdır.
Konrad

Ve CQRS olmadan veri değişmezliğini elde edebilirsiniz
Konrad
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.