Saya telah mencari CQRS / MediatR belakangan ini. Tetapi semakin saya menelusuri semakin kurang saya menyukainya. Mungkin saya salah paham tentang sesuatu / segalanya.
Jadi itu mulai luar biasa dengan mengklaim mengurangi controller Anda untuk ini
public async Task<ActionResult> Edit(Edit.Query query)
{
var model = await _mediator.SendAsync(query);
return View(model);
}
Yang sangat cocok dengan pedoman pengontrol yang tipis. Namun itu meninggalkan beberapa detail yang cukup penting - penanganan kesalahan.
Mari kita lihat Login
aksi default dari proyek MVC baru
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);
}
Konversi yang memberi kita banyak masalah dunia nyata. Ingat tujuannya adalah untuk menguranginya menjadi
public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
var model = await _mediator.SendAsync(command);
return View(model);
}
Salah satu solusi yang mungkin untuk ini adalah mengembalikan CommandResult<T>
bukan model
dan kemudian menangani CommandResult
dalam filter tindakan pos. Seperti yang dibahas di sini .
Salah satu implementasi CommandResult
bisa seperti ini
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
Namun itu tidak benar-benar menyelesaikan masalah kita dalam Login
tindakan, karena ada beberapa kondisi kegagalan. Kita dapat menambahkan status kegagalan tambahan ini ICommandResult
tetapi itu merupakan awal yang bagus untuk kelas / antarmuka yang sangat besar. Orang mungkin mengatakan itu tidak sesuai dengan Tanggung Jawab Tunggal (SRP).
Masalah lainnya adalah returnUrl
. Kami memiliki return RedirectToLocal(returnUrl);
kode ini. Entah bagaimana kita perlu menangani argumen bersyarat berdasarkan status keberhasilan perintah. Sementara saya pikir itu bisa dilakukan (saya tidak yakin apakah ModelBinder dapat memetakan argumen FromBody dan FromQuery ( returnUrl
adalah FromQuery) ke model tunggal). Orang hanya bisa bertanya-tanya skenario gila macam apa yang bisa terjadi.
Validasi model juga menjadi lebih kompleks seiring dengan pengembalian pesan kesalahan. Ambil ini sebagai contoh
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
Kami melampirkan pesan kesalahan bersama dengan model. Hal semacam ini tidak dapat dilakukan dengan menggunakan Exception
strategi (seperti yang disarankan di sini ) karena kita memerlukan model. Mungkin Anda bisa mendapatkan modelnya dari Request
tetapi itu akan menjadi proses yang sangat terlibat.
Jadi secara keseluruhan saya mengalami kesulitan mengubah tindakan "sederhana" ini.
Saya mencari input. Apakah saya benar-benar salah di sini?
sumber
Jawaban:
Saya pikir Anda mengharapkan terlalu banyak pola yang Anda gunakan. CQRS secara khusus dirancang untuk mengatasi perbedaan model antara permintaan dan perintah ke basis data , dan MediatR hanyalah pustaka perpesanan yang masih dalam proses. CQRS tidak mengklaim untuk menghilangkan kebutuhan akan logika bisnis seperti yang Anda harapkan. CQRS adalah pola untuk akses data, tetapi masalah Anda adalah dengan lapisan presentasi - arahan, tampilan, pengontrol.
Saya pikir Anda mungkin salah menerapkan pola CQRS ke otentikasi. Dengan login, itu tidak bisa dimodelkan sebagai perintah di CQRS karena
Menurut pendapat saya, otentikasi adalah domain yang buruk untuk CQRS. Dengan otentikasi, Anda memerlukan aliran respons permintaan yang sangat konsisten dan sinkron sehingga Anda dapat 1. memeriksa kredensial pengguna 2. membuat sesi untuk pengguna 3. menangani salah satu dari berbagai kasus tepi yang telah Anda identifikasi 4. segera berikan atau tolak pengguna sebagai tanggapan.
CQRS adalah pola yang memiliki kegunaan yang sangat spesifik. Tujuannya adalah untuk memodelkan pertanyaan dan perintah alih-alih memiliki model untuk catatan seperti yang digunakan dalam CRUD. Ketika sistem menjadi lebih kompleks, tuntutan tampilan sering kali lebih kompleks dari sekadar menunjukkan satu catatan atau beberapa catatan, dan kueri dapat memodelkan kebutuhan aplikasi dengan lebih baik. Demikian pula perintah dapat mewakili perubahan ke banyak catatan, bukan CRUD yang Anda ubah catatan tunggal. Martin Fowler memperingatkan
Jadi untuk menjawab pertanyaan Anda CQRS tidak boleh menjadi pilihan pertama ketika merancang aplikasi saat CRUD cocok. Tidak ada dalam pertanyaan Anda yang memberi saya indikasi bahwa Anda memiliki alasan untuk menggunakan CQRS.
Adapun MediatR, ini adalah pustaka perpesanan dalam proses, ini bertujuan untuk memisahkan permintaan dari penanganan permintaan. Anda harus memutuskan lagi apakah akan meningkatkan desain Anda untuk menggunakan perpustakaan ini. Saya pribadi bukan penganjur pesan dalam proses. Pelepasan lepas dapat dicapai dengan cara yang lebih sederhana daripada perpesanan, dan saya sarankan Anda mulai dari sana.
sumber
CQRS lebih merupakan hal manajemen data daripada dan tidak cenderung terlalu banyak berdarah ke dalam lapisan aplikasi (atau Domain jika Anda suka, karena cenderung paling sering digunakan dalam sistem DDD). Aplikasi MVC Anda, di sisi lain, adalah aplikasi lapisan presentasi dan harus dipisahkan dari inti kueri / persistensi CQRS.
Hal lain yang patut dicatat (mengingat perbandingan Anda tentang
Login
metode default dan keinginan untuk pengontrol tipis): Saya tidak akan persis mengikuti standar ASP.NET templat / kode boilerplate sebagai sesuatu yang harus kita khawatirkan untuk praktik terbaik.Saya suka pengontrol tipis juga, karena sangat mudah dibaca. Setiap controller yang saya miliki biasanya memiliki objek "service" yang dipasangkan dengan yang pada dasarnya menangani logika yang diperlukan oleh controller:
Masih cukup tipis, tetapi kami belum benar-benar mengubah cara kode bekerja, cukup mendelegasikan penanganan ke metode layanan, yang benar-benar tidak memiliki tujuan lain selain membuat tindakan pengontrol mudah dicerna.
Ingatlah, kelas layanan ini masih bertanggung jawab untuk mendelegasikan logika ke model / aplikasi sebagaimana diperlukan, itu benar-benar hanya sedikit ekstensi dari controller untuk menjaga kode tetap rapi. Metode layanan umumnya cukup pendek juga.
Saya tidak yakin mediator akan melakukan sesuatu yang berbeda secara konseptual dari itu: memindahkan beberapa logika kontroler dasar dari controller dan ke tempat lain untuk diproses.
(Saya belum pernah mendengar MediatR ini sebelumnya, dan melihat sekilas pada halaman github tampaknya tidak mengindikasikan bahwa itu adalah sesuatu yang inovatif - tentu saja bukan sesuatu seperti CQRS - pada kenyataannya, itu terlihat seperti sesuatu seperti lapisan abstraksi lain yang Anda dapat dimasukkan untuk memperumit kode dengan cara membuatnya terlihat lebih sederhana, tapi itu hanya pandangan awal saya)
sumber
Saya sangat menyarankan Anda melihat presentasi NDC Jimmy Bogard tentang pendekatannya untuk memodelkan permintaan http https://www.youtube.com/watch?v=SUiWfhAhgQw
Anda kemudian akan mendapatkan gagasan yang jelas tentang apa yang digunakan Mediatr.
Jimmy tidak memiliki kepatuhan buta terhadap pola dan abstraksi. Dia sangat pragmatis. Mediatr tidak membersihkan tindakan pengontrol. Adapun penanganan pengecualian, saya mendorong itu ke kelas induk yang disebut sesuatu seperti Execute. Jadi Anda berakhir dengan tindakan pengontrol yang sangat bersih.
Sesuatu seperti:
Penggunaannya terlihat seperti ini:
Semoga itu bisa membantu.
sumber
Banyak orang (saya juga melakukannya) mengacaukan pola dengan perpustakaan. CQRS adalah sebuah pola tetapi MediatR adalah pustaka yang dapat Anda gunakan untuk menerapkan pola itu
Anda dapat menggunakan CQRS tanpa MediatR atau pustaka perpesanan dalam proses dan Anda dapat menggunakan MediatR tanpa CQRS:
CQS akan terlihat seperti ini:
Bahkan, Anda tidak perlu menyebut model input Anda "Perintah" seperti di atas
CreateProductCommand
. Dan masukan kueri Anda "Kueri". Perintah dan pertanyaan adalah metode, bukan model.CQRS adalah tentang pemisahan tanggung jawab (metode baca harus terpisah dari metode menulis - diisolasi). Ini adalah ekstensi untuk CQS tetapi perbedaannya adalah di CQS Anda dapat menempatkan metode ini dalam 1 kelas. (tidak ada pemisahan tanggung jawab, hanya pemisahan perintah-permintaan). Lihat pemisahan vs pemisahan
Dari https://martinfowler.com/bliki/CQRS.html :
Ada kebingungan dalam apa yang dikatakannya, ini bukan tentang memiliki model terpisah untuk input dan output, ini tentang pemisahan tanggung jawab.
Batasan CQRS dan generasi id
Ada satu batasan yang akan Anda hadapi saat menggunakan CQRS atau CQS
Secara teknis dalam perintah deskripsi asli seharusnya tidak mengembalikan nilai apa pun (void) yang menurut saya bodoh karena tidak ada cara mudah untuk mendapatkan id yang dihasilkan dari objek yang baru dibuat: /programming/4361889/how-to- get-id-in-create-when-apply-cqrs .
jadi Anda harus membuat id setiap kali sendiri daripada membiarkan database melakukannya.
Jika Anda ingin mempelajari lebih lanjut: https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
sumber