Discovering The Truth About CQRS - No MediatR Required
Вставка
- Опубліковано 5 чер 2024
- ☄️ Master the Modular Monolith Architecture: bit.ly/3SXlzSt
📌 Accelerate your Clean Architecture skills: bit.ly/3PupkOJ
🚀 Support me on Patreon to access the source code: / milanjovanovic
CQRS, which stands for Command Query Responsibility Segregation, is the most misunderstood pattern.
You don't need any of these things:
- Separate read and write databases
- Event sourcing
- MediatR
In this video, I'll show you why CQRS is really simple and how to implement it straightforwardly.
Join my weekly .NET newsletter:
www.milanjovanovic.tech
Read my Blog here:
www.milanjovanovic.tech/blog
Subscribe for more:
/ @milanjovanovictech
Chapters
0:00 In-Memory Repository + API
2:07 Before CQRS, there was just CQS
3:54 What CQRS actually is
8:03 Granular CQRS (without MediatR) - Наука та технологія
If you want to learn more about .NET and software architecture, consider subscribing to my newsletter.
→ Join 22,000+ engineers here: www.milanjovanovic.tech/
Thank you for the video. I would like to clarify the concept of CQRS (Command Query Responsibility Segregation) to address your points more clearly.
CQRS is indeed about segregating the responsibilities of handling commands and queries, but it doesn't necessarily mandate a strict separation of only commands handling state changes and queries returning data. The core principle is to recognize that commands (actions that change the system's state) and queries (actions that retrieve data without altering the state) have different requirements and should be treated differently.
In the context of CQRS:
Commands typically represent operations that modify the system's state, such as creating, updating, or deleting records. These operations may or may not return an ID or other information depending on the specific use case or system design. It's perfectly acceptable to return data if it's necessary for the application's requirements, and CQRS doesn't forbid this.
Queries, on the other hand, focus on retrieving data from the system without altering it. They are responsible for providing information to the client.
In summary, CQRS encourages separating the handling of commands and queries because they have different purposes and requirements, but it doesn't strictly enforce a specific way of returning data or IDs. The exact design and implementation can vary depending on the application's needs and architecture.
Nice summary!
You know what Milan?
Today I took the step. You are officially my first ever patreon creator I'm subscribed to.
Your videos are very useful to me so I decided to support your work!
Thank you so much, Gianluca! I'll make sure to keep the video quality & code to a high standard. 🚀
Thank you! This is exactly what I've been thinkning while researching the subject for a new project. Every article on Vertical Slice Architecture or CQRS always starts out with Mediatr. But I don't need all of that extra depedency or complexity in this particular project.
I hope this'll help people seeking salvation from MediatR 😁
Awesome! .. we as (new) developers sometimes forget that libraries and packages just put something fancy and complicate things around some concept that was written on books many years ago
I still think MediatR is great!
Thanks for sharing! Nice to you took a step further and implemented commands and queries in Mediatr fashion.
What I am missing here, and most of the videos talking about CQRS is WHY. Why do we even have CQRS, why separation, and it's not due to single responsibility principle - that's just an outcome.
Having separated Writes / Reads is also WHAT but not Why. So I would appreciate if you're willing to explain that :)
Thanks again, informative!
I use it because it results in smaller and simpler classes, and design. Additionally queries can bypass the business/domain layer. Before the advent of RESTful API (which I abhor for various reasons), for every package or DLL I used to have 2 facades - one for writing, one for reading. Separated Writes / Reads makes you use separate databases - one that is optimized for writing (SQL and NOSQL are an option) and another for querying. You querying DB design, avoids all types of joins, as it is designed for a simple select query.
Then you should understand why separation of concerns/single responsibility is good, I'd start there
Just Tomi See my attempt at clarification above.
@@MilanJovanovicTechI have been SRP since around 1998 or so. 🙂
Awesome content! This come clarify much things in my mind about CQRS. Thank you Milan.
Glad it was helpful!
very often I see that cqrs is understood as simply separating queries and commands, but it's so simple to separate it into a whole architectural pattern. This is often shown through implementation using MediatR, but all such implementations lose the point of separating queries and requests. The bottom line is that queries for obtaining data have completely different requirements and materialized views of the database or a completely different type of database that is synchronized with the database for writing can be used for them. It would be nice if you showed such an example and talked about it
That's an optimization that isn't practical for most applications out there, but I am working on showing some of those patterns. I have a video on materialized views coming out soon.
@@MilanJovanovicTech If you are event sourcing CQRS really comes into its own, since writing events and reading from projections makes the need to separate the models much more obvious.
Correct. inclusive that very video is missing the point by a lot.
We are using CQRS without MediatR and works very well :)
Are you using a similar approach to this?
@@MilanJovanovicTech Kind of. We have two interfaces for commandhandler and queryhandler that will be injected when we dispatch the query or command. :)
We going from data-logic-web design to more vertical slice and using CQRS for communicate between different modules in the service.
Thanks for the simple and to the point video.
Glad it was helpful!
Thank you! Clearly and precise.
Glad you liked it!
Clearly and simple, Thank you.
Glad you liked it! :)
Very nice and clear video. Well done.
Thanks a lot! :)
You are the best. thank you for this beautiful video.
Thank you too!
Thank you first !!!
Finally! I have been waiting to see a video where it is well explained that CQRS DOESN'T require MediatR like its shown in many YT videos.
CQRS pattern version where there are separate classes for Read or Write will be most suitable for a majority of projects.
IMO, CQRS where each of the commands and queries are in their own classes is just an explosion of classes. If somebody disagrees with that, there could be two reasons =>
1) Either they don't have a lot of queries/commands; in that case you shouldn't even have separate classes in the first place for each query/command.
2) Or you have a lot of queries/commands, which obviously results in an explosion of classes. Even if you just have CRUD, 10 domain entities will result in 40 staggering classes.
Having just 2 different classes at most for Read/Write should fulfill the architecture requirement for many projects where Read/Write happens through different DBs.
Also, if the above is followed, there really is no need for a library like MediatR, unless there are other uses.
MediatR offers more than just splitting commands/queries, that's why I like using it
I enjoy watching your videos are night before going to bed. Thanks for sharing. I have one question, How would you handle a dbcontext that is static?
When would you have a static DbContext? I think that's a bad idea since it will get bloated over time (the ChangeTracker)
@@MilanJovanovicTech sorry i forgot the "not" somehow. so not static. Im not crazy enough yet to use a static db :)
Thank you it's very clear now :)
You're welcome!
Exceptional explanation!
Glad it helped!
I first started using a CQRS like approach around 2007. Never ever felt the need for MediatR.
I like using MediatR for the other features it has
@@MilanJovanovicTech I get why people use it. But most of our CQRS code is generated at compile time and the generated Command dispatcher classes is used to dispatch the commands. The mapping of command to command handlers is done in xml. Code generation saves you a lot of headaches.
@@arunnair7584 I believe there is a library that duplicates the functionality of MediatR using code generators. I don't recall the name of it.
@@pilotboba I see. I have never come across that. Anyway our apps require sub second response times. And where ever we can find a performance boost, we move the domain logic into stored procedures. This is mainly to squeeze out every bit of performance from the system, We also have a plug and play type of architecture, where new features can be added. The architecture has served us well for more than a decade now.
@@arunnair7584 I don't see how this affects perf. Perhaps a small bit if you aren't managing your lifetimes correctly and see a lot of l2 and l3 garbage collection.
But, more, smaller classes isn't going to cause any noticeable perf issues. I think Nick even covered this in one of his videos.
The litterature about CQRS motivates the separation of queries and commands because you would eventually need separated *models* from reading and updating the information.
Yet, I have not found different *models* in your video, you only broke the repository's implementation. Still, there is still only one Book entity that is used in both query and command scenarios.
I would have liked seeing different models (because for me, that's the most important part) of that Book concept, one model that is used for reading Books, and some other model for updating Books. Once you do that, I don't even think it's necessary to apply CQS at the repository level anymore.
What does a different model mean for you?
@@MilanJovanovicTech The way I understood it, is mainly the entities. For example, for querying scenarios, having a BookDisplay, BookSummary, etc.., and for commands, Book.
@@nikogj9495 I think what you're referring to is the response and request models for reading, creating and updating the Book entity.
Hey, nice video! Just a doubt, is instantiate a "command" class good in these scenarios ? I mean the garbage collector will make his job when the request ended no ? because for me even
looks clearer than the mediaTr approach that uses reflexion so you can't go to de command directly from the call
No indirection is a benefit
@MilanJovanovicTech Yep, thats what I wanted to say, I prefer instantiate the class reather than use MeditR, sorry my english is terrible hahaha! keep going with your tutorials!
Very clear explanation at all levels of the CQRS co fusion. Nicely done
Thanks buddy!
Let's say I want the command methods to be separatly scalable from the query commands. In that case I would have to create 2 different microservices right?
Yup, you'd need to split them into separate services. Most of the time, you probably won't need that.
🎯 Key Takeaways for quick navigation:
00:00 *📚 CQRS is not about mediator, separate databases, or event sourcing; it's about command query separation (CQS).*
02:17 *🔄 Command Query Separation (CQS) emphasizes separating methods for reading (queries) and updating (commands) data in your code.*
04:06 *🧱 CQRS builds upon CQS by segregating commands and queries into separate classes or objects, enhancing clarity and focus in your codebase.*
06:51 *🛠️ Separating commands and queries enhances code clarity, enforces the single responsibility principle, and doesn't necessitate separate databases or event sourcing.*
08:09 *🔧 Extending CQRS involves creating separate objects for each command, further emphasizing logical separation and maintainability.*
12:44 *📝 CQRS boils down to logically separating objects responsible for writing data from those responsible for reading it, promoting code clarity and maintainability.*
Made with HARPA AI
Cool
by creating that much objects (Command) doesn't it effect the performance ?
Not nearly as much as one round trip to the database will
What other benefits does CQRS add other than separating the code into logical chunks?
There are two main ones: First it enables you to use different database as a source of data for queries. (although one may argue that you can accomplish the same in service).
The second (More important one) is that instead of having nasty services with thousands lines of code and dependency injection nightmare we end up with clean classes which obey single responsibility principle
That IS the benefit. When you have a bunch of complex business logic that needs be performed during the handling of one request, you may want to separate it into its own class to make your source code less of a cluttered mess. Let's use books as an example like Milan did in this video. Say you're running a web app for a large bookstore. When that bookstore has a new book added to its inventory, the staff don't just record it in the database and forget about it. There are a number of other things that can happen when a book is added:
* the book is added to the "New Books" section of the bookstore's website's front page
* the book is added to a weekly newsletter publication
* the book is added to a search engine and recommendations algorithm
* the book is removed from a list of expected arrivals from an integrated inventory management system
Et cetera. Et cetera. Like others have said, the CQRS pattern is meant for large, enterprise-scale applications where seemingly simple events can have a ripple effect across the entire application. In those cases, you want to compartmentalize those events and have classes that handle them individually so that you can test them more easily.
That is the benefit, you just have to wrap your head around it
Should I use Filter instead of PipelineBehavior in this situation?
Or regular middleware?
@@MilanJovanovicTech what if we want to get the result of the request to do caching for example?
“What is the first thing that comes to mind when you hear CQRS?”
Is this a collaborative domain?
What would an example of that be?
The problem with the sample you created there is that it is extremely simplistic and unrealistic. Any real world app will have some dependencies, like DbContext if using EF Core or a DbConnection for Dapper or any other kind of service the app depends on, so now you will have to inject all of that in the command, which is expected to only hold the data it will provide later to the handler, but also with dependencies that will need to be provided in each of the endpoints. For this you will then create some generic stuff to reuse everywhere so you don't repeat yourself so much, and what you end up with? With another home-made version of MediatR 🙃
In the end, you can do CQS/CQRS without MediatR, that's correct, but you end up either making your code more cumbersome or you end up reinventing the wheel to make your own (dumbed-down?) version of MediatR... So implementing MediatR is more a convenience/advantage than a requirement.
not necessarily. a route that one can take it to treat the various commands/queries as standalone services in a sense and inject the command/query in each endpoint then simply inherit from some “IUseCase”-like interface and just register each “service” that implements it via DI with a bit of reflection.
@@thatojadezweni like I said: reinventing the wheel all over again with your own home-made version...
You have to use simple examples when explaining concepts, otherwise from my experience, people will completely miss the point.
@@MilanJovanovicTech I do get that and for the most things that's all right, is just that people later tend to take the simplest samples to the letter and I was just pointing out why in the majority of the real scenarios MediatR is chosen for implementing CQS/CQRS... It was not precisely a critic to your video (which was very nice, btw), but an observation comparing to real life implementations, mainly due to many of the comments oversimplifying this.
Cheers!
I‘m sorry to say this, but this video totally misses the point. CQRS is about having different models for querying and manipulating data, and not splitting up data access methods into different Command and Query classes.
Consider a scenario where a company develops a new product and several departments need to work together in a process which might look like this: marketing identifies that a new product might be a good fit for the market, so they create it. The technical planning team then adds plans on how to build the new thing, production then adds infos about the steps needed and machines to use and how fast it can be produced, the safety department adds infos about regulation in different countries, and management finally reviews and gives green light.
This complicated process involves several tables in a relational database, and in the different steps of the process, you would have seperate models for accessing these tables for the different domains. You would have several entities accessing the same tables/views. For example, the safety department shouldn't be able to manipulate data for the technical planning team, but it can view the data to be able to do all things concerning regulation.
This is explicitly expressed in the data access model: if you are using EF Core, you would have dedicated entity classes which only encompass the columns the safety department can actually change, and dedicated read entities containing the columns that the safety department are able to see. That's what CQRS is about.
That's a nice explanation, Kenny!
why are you using Guid, isn't long is better?
Better is debatable
What a load of old ballcocks - in every system i have built a delete action returns a class that lets the caller know whether it succeeded or failed, and if it failed what the reason was. Because in anything more complicated than a todoitem application there might be many reasons why a delete should be rejected - the idea of returning a void in this case is just dumbing down.
There's a difference between "theory" and what actually makes sense in practice
@@MilanJovanovicTech Yeah - sorry for the flame
Clearly CQRS is not meant for 70-80% projects out there which are medium to small scale projects. It only adds complexity without much benefits.
Do you think if it is a simple to medium project we should implement business logic directly in the controllers/endpoints or injecting a service with CQS approach instead?
I’d say it’s the other way around. Most apis would benefit from it - separating concerns is always a better default than not. You can knock up a command/query handling via DI very easily, nothing fancy but it will work very very well, no need for a fully featured Mediatr.
CQS and CQRS are good for every project, it’s a choice it’s not a rule or salvation. If you do it just don’t use the wrong tools to make it hard.
The benefits are conceptual, and to me they are worth it
@@booby163 It depends. If there is scope of improvement or further development then YES we should be separating concerns however we must not complicate our project from the very beginning with all this DDD, TDD, BDD, CQRS, MediatR etc stuff.
Sure simple if you use a static database like you did. That is not REAL life though. No one does that. Lets see how simple this video is if you need to inject services into your handle method. I'm no fan of Mediatr but come on, Mediatr is a lot more simple then the method shown here. Hell, you should have made it more simple and cleaner by making your Handle methods static!
Let's do a Part 2 then!
Where the create book record stands? It should return the newly created guid at least 🤔
That's okay
@@MilanJovanovicTech could it then return the newly created entity then? We had a debate with one of my coworker on this, I stood for just the guid so the next call would retrieve the full record while he said it is a redundant call
No one seems to explain *why* commands can’t return data, nor address the elephant in the room: if they can’t return data, how would your example look if books had autogenerated IDs? If clients send an update command, why should they then need to query to see the results? If the command is long running, how the hell can the client even be informed about progress if it has no handle for the execution? I know you said be pragmatic but that just makes the entire premise of commands never returning data completely bogus. Sorry, a bit frustrated after spending over an hour watching CQRS videos and everyone is just talking about the obvious and showing crazy levels of abstraction to support the pattern but never actually talking about the more interesting questions that arise when applying this principal in the real world.
Check the first two sections here: www.milanjovanovic.tech/blog/cqrs-pattern-with-mediatr
"This doesn't mean a command can never return a value. A typical example is popping a value from a stack. It returns a value and changes the state of the system. But the intent is what matters here.
CQS is a principle. You can follow this principle if it makes sense, but be pragmatic."