EF Core In The CQRS Query Side... Or Something Else?

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
how do you implement the query side of the clean architecture are you depending directly on EF core or SQL or are you making your queries completely persistence ignorant in this video we're going to explore what options we have and what are the pros and cons of each approach let's first take a look at the get order query Handler which is depending on the I application DB context interface if we take a look at this interface you're going to see that it's just a wrapper around the EF core database context which is implemented in the persistence project it's exposing the database sets of the entities that we are using in our domain and the benefit of this approach is that you can directly use Link queries in your handlers and it's pretty easy to write your queries the disadvantage is that you are depending directly on EF core and you have different abstractions present in the application layer for example he here we are using the I application DB context for queries but in the create order command Handler we are using the approach with repositories and the unit of work to implement the flow for creating an order it's easy to see why this would be confusing because you have multiple paradigms mixed in the same project writing raw SQL queries with something like Dapper or even EF core suffers from the same problem as this approach where we are using EF core directly so how can we implement the query site so that it is persistence ignorant like our Command side we're going to start by creating an order service or you can even name it order read service so that it's more obvious what it's doing so let's define this service or rather just the interface for this service in the root folder of the orders feature folder so let's say this is the I order service now I'm going to make the this into an interface and what we're going to do is we're going to move this query here into that service the method is supposed to return a task of order response and we're going to call it get by ID async and we're going to accept the order ID argument which is also used in the original query now our get order query Handler is going to depend on this service so I order service and we're going to inject it from The Constructor now the order response is not going to be coming from this query it's going to come from order service get by ID async and we're just going to pass a new order ID so the end result is that our query Handler becomes extremely extremely thin and there's pretty much nothing inside if we also get rid of the dependency on the I application DB context this is what we are left with now it will probably be a good idea to also introduce a null check here and for this to make sense I'm going to change the definition of this method to return a nullable auto response because it could be the chance that we get a now response from the database and the appropriate thing to do would be to throw some sort of specific exception in this case so let's say we have an order not found exception and we're going to pass it the order ID value or rather let's move the strongly typed order ID into a variable and we're going to pass it to this exception that we're going to create so where should we Define the exception you can put it in the domain layer which is the approach that I took for the products when I created the product not found exception so let's use the other blocks I'm going to add the order not found exception as part of my domain layer and let's make this into a public and shield class it needs to inherit from the exception Base Class and let's add a Constructor that accepts a strongly typed order ID and we are just going to pass a string message to the base Constructor of the exception class which is going to say something like the order with the ID equal to ID value was not found this is good enough for our example and if we go back to our query Handler you'll see that everything is looking fine so we remove the dependency on EF core and replaced it with a read service so you could rename this to high order read Service to make it more obvious and actually let's go ahead and do that so I'm going to make this into I order read service or it can even be query service so naming is pretty important and I think this expresses the intent much better than the previous name for the implementation of this let's head over to the persistence project and let's create a new folder which is going to hold our services and let's add the order read Service implementation so order read Service it's going to be an internal and sealed class and we're going to make it implement the I order read Service we're going to inject the DB context directly instead of depending on the abstraction and I'm going to just copy the implementation that we previously had in the application project and we're going to try to fix the things that are broken so I'm going to start by making this asynchronous we need to update this to use the order ID that we got as the argument and we no longer have the cancellation token dependency because I decided to leave it out fix the indentation here and we should just be returning order response now for this to be nullable I'm actually going to use the overload that returns single or default and the slight problem with this approach is I'm calling single which is going to throw an exception if there are no records found because it is expecting exactly one record from the database so let's use first or default async and we're going to let our application layer throw the more specific order not found exception so this is how you can move your query into the persistence project and make your query Handler completely persistence ignorant the downside is your queries become just a thin wrapper around whatever is the query service in this case the I order read Service we shouldn't forget to register the I order read service with dependency injection so let's go ahead and do that in the persistence project we're going to add the eye order read Service as a scope service in the persistent project and this takes care of configuring the dependency injection let's try to move another query into the I order read Service let's take a look at the get order summary query Handler this one is really straightforward it's just querying the order summaries and returning the one matching the ID it's already simple enough as it is if we try to move this into the I order read Service we're going to have a method that is returning an order summary instance and we're going to call it let's say get summary by ID async now the ID in this example is a good and if I go back to my get order summary query Handler we can replace this with an I order read Service and we're going to add I order read Service here we're going to rename this from Context to order read Service and this is going to become order read Service get summary by ID async and we pass it the order ID and in the implementation we're going to move our query right here so this is going to be return await and the query that we had earlier fetching the order summary by the ID this is how our order read Service looks like and our get order summary query Handler is again just a wrapper around the order service this feels pretty pointless because your query handlers become just simple wrappers around some sort of service that is fetching the data this is because there is pretty much no logic present in the queries themselves other than flowing exceptions in some situations if we introduce throwing some sort of exception when the order summary is null then this starts making a little bit more sense the actual value is that we are using mediator here and we have access to the behavior pipelines which we can use to introduce some cross-cutting concerns one of those could be introducing caching to specific queries with this approach introducing caching becomes pretty straightforward now I'm going to try to refactor this slightly we're going to make separate abstractions and this is going to be similar to the vertical slice architecture and the concept of vertical slices for example we could do something like this introduce an interface in the get order summary query folder let's say I get order summary this is the interface name and it's going to return the same order summary response as the previous method that we have in the I order read Service and let's just give it a method called execute async and we're going to give it the ID argument required to fetch the order summary now instead of using an eye order read Service we are using something like this the implementation of this would again live in the persistence project so let's say in the services folder we have a get order summary class it's going to be internal and sealed and it's going to implement the I get order summary interface so let's copy the implementation from the order read service so this is going to be our implementation and we have to make it asynchronous and we are just missing the application DB context dependency so let's inject this from The Constructor so this is our get order summary class and now our get order summary query Handler is going to depend on this instead of the order read Service and it's going to have just one method so get order summary so now we are not depending on the order read Service we are using a small service that is scoped to our query exactly to implement the required logic let me try to highlight a few benefits of this approach one is that you can make these small Services very specific to your query you you can decide which implementation you want to use so in this example we are going to use the application DB context or EF core directly in another example we may decide that it's more performant to use SQL and in another example we could decide that the most performant approach would be to use some sort of key Value Store like redis this is practical when you have a lot of queries in your application and you are facing the problem of the order read Service in this example having too many methods and becoming quite a big class with a lot of code inside which is difficult to maintain and it's difficult to know where specific method you want to call in a specific use case another symptom of this is that you're going to end up with methods that are called in a single place in your code base so it makes more sense to use a specific approach like creating some sort of abstraction in this case the I get order summary interface the downside of this approach with vertically sliced abstractions is that you're going to end up with a lot of small classes for every query you would have a specific service and a lot of classes to maintain if you want to know how to easily introduce caching using The Decorator pattern you can watch this video here let me know in the comments how you would implement the query side of the clean architecture and until next time stay awesome
Info
Channel: Milan Jovanović
Views: 9,242
Rating: undefined out of 5
Keywords: cqrs, cqrs vs saga, cqrs event sourcing, cqrs kafka, cqrs saga, cqrs query, cqrs query side, cqrs queries, cqrs read side, cqrs read model, cqrs query model, cqrs caching, cqrs query performance, cqrs clean architecture, clean architecture, cqrs pattern, cqrs microservices, cqrs performance, cqrs abstraction, yagni, tradeoffs, cqrs tradeoffs, cqrs .net, cqrs ddd, ddd, domain-driven design, cqrs domain model, cqrs read database, read database, read model, cqrs replication
Id: RgqCavV2cqQ
Channel Id: undefined
Length: 13min 18sec (798 seconds)
Published: Tue May 30 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.