"Clean Architecture" and indirection. No thanks.

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Entity framework core on the query side of cqrs or something else well this was the topic for a video posted by Milan javanovic and I had a member of my channel ask me for my thoughts on this topic in the video so I started watching it and then I caught myself talking out loud so then I thought well why don't I just record me watching the video and providing my thoughts so here we go thanks to event store for sponsoring this video event store DB is a new category of operational database built for event sourcing cqrs and event-driven microservices for more on events.dv check out the link in the description I also sent a tweet to Milan asking him if I could do this type of video which he said go ahead but just note that I'm not advocating this approach it was more of a how you could do this if you wanted to and I'll have a link in the description to a counterbalance video that he posted 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 Okay so it was mentioned clean architecture was mentioned in there but the query side of that I think really maybe that was just a mix up there or we're talking about clean architecture and cqrs on the query side and then talking about EF core or something else but also what she was mentioned in there was persistence ignorance so I think that's where the clean architecture part gets in of kind of abstracting some of some of this data Access First Take a look at the get order query Handler which is depending on the eye 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 all right let's stop right there I actually want to back up a couple slides here and show this is that I often see this where there's some interface that's being created and the purpose of exposing it injecting that is then you're kind of abstracting Entity framework core and making maybe it easier to test I understand that you are kind of hiding some implementation details of Entity framework but at the same time you're not because you're still exposing the DB set again I say if this is about testing you can test the F core that's not like a really to me a concern so I really see little value in having this I application DB context queries and 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 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 so I'm going to stop there as well because if that's confusing I think it's just a different understanding of why you're using Seeker s that's the purpose is so if you have your query side deciding hey I need to get closer to the data I'm not going to extract as much maybe I'm using EF core Dapper as the example I think that was mentioned you're doing it because it's a very specialized query that's the point on the command side you're likely if you are if you have some type of Rich domain model you may be using Aggregates and returning an aggregate route from a repository again Aggregates are about being a consistency boundary they're different concerns you don't need to return an aggregate route on the query side because you're likely over fetching data again if you're not making State changes you don't need the concerns of a consistency boundary that's why you're doing it on the command side they're just different purposes so is it confusing that on queries you may Access Data one way using different libraries different tooling then on the command side no I mean that's that's kind of the whole purpose 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 site we're going to start by okay so persistence ignorance is coming into this year 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 this into an interface and what we are 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 slight pet peeve I'm stopping here calling everything a service kills me 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 so it became extremely thin and what value does it serve at this point so we have get by ID async that was doing that actual call we just move that call that that Entity framework call somewhere else in a separate class now we're depending on this we just add it in Direction so I'm still lost on the value here the thing is is that that again I said this earlier this is specialized query that's the point of a query is to be specialized so let's get by ID async really is kind of generic in the naming because it's not really it's returning the order response this thing is the order of service is being called and it's returning exactly the response that this Handler needs to return so it's like if we were returning a more generic order type of object and then transforming that I still wouldn't be behind that either um but you could see that when that's actually when people start get into the well I got a map this object to this object to this object from layer to layer to layer that's why we get into this place is because we have this indirection there's pretty much nothing inside if we also get rid of the dependency on the eye application DB context this is what we are left with now it would 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 null 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 I'm going to jump over there with this part I remember it I think yeah I think the this exception and throwing in this case and returning null is a whole different design discussion then kind of this video was about so that's kind of a side tangent so let's see make this okay so we rename read Service sorry I skipped a little too far we renamed the order service to the 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 Border read Service implementation so order re-service is going to be an internal and sealed class and we're going to make it implement the eye 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 okay so basically what happened here is the query as we expect got moved here we're not depending on the interface anymore we're just depending on the DB context directly and I'm kind of following her along here is that's now because we're actually in the persistence project so we really don't need that interface we actually have the DB context here hence the party build clean architecture so I'm following but I still don't see the value 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 we dependency injection so let's go ahead and do that in the persistence project we're going to add the eye persistent 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 all so this is really interesting here because what's happening is instead of having again A specialized query in this example it's actually just returning the actual underlying data model from the DB context so this order summary is what is in the DB set that's actually what's being used in our context and that's what actually is what's being returned here so there is no transformation we're just basically creating a path directly to our database and exposing ultimately kind of our our schema our implementation details of our data already simple enough as it is if we tried which I don't recommend to move this into the I order read Service we're going to have a method so as the example the order response that was completely different it was kind of creating with link a select a projection to return something specialized but this one is not we're just actually under returning the underlying data directly 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 so an interesting note about this as well is I mean this get you can get my feeling already but removing the cancellation token kind of me to me is like the indicator of well what's really happening here we have the cancellation cancellation token inbound from the actual HTTP request and then we're just offing it for some reason when you really wouldn't want to be because you want to be actually passing that to that database call but we're removing it from this API again if you had to pass that that kids like cancellation token deeper within layers deeper in Direction here it's kind of the indicator of like oh this this feels uncomfortable and it should be it should be the indicator 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 I appreciate that I was in knowledge that this seems kind of useless because it is unless you're adding more things to either the Handler but checking the response or doing something with like because this is used meteor and having some type of pipeline for your request where you're going to be doing other things um let's continue 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 I order read service so where this is getting broken down now instead we're going to have an interface that has execute rather than having the I order read Service that had multiple different methods that need to be implemented for each different use case which is again A specialized query it's a use case now we're going to go and interface per I don't know where who said it first but I keep repeating it which is if you have a function if you have a class with one method or if you have have an interface with one method you have a function so what we're about to do right now is take an I request Handler that essentially is a function that has one method to handle and now we're also going to have an interface that's going to have one method this execute async 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 auto read Service we are using a small service that is scoped to our query exactly to implement the required logic let me try so I stop here again so the idea being okay we moved it now into its own separate class the value of there is you had this Pro likelihood you would end up with the original I order read Service it's implementation as well would likely end up very large because again you're creating specific specialized use cases that's the point um but again I go back to this is what this thing's going to look like if you needed to add something else to it some type of cross-cutting concern I often find that you're likely not going to be doing it for everything it's going to be individual use case of maybe this particular Handler which you may do actually here um a part of the handle some other different way caching always gets brought up it got brought up here I think caching's a really poor example generally to use because caching is not easy at all and it's not just like oh we'll just throw a cache on something there's a lot of implications with it however I get cross-cutting concerns are a thing I don't think caching is one of them but the idea here is that you just we have this thin query Handler that's calling some other Handler and if I just I'm we're adding indirection right now and there's actually like I I'm a loss on the purpose of the actual value here I'm I believe the value or what's being presented or the ideas of how this could work is because we want to get persistence out of these handlers that's kind of the motif here is we want to move persistence that persistence project so that's why we're kind of moving everything there but in doing so you're just adding indirection for what purpose so that you don't have persistence directly within this Handler it's exactly going to be a one to one to one almost try to highlight a few benefits of this approach one is that you can make these small Services very specific to your query 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 become so I'm fully on board with what he's saying is that you're tailoring your use case to how you actually want to do it whether it's for performance reasons there's a magnitude of reasons why you may do it I'm just still can't get behind of why adding the interaction I'm in quite a big class with a lot of cold inside which is difficult to maintain and is 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 all right so that's the end of it what are my total thoughts here so here's the overall gist that I got from the video and what my thoughts are so I think the point of it really clean architecture plays an important role of what was trying to be accomplished because using mediator having a query and a query Handler the idea was okay well then how if I'm using applying clean architecture do I remove access from that data directly so at first it was saying okay well we're going to have that I application DB context and because I'm assuming because you're still slightly exposing EF core and persistence we want to remove that persistence completely we don't want to be using that interface we want to remove something separately so that we can expose some other interface that's going to really encapsulate that data access and everything about it the two ways that were mentioned we're just kind of creating that read Service which it was also mentioned was just going to get polluted by all these specialized queries so instead was then creating a separate service service interface and implementation again per query type which then you would end up in a mapping of a one to one to one I see little value in this I see little value in trying to abstract data access of a specialized query because that's what the query is trying to do generally I say this because if you're concerned about abstracting it because you don't want to depend on it and you don't want to be coupled to it the key word there is coupling how coupled in terms of usages are you if you're if you're coupled to say some particular DB context and you have like hundreds of usages because you have how many different DB sets in it that's actually the problem not that you have if you have for example a dozen usages of a of a DB context that you have and you wanted to change what your persistence is you could go ahead and change it it's not like you have a thousand usages having all that coupling is the problem trying to for me trying to abstract these usages and adding all this indirection I really don't know the value of it to me it's more focusing on coupling how you're using these libraries and Frameworks how you're coupled to them rather than to abstract them but still have a thousand usages that really doesn't solve the problem so overall for me it's just adding indirection there's little value I think it on the query side on the command side that's a different story I was using repositories fetching on an aggregate sure on the query side again it's specialized it's you whether it be for performance reasons over fetching data there's a lot of reasons but again it's a specialized use case that's the point of the query making it more difficult if you add more layers add more mappings you're kind of defeating the purpose there if you enjoyed this style of video let me know in the comments I've done similar types of things before but they're usually around blogs I've done one recently on Amazon Prime video and serverless McDonald's event driven architecture Wix micro Services things like that I enjoy doing them if you want to get access to a private Discord server where you can talk with other developers about these topics or provide a suggestion like this because that's where it came from you can join my channel get access to that private Discord server links in the description on how to join if you enjoyed this video please give it a thumbs up if you have any other thoughts or questions make sure to leave a comment and please subscribe for more videos on software architecture and design thanks
Info
Channel: CodeOpinion
Views: 37,414
Rating: undefined out of 5
Keywords: software architecture, software design, cqrs, event sourcing, design patterns, software architect, programming, .net, .net core, asp.net, asp.net core, soa, microservices, message queues, kafka, event bus, event driven architecture, azure service bus, rabbitmq, distributed transactions, service bus, mass transit, nservicebus, message queue, message queuing, messaging patterns, service oriented architecture, microservice architecture, domain-driven design, enterprise service bus
Id: _rgH0Kb9Bis
Channel Id: undefined
Length: 25min 5sec (1505 seconds)
Published: Thu Jun 15 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.