"Clean Architecture" and indirection. No thanks.

2023 ж. 14 Мау.
41 597 Рет қаралды

Entity Framework Core on the Query Side of CQRS... Or Something Else? Well, a video was posted on this topic, and a member of my channel asked me my thoughts. I started watching it, and I realized I was talking out loud. So instead, I decided to record my thoughts and provide feedback. So here we go.
🔗 EventStoreDB
eventsto.re/codeopinion
🔔 Subscribe: / @codeopinion
💥 Join this channel to get access to a private Discord Server and any source code in my videos.
🔥 Join via Patreon
/ codeopinion
✔️ Join via KZhead
/ @codeopinion
📝 Blog: codeopinion.com
👋 Twitter: / codeopinion
✨ LinkedIn: / dcomartin
📧 Weekly Updates: mailchi.mp/63c7a0b3ff38/codeo...
Original Video by Milan: • EF Core In The CQRS Qu...
Follow Up: • Why You Don't Need To ...

Пікірлер
  • I appreciate your commentary and review Derek! I think there's much to learn from it - mainly how "silly" the whole idea is. CA purists will still advocate for this approach, so that's why I decided to show it. But as I said, it's not my preferred way of doing things.

    @MilanJovanovicTech@MilanJovanovicTech10 ай бұрын
    • So why you create content on something you don’t recommend? Maybe changing the title to “how it shouldn’t be done” will be enough - idk

      @adrian_franczak@adrian_franczak10 ай бұрын
    • @@adrian_franczak Even if you don't like a tech/paradigm/etc you should understand it and what the tradeoffs are. You might decide you like parts, but not to the extent they take it, or it may just solidify you opinion that the tradeoffs of your tech stack make more sense for you.

      @adambickford8720@adambickford872010 ай бұрын
    • @@adambickford8720 I know that that’s why I’m reading a lot of stuff from different points of view but I wouldn’t promote topic where I don’t agree with something

      @adrian_franczak@adrian_franczak10 ай бұрын
    • @@adrian_franczak I don't see anything wrong with discussing implementations. For someone else, this is a great approach. It's my freedom to talk about anything on the channel - even things I don't agree with.

      @MilanJovanovicTech@MilanJovanovicTech10 ай бұрын
    • @@adrian_franczak It would make you a better engineer. One of the best ways to make sure you *actually* understand something is to teach it in good faith. Use Cunningham's Law to your advantage.

      @adambickford8720@adambickford872010 ай бұрын
  • I'm regularly astounded by the amount of ceremony that is present in enterprise programming.

    @pillmuncher67@pillmuncher6710 ай бұрын
    • I think the trap developers fall into is applying patterns/principles without context and understanding why those patterns/practices exist. And the not using their context in that decision. eg, I can't have data access here because it can't live in this layer.

      @CodeOpinion@CodeOpinion10 ай бұрын
    • @@CodeOpinionExactly. That is my opinion as well. I’m currently in a project that has a service layer with classes just to group stuff.

      @marna_li@marna_li10 ай бұрын
    • @@marna_li like a facade pattern or for some other reason?

      @KyleSavant@KyleSavant10 ай бұрын
    • @@KyleSavant It is where business logic is supposed to be put. To separate it from the presentation logic. Controller calling the services. Putting stuff in services is quite arbitrary. It is not like the services really have any meaning. They are more like modules, in something resembling feature folders. I wouldn't structure my project that way. Services, if they should exist, should have purpose. I'd rather have commands and handlers.

      @marna_li@marna_li10 ай бұрын
    • @@marna_li I’m in a similar situation. I misunderstood your first comment. The organization I work for has massive classes called “Managers”. It is just a dumping ground for methods that vaguely do work around some conceptual noun (ex. - PersonManager). There could be many methods in this manager, many with different dependencies. Imagine how big this constructor is and how coupled it becomes to unneeded classes. It just makes my skin crawl every time I need to work with it.

      @KyleSavant@KyleSavant10 ай бұрын
  • The main issue here is that a lot of developers bought the idea that you can decouple things that are naturally (or semantically) coupled.

    @leonardomangano6861@leonardomangano686110 ай бұрын
    • Exactly!. Worse still is the indirection, masquerading as "decoupling" , that results in the hidden coupling of things that are naturally not-coupled.* * (see Milan's in process Domain event video where separate aggregates are ultimately saved in the same transaction).

      @vincentcifello4435@vincentcifello443510 ай бұрын
    • @@vincentcifello4435 You are right bro

      @leonardomangano6861@leonardomangano686110 ай бұрын
  • Yeah. You are not eliminating coupling, just moving it somewhere else. It doesn’t matter if it lives in a separate assembly. That will also make stuff messier. I’d use EF directly in request handlers within vertical slices. Only extracting stuff to classes when it makes sense to.

    @marna_li@marna_li10 ай бұрын
  • You should run a code review series where people show their approach, and you point out the good and the bad.

    @Fafix666@Fafix66610 ай бұрын
    • Good suggestion!

      @CodeOpinion@CodeOpinion10 ай бұрын
  • I would love some code review videos. Also I'd love to see how you'd fix this problem

    @superpcstation@superpcstation10 ай бұрын
    • Just leave it the way it was at the start?

      @CoreCommander2@CoreCommander210 ай бұрын
    • This channel barely does that

      @ProtectedClassTest@ProtectedClassTest10 ай бұрын
  • I appreciate the "code review" styled video as the stacked opinions help to think about a problem while taking a couple steps back, opposed to just the direct "information absortion" mode we fall into when watching a video. A part of me relates to Milan because I have a fear of doing things "wrong", both for my perception of the code and to mitigate future (unknown) issues. Another part of me relates to Derek because experience tells me I tend to over-engineer projects in a way that rarely benefit anyone in the real world. I think specific architecture patterns or concepts are an easy way to get started and build acceptable solutions to a problem, but ultimately there's much more basic underlying concepts that take experience to be confident enough to use as the main guides in our decision making.

    @leuquim@leuquim10 ай бұрын
    • I think the trap is applying patterns/principles/etc as rules without understanding the underlying problems they are trying to address. Then taking that underlying problem and understanding in your context if it's valuable. There are always trade-offs and you need to be able to evaluate them. I alluded to that more-so at the very end.

      @CodeOpinion@CodeOpinion10 ай бұрын
  • Depending on such abstraction decouples you from your persistence provider(in this case EF Core). Application no longer has to reference EF Core. Moreover it enables you to create several infrastructures(if needed) and easily change it in the future. One of the reasons why teams stick to their ORM provider despite improvements in others is because they neglect this step. The same teams also can't use different ORMs for reads and writes for the same reason. Dapper for queries and EF Core for commands is worth considering.

    @andreiguzovski7774@andreiguzovski777410 ай бұрын
  • SingleOrDefault doesn’t throw an exception if there isn’t a result.

    @MiningForPies@MiningForPies10 ай бұрын
    • Yes. If there are more than 1 it does

      @sanjayidpuganti@sanjayidpuganti10 ай бұрын
    • @@sanjayidpuganti exactly, so he’s broken the original intent of method.

      @MiningForPies@MiningForPies10 ай бұрын
    • exacly!

      @adrian_franczak@adrian_franczak10 ай бұрын
    • The behavior of SingleOrDefault can be summarized as follows: If the sequence contains exactly one element, that element is returned. If the sequence is empty (contains no elements), the default value for the element type is returned. If the sequence contains more than one element, an InvalidOperationException is thrown.

      @atechdude@atechdude10 ай бұрын
  • I love this video and your comments. We talked about this kind of stuff 5 months ago: How can we share queries? How can we abstract the data access for unit tests? We decided to not share queries at all, instead depend on the DbContext (as you said there is no value in using an IDbContext where DbSets are exposed), because each query is so different and the result is mapped to it's own purpose. For the command-side, we decided to use DataAccessors (how we call them, doesn't really matter) and they can be easily mocked. They are also never shared between different command handlers. Thanks you your useful thoughts and videos!

    @alexanderpabinger3960@alexanderpabinger39606 ай бұрын
  • Enterprise code is different. When a project lives many years and has multiple 'generations' of devs, you do things that are 'dumb' otherwise. It's easier to find corporate drone devs that can recognize and churn out more dumb patterns than devs who will grok and truly 'own' the app.

    @adambickford8720@adambickford872010 ай бұрын
    • This was what I was thinking too. There are things I do that the value is in trying to guide future devs towards better code that I know if I let them to their own devices they would create crap code.

      @evancombs5159@evancombs515910 ай бұрын
    • This comment is spot on in my experience. I work somewhere where it is common to have a large codebase existing for 2 decades. I've used this "query object" pattern on a few projects over the past 5 years and they've been the easiest apps to maintain and add cross cutting concerns to even though much of the code is redumbdant. It's kind of like N-Tier chopped down to its bare minimum along with favoring SRP over DRY. It's overkill for very small projects but it actually helps coding momentum in medium-sized+ projects where there are a lot of people coding but you are not sure where all the data access and cross cutting concerns are going to land.

      @6stringmonk@6stringmonk10 ай бұрын
    • this comment is gold, after two years working in a project with multiple devs i think this architecture allow us to do things quickly and feel realy natural any change. It improve the development cicle.

      @carlosbaldwin6335@carlosbaldwin63358 ай бұрын
  • The guy in the video what you reviewed almost reached perfection IMO. As he said he refactored the code to be more vertically sliced but exposing some of the entity framework defeated the purpose, but I think he wanted to keep it a somewhat more appealing length. If he would have transformed the result from the db queries into some domain specific object that would have made a true separation. With true separation you can switch out the whole persistence layer without disturbing the domain layer. If you go the extra mile and actually implement a strategy pattern you can mix persistence layer solutions and still providing a unified interface towards the domain layer.

    @banatibor83@banatibor8310 ай бұрын
  • 7:15 An OrderService should return Orders, because its domain logic. It should not return something that can be sent out. That's what the handler should handle! 7:40: Exceptions are not made for control flow. Validate your id before doing anything! 18:00: Splitting logic into seperated interfaces for each already seperated handler adds complexity, but no value. To really reach seperation of database/persistence from usecases: In the Handlers, call a domain service. The domain service uses a repository, which is implemented in the persistence layer and gets the DBContext injected. Result: Domain service can be reused in several different handlers to get, create, update delete your domain objects. Service makes sure, everything is valid/consistent. Repository takes care of persistence and uses EFCore or whatever to achieve that.

    @dagochen@dagochen10 ай бұрын
    • You are confusing a domain service with a application service. Domain services does not use repositories. In fact anything related to domain doesn't.

      @gbroton@gbroton6 ай бұрын
  • I love these style of videos, as a mid ish level software engineer my next steps to improving are self reflection on the methods and tools I've picked up over the last couple of years.

    @nickolaki@nickolaki10 ай бұрын
  • Appreciate this kind of video to show your different opinion and thoughts. :) I try to apply CA (without CQRS) and I did put query into repository layer to keep everything fit into CA as possible. But I didn't put one query into one class. Good to know CQRS and different opinions.

    @sj82516@sj8251610 ай бұрын
    • That’s good approach but the problem is you can still have CQRS and you don’t need these query handlers you can inject your repositories directly!

      @sergeykichuk2586@sergeykichuk258617 күн бұрын
  • This comment is in regards to a generic repo; when I create a generic repo. My "get by id" usual looks like this => Task getFirstOrDefault(Expression filter); I have a few like methods like that I use so I understand where he's coming from.

    @phantastik3389@phantastik33897 ай бұрын
  • I really like this style of video. The review provides another point of view that helps me think of the nuances in the various approaches to application design and implementation. I would love to see more of these.

    @darylolson5089@darylolson508910 ай бұрын
  • I think it's not really about the abstraction but limiting how and where you can implement things, it means that there is less chance of you messing something up (the intern problem) if you use the current architecture layout. Especially is such large systems. It's a little more work, but you get used to where everything is, so moving things out of the way isn't really a problem.

    @jwbonnett@jwbonnett10 ай бұрын
  • Let me put a handler in your handler, so I can handle the requirements your handler should handle, but wait there's more! I've hidden your DbContext behind an interface that is just a copypasta of the DbContext itself. And why? I have no idea really.

    @nyonyo3553@nyonyo355310 ай бұрын
    • Because they don't want to couple directly to the DbContext but rather an interface they own that lives in the Infrastructure layer. But you're still coupled to it because you're exposing the DbSet. I also don't get it.

      @CodeOpinion@CodeOpinion10 ай бұрын
    • Cargo cult programming.

      @markovcd@markovcd10 ай бұрын
    • Exactly like exposing Iqueryable to be able to do .Include

      @dariogriffo@dariogriffo10 ай бұрын
    • @@CodeOpinion The IApplicationContext should actually use IDbSet and not DbSet. I agree with that assessment. Only the EF Contracts should be a application layer dependency, not EFCore proper.

      @pilotboba@pilotboba10 ай бұрын
  • 10:15 The EF SingleOrDefaultAsync does not throw an exception if there are no records. It returns null, thus the "Default". When looking for a specific ID, it is best use to SingleOrDefault vs FirstOrDefault. If there are multiple rows with the same ID, FirstOrDefault will allow the operation to be performed, which can cause data issues.;

    @rhtservicestech@rhtservicestech10 ай бұрын
    • Well, I assume everyone has constraint on ID field (since "ID" stands for "identity"), so this case is a bit exotic, but you're right that "Single..." should be used just because it express your intentions better.

      @proosee@proosee9 ай бұрын
  • I really enjoy your take on things, and this type of video. I feel like the Milan's video that was pretty balanced at least in offering different options, and explaining some of the tradeoffs involved in the approaches although I don't believe it answered your question of "what does this buy you?". Also as Milan states below it is a lot of what people might see in the wild, wether or not its a preferred approach. To extend your thoughts a little further, I do see mediator or frameworks like it used often in web applications and treated like an entry point when typically the actually endpoint is the entry point in those situations I have the same thought. What is mediator actually adding besides indirection? To be fair I used to program like this until I realized as you said the single member interfaces are often simply a function. The purpose of many applications is to handle specific use cases, and its not often that we need to have more than one way to handle them at runtime. This reminds me of the memes about jr / mid / sr level engineers where the jr does something simple to solve the problem as they become more experienced (mid level) they make more complex solutions, and then after gaining more experience (sr level) they regress back to the simple solutions with the understanding of why simple solutions are often best.

    @awright18@awright1810 ай бұрын
    • What's the value of mediatr type libraries? Depends where it's being used. It some places it might be useless, others it can be helpful. For example, if you were using aspnet core and you specifically want to remove aspnet core dependencies from your code. Meaning, are you creating a web application or an application. It's used over an integration boundary. So assuming you don't put anything aspnet related in your request objects. Meaning, you're using it at the outer most I/O boundary that you might not control and convert it to something you do control. Let's say you have a request/handler that is used in a a controller as the entry point. You also have another entry point that lets say has to periodically pull data from an external system, translate it and call that same request/handler. In both situations you're taking the top level entry point and translating that in your application request. Can you do this without Mediater, absolutely, it just hides the execution, which then adds things like behaviors/piplines etc. If you're not doing any of that, does it provide you with any value?

      @CodeOpinion@CodeOpinion10 ай бұрын
  • I really don't get why some devs insist on using MediatR. To me it offers almost nothing since seperation can be acheived easily without it. Maybe if I was dispatching commands to a handler which drops a message onto a message bus? If it's just seperating controller layers from DB/Service layer then I don't see the point.

    @edgeofsanitysevensix@edgeofsanitysevensix10 ай бұрын
    • This is not the main idea of this tool.... What about pipeline behavior? I personally find that feature very powerful and I believe it is the main purpose of the library and not that API/Application separation.

      @yardeZ93@yardeZ9310 ай бұрын
    • @@yardeZ93 Well fine. But I do see it used 'because its cool'

      @edgeofsanitysevensix@edgeofsanitysevensix10 ай бұрын
  • Great video! For me it's about decoupling the query handler from the storage layer and technology. We're using EF SQL today, but in six months we may use CosmosDB (especially when we consider the polyglot nature of CQRS projections). Having non-leaky injected interfaces implemented in their own assemblies allows us to do this without altering UI or logic layers. Since these interfaces should not leak technology types, vocabulary or concepts, we should make them non-persistent in nature. EF supports state, other storage layer technology does not.

    @madfish1973@madfish197310 ай бұрын
    • I'm a huge fan of my EF models being internal to the storage assembly, enforcing clean tech agnostic interfaces.

      @madfish1973@madfish197310 ай бұрын
  • It’s great to see different point of view; we can learn more on this long pathway. I will take both opinions.

    @adiazwise@adiazwise7 ай бұрын
  • I think we can move the handlers of the read side to another project instead of developing a new abstraction layer In the project, we can use many ways to read data quickly and easy to test. Additionally, application project, not dependency persistence library

    @lexuantruong3835@lexuantruong383510 ай бұрын
  • Thx for this video derek, it's a good point of view that some future engineers can have when you are learning. I would like to see more of this content.

    @emanuelrodriguez3155@emanuelrodriguez315510 ай бұрын
  • Or I could just implement the query handler where my DbContext is to avoid that silly leaky interface business altogether.

    @hektonian@hektonian10 ай бұрын
  • 16:24 if you have a class (or interface) with one method, you have a function. I think you just described MediatR lol. Thought it’s more like a closure, as the constructor closes over the variables used in the Handler.

    @br3nto@br3nto10 ай бұрын
  • Great code review and I fully agree on your points. If you want to use Clean Architecture pattern and hide the persistance layer's implementation details then do it properly. In this example EF Core's DbApplicationContext is exposed as-is and the caller in application layer can misuse it freely (just cast the result to DbApplicationContext and off you go). Also greate insight on dropping out the cancellation token without a reason to keep the code nicer. I think the root cause may rely in the original Clean Architecture code example where the author did just this. I don't know why EF Core dependency was added all the way to domain layer, but this idea has been copied by many without understanding the consequences. I have used mostly Dapper with repository pattern and then you really need to think about the layer separation when applying clean architecture - you need to actually fetch or store the data before returning the response.

    @Greenthum6@Greenthum610 ай бұрын
    • "If you want to use Clean Architecture pattern and hide the persistence layer's implementation details then do it properly." What's properly? That's the $100 question here. " I don't know why EF Core dependency was added all the way to domain layer," I don't think this was done. What are you talking about? Are you talking about the IApplicationContect interface created in the application layer? That can be done by only depending on EFCore contracts.

      @pilotboba@pilotboba10 ай бұрын
    • @pilotboba Referring to Jason Taylor's "clean architecture solution template": You are right, EF was not added into the domain layer there but the application layer instead. IApplicationContext has IDbSet, which is a hard EF dependency.

      @Greenthum6@Greenthum610 ай бұрын
    • @@Greenthum6 I also think Jason addresses this when he talks about it and it is a pragmatic decision he made. I generally agree with him. The overhead of wrapping the ef interfaces so you can remove a soft dependency on a contracts project is not worth the time, complexity, and extra layers. I think any clean architecture templates out there are guidance and not hard and fast rules. Use what you like, throw away or modify the rest. That's how I approach it.

      @pilotboba@pilotboba10 ай бұрын
  • So in a nutshell instead wrapping each query into specific service/abstraction would be ok that in each Meadiator query handler to write query directly as simple as possible using a ligther tool the ef as Dapper? Thank you!

    @thedacian123@thedacian12310 ай бұрын
  • Great video! I would have liked to see as side-by-side of what would be more favorable and meaningful. If the takeaway was to "leave it as is", does it imply there was no problem at all initially? If not, how do we identify when there is a problem and when there isn't? Yes. The details about coupling was a step in the right direction, but I need a "to do" and "not to do" side-by-side for it to sink in.

    @brandon9247@brandon92476 ай бұрын
  • Its for test faking! Not that I agree with it necessarily, but i do miss it sometimes instead of using in-memory database for tests. Would öove to hear your comments

    @llindeberg@llindeberg10 ай бұрын
    • Seconded. Can we see an example of unit testing the logic in the handler without using this?

      @anthonymorgan2001@anthonymorgan200110 ай бұрын
    • Yeah I also generally wrap the efcore dbcontext in my own wrapper class because it's impossible to fake itself. What I mostly find disturbing in the example is the sheer amount of interfaces. It's so useless to make an interface type for single implementations. Most of the time when I ask people why they do it the answer is because they want to mock it. But why would you ever want to fake more then the external infrastructure? Sociable tests are a thing, and in my experience worth the tradeoff of having multiple tests fail if you break your own code. Since I started doing TDD and sociable tests the amount of interface types I use has dropped significantly.

      @Timelog88@Timelog8810 ай бұрын
    • ​@@Timelog88 To take it to the extreme, one could do with only e2e-integrationtests I guess. But unit tests with as many dependencies mocked as possible isolates the error for quicker debugging. There's an extreme of that side too, though, mock C#/.NET? But I see your point, tradeoff worth considering. I'm sad Microsoft are hesitant to recommend/expose it.

      @llindeberg@llindeberg10 ай бұрын
    • @@llindeberg Well personally I stopped using mocks entirely. I just do pure state based tests that test behavior and abstract away all implementation details from the tests feasible. The way I do that is a fairly novel way of testing introduced by James Shore called Nullable Infrastructure. This means that I limit the use of layers in my code, and the amount of dependencies in my code. The tradeoff? Test code in my production code. But as a counter argument to that, Microsoft does it as well, fe. NullLogger, NullStream and UrlTestEncoder. Main difference is that I use Embedded Stubs.

      @Timelog88@Timelog8810 ай бұрын
  • How do you handle scenarios where you want to reuse a Query? The way I pick was to move the code to a service and used this service in both Queries but I started without one first. One other solution could be to orchestrate everything in the Controller but then you cant return Viewmodels from your queries.

    @CryptoWulf_app@CryptoWulf_app9 ай бұрын
  • "Calling everething as service killing me" YES YES YES YES!!!!!!!!!!!!!!

    @pispis3617@pispis36178 ай бұрын
  • I like this video review. I'm a big advocate of using EFCore/Dapper/etc directly in the command handler directly as opposed to abstracting it into some IRepository class (some argue for testing purposes). If you are truly worried about unit/integration testing, EF Core provides an In-Memory database that you could use in your test project. At the end of the day its all about exercising pragmatism for various use cases.

    @dii2@dii210 ай бұрын
    • That's cool, EF really has it all it seems

      @irvingceron1016@irvingceron101610 ай бұрын
    • Don't use EF's in-memory database for testing. Use the real database engine to make sure your queries really work.

      @Greenthum6@Greenthum610 ай бұрын
    • It should be noted that Microsoft explicitly discourages testing using the EF Core In-Memory database. From the docs: "While some users use the in-memory database for testing, this is discouraged."

      @davideastman5806@davideastman580610 ай бұрын
    • @David Eastman Oh lol, why do people do it then?

      @irvingceron1016@irvingceron101610 ай бұрын
    • ​@@davideastman5806 Yes, using inmemory database is not recommended. But, people add indirection by arguing that it's for testing, to mock.. Using inmemory, this argument no longer makes sense.

      @juniorzucareli@juniorzucareli10 ай бұрын
  • This sort of indirection can make sense if we also want to have different compilation units for application logic and database access. CA on steroids is Diamond Architecture, where the core of the project doesn't depend on any infrastructure lib. In CQRS, you'd want commands and queries (as classes) to be next to each other so it's easy to see all features/capabilities of the system. Still all repository implementation along with 'xyzQueryServiceImpl' would go to a DB project. Why? Because some languages are slow to compile (scala, rust) and a larger monolith's compile time can be sped up by this approach

    @szotsmiklos8549@szotsmiklos85499 ай бұрын
  • 11:41 this is probably the worst offends, but SO common: adding all these layers, but with leaky abstractions everywhere. The result is tightly coupled code spread across multiple classes and assembles.

    @44Bigs@44Bigs7 ай бұрын
  • Early on, I enjoyed adding patterns whenever I could to a project that I was working on, but as time went by I prefer my codebase to be as lean as it can be and only add abstractions when I truly need it. The CA pattern is a consistent way to implement a project, but I feel that it has this underlying assumption that a layer might be swapped out in the future. I understand the persistence layer "might" get swapped, but i'm on the fence about the application layer, do we really need to have a dedicated "Application" and "Web" or "Api" layer? In most cases an Api project will always be an Api project, so why not just define whatever is in the Application layer in the Api layer.

    @darylclarino5439@darylclarino543910 ай бұрын
    • It's not about swapping out in the future, it's about coupling to abstractions rather than implementations and having a direction of dependencies. Where things go wrong is when people take a template or a formula for doing that and not taking their context into consideration.

      @CodeOpinion@CodeOpinion10 ай бұрын
    • @@CodeOpinion i see what you mean, and i agree sometimes people just use the patterns for the sake of using them

      @darylclarino5439@darylclarino543910 ай бұрын
  • There is definitely value to it. Consider the Order Query requires to combine results from multiple queries to produce the final result, your design is open to introduce that glue in thin query handler while keeping the query details in the respective Service class. You won't see value in vanilla use cases but this helps in complicated enterprise applications where data needs to be assembled from multiple queries and sources. In real world, scenarios can begin vanilla and get complicated as business evolves. So following something like this helps.

    @zfold4702@zfold470210 ай бұрын
  • I got a bad feeling when I see this abstractions, especially when we try to abstract the DataSource with Services/Repository patterns. I understand you want to have only one way to writing/structure your code base inside the applications so use the Mediator even though its just a thin layer for some cases but can serve as a agreggator for others its ok in small applications or modules. Still would simplify your life by making a vertical arquiteture and only abstract your outboundaries .

    @luizfernandopereira5120@luizfernandopereira512010 ай бұрын
  • Great video. I'm struggling with the same indirection thoughts.

    @13odman@13odman10 ай бұрын
  • Ain't quite sure if I'm properly dressed to witness such code formalism :) btw, looking forward to the day LOPJB (Layered-Oriented Programming Just Because) will no longer be so appealing for newbies/(almost) mid engineers. Lately in my job we released a dumb service that just acts as a CMS offload to offer some lower latency for two app-specific queries. Guess which "architecture' one guy from my team just started applying to it (just because)?

    @buarki_@buarki_10 ай бұрын
    • Did you just make up LOPJB? Either way, I'm using it and will give credit.

      @CodeOpinion@CodeOpinion10 ай бұрын
    • @@CodeOpinion lmao yes, it just popped up in my head here, feel free to spread the word against LOPJB

      @buarki_@buarki_10 ай бұрын
  • The levels of indirection I've seen over the years is really starting to bug me. It's got to a point now where I even see MediatR as unnecessary in some use cases and it's just easier to have a single class for the endpoint itself rather then having any kind of indirection. (I say endpoint, can be using FastEndpoints, controllers, or any other kind of endpoint construct. As long as it's a class with a single method / purpose)

    @jamesmussett@jamesmussett10 ай бұрын
    • The Endpoints are a UI/Presentation layer. How does the endpoint query the data? Are you coding to an implementation in the endpoint? What if you had a second Presentation layer, say a graphql server. You would have to write the query all over again? I don't think there is a simple, single, correct answer her for every use case and application.

      @pilotboba@pilotboba10 ай бұрын
    • I don't use Mediator :)

      @atechdude@atechdude10 ай бұрын
    • @@pilotboba I don’t disagree with the fact that certain use cases will require a different approach. But as ti mentioned, it certainly doesn’t require libraries like MediatR. Thinking about all the use cases upfront certainly helps eliviate having to re-write everything later on, but I appreciate that requirements can always change. From my personal experience, endpoints are designed for specific scenarios in which the database queries are normally optimised and tailored for. Query Language APIs like graph QL will most likely have to have it’s own queries written for it anyway. Adding any kind of shared logic and indirection between then is more likely to cause issues in query performance and just add unnecessary complexity.

      @jamesmussett@jamesmussett10 ай бұрын
    • @ti Guess you didn't understand my "what if". I wasn't saying you are designing for the future, I am saying your current design INCLUDEs 2 presentation layers. The read model should be available to both presentation layers rather than implementing it twice. But, sure, if you know it will only ever be an API and you want to implement the read model in the endpoints, go for it. I am usually more pragmatic than dogmatic.

      @pilotboba@pilotboba10 ай бұрын
  • What do you think of moving query handlers to Persistence layer?

    @jedielson31@jedielson3110 ай бұрын
    • I don't think of layers in terms of clean architecture. I don't think of a "persistence layer". I think of how I handle persistence given a use case or a set of features. That could be in a transaction script if its CRUD, or maybe i have a repository if i'm using an aggregate. It depends on the context. As mentioned at the end of the video, a large factor is how much coupling is involved to the underlying data.

      @CodeOpinion@CodeOpinion10 ай бұрын
  • I always love to learn from more senior devs. Trust your elders and all that😂

    @PaulSebastianM@PaulSebastianM10 ай бұрын
  • Great video! I hope this will have more views than original one.

    @adrian_franczak@adrian_franczak10 ай бұрын
  • uuuhmm i had a project in blazor server, there is no intention to make a mobile app or some other kind of front end...so i have the dbcontext directly in the blazor component for fetching and saving stuff. I cant see how this is bad given the requirement of the project. If i will ever need to make some page render in web assembly mode i will move the methods in an api just for that page needing of client side performance. Maybe i'm not writing big enough projects...

    @ghevisartor6005@ghevisartor60055 ай бұрын
  • I really appreciate your videos Derek. I had a couple thoughts of my own here that I have wrestled with and want to get your opinion (or other people's here) on. I have made a habit of always hiding EF from my business layer (where my query and command objects live if I'm using CQRS). I do this because of 3 different types of experiences I've encountered that made me decide to do this. My main question is, am I overreacting from these experiences (I know that word overreact is subjective, sorry) 1. Poorly designed database schema where I want to hide the details of tables and queries out of the high level areas my app (the business layer). I've worked on many projects where the database was designed poorly or without foresight and we didn't want to take time re-designing it (and therefor performing data migrations at deployment time). I'm not just talking column name changes. I've had to work on projects where they stored two different types of entities in the same table that were similar but different and used in two completely different contexts. I wanted my domain to represent them with 2 different classes... not even having a base class in common cause they weren't ever reasoned with as similar entities... yet my EF model had to be the same cause they were the same table. I also needed 2 different sets of queries (use these where clauses to get records of these types but these other where clauses to get records of the other type). I really did not like seeing the complexity of "these are the same models... but oh wait, they're totally different." in my business logic. It was too low level for the business layer which I wanted to be higher level. 2. Storage location switch. I've had a couple experiences where we use to cache a set of data from an API in the database so we had the business level go directly to EF to get the data... then we decided to cache in redis... suddenly I found myself spending a whole month updating references and query operations to use a redis client instead which, if we had abstracted the querying out, it probably would have been half a day or at most a couple days. Worse, suppose we did do a table split like what I described above in the first example... depending on the complexity of the queries to that table just to differentiate between the 2 tables, that change could take a while and be very prone to missing references or updating some incorrectly. 3. Hiding EF specific operations (i.e tracking nuances). I've had lots of times where I've had to do some wierd logic in the EF context to deal with nuances in tracking and sometimes cachinng... I don't like putting code like "Add this entity, but check to see if a related entity is already tracked cause if not, you gotta attach that first... oh wait, i've already tracked a duplicate instance of that entity from another query so I gotta swap this instance out for that one". Logic like that doesn't belong in high level logic like the business layer in my opinion. For reasons like these, I've just made it a rule for my projects to always just hide EF completely so I don't even have to worry about those issues. I don't even allow myself to add a package reference to EF in my business assembly. Am I overreacting? How else could I address these issues if I allow EF to exist in my business layer?

    @iankirkpatrick2022@iankirkpatrick202210 ай бұрын
    • No, not overreacting. I can't express this enough, it all comes down to coupling. If you have specific data that's going to be used in a high number of places (likely queries), than you likely want to expose that behind some API that you can control. Writes are often limited to a select number of actual usages. If you're using an aggregate, maybe its only there. If you're using transaction scripts, maybe it's just a few places that perform a write. In those cases, abstracting your persistence has a very different value than the earlier mention of a query that has a high degree of coupling. It's about where you need control.

      @CodeOpinion@CodeOpinion10 ай бұрын
  • I don't get the idea of using MediatR just for cross-cutting concerns. You can just use decorators instead.

    @megaFINZ@megaFINZ10 ай бұрын
  • "Calling everything as service kills me" HAHAHA Indeed, this days you can name everything "service" and nobody will claim.

    @MrFreddao@MrFreddao7 ай бұрын
  • This was fire- love this style of video

    @mux____@mux____9 ай бұрын
  • 06:50 That's you explaining what a Poltergeist / Gypsy Wagon is. 👍

    @mabdullahsari@mabdullahsari10 ай бұрын
  • Hi, I'm 16 years old and I love microservices and ddd and I've been on some enterprise projects i really liked your review and you made some excellent points, I recorded some videos about microservices and architecture and I'll be happy if you review them and gave me your thoughts about it and say witch part I'm right or wrong ❤️

    @enterprise_programmers@enterprise_programmers10 ай бұрын
  • This style of videos are like code reviews. Really interesting and useful. @CodeOpinion, I would love seeing you making such videos regularly.

    @MrJonnis13@MrJonnis1310 ай бұрын
  • I definitely enjoyed this style of video. With regards to the content, there's nothing worse than being excited about a new project, you start abstracting out the different functions, and then realize that you've mapped one-to-one-...to-one all the way down to the database. That said, I think storing anything but a one-liner in a handler leads to bad places, but the amount of indirection applied to this specific project was too much. Once you start creating abstractions of functions (`ExecuteAsync` in this case), you've lost me.

    @Galakyllz@Galakyllz10 ай бұрын
  • We would really like to see a clean architecture from your point of view, in any language you would like to.

    @alexandru-mihailadam8798@alexandru-mihailadam879810 ай бұрын
    • Good suggestion.

      @CodeOpinion@CodeOpinion10 ай бұрын
  • I fully agree with you, but man is this an uphill battle in 90% of organisations. You’re doing the lords work!

    @asdqwe4427@asdqwe442710 ай бұрын
  • It would be great to see what your thoughts are on the alternative.

    @brakenthemole2377@brakenthemole237710 ай бұрын
  • Developers should generally start returning the result of e.g. queries as opposed to assigning them to a variable and then just returning the variable; guys, it's not adding any value assigning stuff to a variable and then on the following line, returning that particular line. Your method (that encapsulates the query or operation) should relay the intent of the contents of the method.

    @andersborum9267@andersborum926710 ай бұрын
  • Incredible mashup and discussion!!

    @Musician4231@Musician423110 ай бұрын
  • If you expose DbSets or IQueriable, you are doing something that is not Clean! Having the Infrastructure Layer on top of other layers is beneficial because you decouple your app logic from the infrastructure wiring code. If you add on top some bootstrap layer, your web application will be oblivious that it is a web app (e.g., you can extract your Controllers in a class library). And you can design your business logic like all the data you need is in memory. The result is code more in line with the OOP and SOLID principles. The idea is to design your app like your data is in memory. And your system to speak the business language. The idea of using Repositories is to use them to make queries through Aggregate Roots, but returning the whole Aggregate is unnecessary. This way, your Aggregate root can force all invariants. And you have a single flow point between different Aggregates, which leads to better (in my opinion) database schema design. The Entity from one Aggregate should not have a foreign key to the Entity from another Aggregate. These foreign keys are made through the Aggregate Roots. Bootstrap Layer -> ASP app Infrastructure Layer -> Persistence etc. (here, the Repositories are implemented) Web Layer -> Controllers and MediatR Application Layer -> CQRS, Repositories Interfaces Domain Layer -> SOLID OOP (Entities, Aggregates, etc.)

    @piromanaBG@piromanaBG10 ай бұрын
  • Can you make a video how to design testable code? Code that allows to write tests easily and those tests can run really quick in milliseconds?

    @MohamedCherifBOUCHELAGHEMdz23@MohamedCherifBOUCHELAGHEMdz2310 ай бұрын
  • Based on the article: “repository pattern is simple but yet misunderstood”. I’m tempted to let my web services (fastendpoints) directly use EF. I also want to completely avoid dtos and mappings. I’d play around with the serializer (eg: custom jsonconverter) to cut out unwanted data if linq select in anonymous types is not possible . What your thoughts?

    @maurosampietro9900@maurosampietro990010 ай бұрын
    • dto's and mappings give you an order to manage you data. When you work with a lot of people, you need to understand really fast things.

      @carlosbaldwin6335@carlosbaldwin63358 ай бұрын
  • Separated purpose WriteSide - ReadSide is not that hard. What about specyfic queries from Write-Side to accomplish use-case?

    @dariuszlenartowicz@dariuszlenartowicz10 ай бұрын
  • 4:20 Yeah this is why the onboarding process for a new colleague usually takes like half a year ffs! :) Nobody will understand the reasoning later on, especially if it is not written down. But obviously it will not be written down since the code should be self-explanatory, nobody's wiring code-related documents only high level ones.

    @temp50@temp509 ай бұрын
  • Trying to summarize your thoughts at the end to better understand: because you're already implementing a CQRS pattern each Read Model is specialized and adding indirection to the persistence layer isn't providing value because IF the underlying persistence implementation changes the specialized Read Model may be irrelevant and because it's specialized and will likely have limited usages? However on the command/write side it could be more valuable because we may need to query the aggregate root in order to validate or apply a state change which is a domain centric action instead of a specialized usage based on the persistence implementation?

    @colton2432@colton243210 ай бұрын
  • What confused me was his abstraction was still using the DB sets transforming them into the objects that you might want from a testability perspective this throws me a little bit because if you're wanting to not depend on the database for testing then you need to be able to create mocks somewhere

    @Veretax@Veretax10 ай бұрын
  • I am really DDD guy. So i would make handler which inject repository, validator and mapper. Firstly we validate a request then repository return desired entities and request handler maps it to response. Additionally request handler should handle all possible exception imo (or we can leave it in Filters, i dont know) NotFoundException is good but in Write operation. In Read operation it is completly expected to not found values.

    @Masteroxify@Masteroxify10 ай бұрын
    • DDD is full of bullshit

      @pickle1987@pickle198710 ай бұрын
  • I just use odata for query separations. I cant imagine how much time i've saved for a single-developer side job

    @SergijKoscejev@SergijKoscejev10 ай бұрын
  • Great video,, two guy I love to watch. Thank you all to creating good contents.

    @E_G_@E_G_8 ай бұрын
    • Thanks for watching!

      @CodeOpinion@CodeOpinion8 ай бұрын
  • Noone talks about how difficult it is to introduce transaction logic into this madness

    @gordonfreimann@gordonfreimann9 ай бұрын
    • it's really easy. Just call services and repositories inside handler's and that's it. Use strategy instead.

      @carlosbaldwin6335@carlosbaldwin63358 ай бұрын
  • Patterns can be awesome, but the practice of turning helpful patterns into boilerplate often ruins the utility of code; even the smallest task requires a survey of tens of files. If it is simple, keep it simple. I love CQRS because it really enables focus on what’s important. I’d also note that no style, whether it is CQRS or Clean, is going to save you from over coupling and low cohesion

    @leopoldodonnell1979@leopoldodonnell197910 ай бұрын
  • Indirection without a very clear purpose makes code so hard to follow. Especially when you're creating a new interface and injecting the implementation through a DI framework. You end up needing to run the code to figure out what concrete types everything resolves to. And if anyone needs another reason to argue against indirection for indirection's sake, keep in mind that there are performance penalties for it. Interfaces require vtable lookups and processors don't particularly like jumping around all over the place.

    @MarcusTheDorkus@MarcusTheDorkus10 ай бұрын
  • You shouldn't unit test with a concrete instance of DbContext. That's integration testing.

    @awmy3109@awmy310910 ай бұрын
  • You guys are on to something …. This content is very valuable

    @wallyhighsmith9005@wallyhighsmith900510 ай бұрын
  • It's like we forgot that our job is to bring value to people, not marvel at some fancy abstractions or design patterns.

    @toopkarcher@toopkarcher9 ай бұрын
  • Thank you both , i personally don't like commenting on others content, you could just place your opinion without personalization, coding is like writing a poet its not science, there are many ways to solve same problem, interface for persistent layer is a requirement for clean architecture , domain should not depend on persistent layer

    @mohammadtoficmohammad3594@mohammadtoficmohammad359410 ай бұрын
    • As long as you're not being disrespectful, why not commenting on others content? Derek has a lot of experience hence a lot to say. I love what he did here. I bet Milan and many others learned a lot from this video. It's a toxic mentality that commenting on others people is a bad thing. It's my pet peeve.

      @mihais2911@mihais291110 ай бұрын
    • @@mihais2911 judge me when you are perfect, and i am sure no one is perfect

      @mohammadtoficmohammad3594@mohammadtoficmohammad359410 ай бұрын
    • @@mohammadtoficmohammad3594 But you just judged Derek because he commented on Milan's work.

      @mihais2911@mihais291110 ай бұрын
  • At 13:10 is a bit confusing what you're saying about CancellationToken. Could you please confirm that what you meant is that it should really be passed on and not remove it? Because you seem to be saying that and then finish by saying that "it's kind of the indicator like oh it feel uncomfortable". Thanks!

    @mihais2911@mihais291110 ай бұрын
    • Yes, it should be getting passed through and used. What I mean is that it was removed, probably because it felt odd to have to keep passing it from layer to layer. And that "oddness" is the indicator that you have a lot of indirection. Removing it's usage is hiding the "oddness".

      @CodeOpinion@CodeOpinion10 ай бұрын
  • This style of video is great, but also your traditional way of doing it is. This style allows us viewers to get their thoughts verified heir on approaches out there on KZhead that do not really discuss the approach. The video you reviewed felt like a how to video but only your commentary added the discussion.

    @robotrabbit5817@robotrabbit581710 ай бұрын
  • Wow, this guy managed to take a single, concise query and spread it around the codebase like he's making a PB&J sandwich. This video really felt like reading the *EnterpriseQualityCoding/FizzBuzzEnterpriseEdition*

    @spicynoodle7419@spicynoodle741910 ай бұрын
    • The problem with this video is that he is creating a million classes for a million queries. Creating an abstraction over a single immutable constant. This is wrong. At work I use Eloquent ORM, it has one interface with different DB drivers and that is a way better architecture.

      @spicynoodle7419@spicynoodle741910 ай бұрын
  • What I've seen everywhere 1. Super convoluted - need a big team with specialists to maintain it 2. Build on top of bad technologies, thinking vanilla tech, eg. EcmaScript2020, is obsolete 3. Use the wrong methodologies (Agi$e) 4. Use the wrong architecture and the wrong paradigm (abstract and interface rules!) 5. Designed by incompetent with big mouth (cronyism) - using fast talk technique to seduce the moron leader in place 6. Let users design the features they want - letting developer build a Frankenstein monster

    @AlainTrepanier@AlainTrepanier10 ай бұрын
  • I think the example is lacking complexity. If the domain was more complex with the query handler needing to collect or modify the response based off some domain specific rules. Then creating some services to allow the query handler to become persistence ignorant would allow for a more focused query handler.

    @crazyfox55@crazyfox5510 ай бұрын
    • Question: If you're underlying data model was used in a dozen places, would you want to want the query handler to be persistent ignorant?

      @CodeOpinion@CodeOpinion10 ай бұрын
    • @CodeOpinion if the goal is DRY then maybe that is a good reason too.

      @crazyfox55@crazyfox5510 ай бұрын
  • Awesome video. Ok but seriously...there at the end...he has snake eyes, I swear. It was kind of mesmerizing.

    @ToadieBog@ToadieBog10 ай бұрын
    • me?

      @CodeOpinion@CodeOpinion10 ай бұрын
  • Clean Architecture is one of the biggest trojan horses. And there are people trying to justify it by any means. Incredible!

    @CesarDemi81@CesarDemi8110 ай бұрын
  • What a huge mess IMO. Too many abstractions and absolutely no gain except pissing off Junior Devs LOL. I am not saying expose your Entities to your UI / Controllers, Razor Pages etc... So some level of abstraction is needed. You are still going to be in mapping hell when it comes to your presentation layer but that is somewhat expected. Get a tool like Mapperly for that. Great video as always man. I was cracking up on your opinion of naming everything IInsertResponsibiltyHereService hahaha.

    @atechdude@atechdude10 ай бұрын
    • IEveryingIsAServiceApparentlyAndNeedsAnInterface_EndSarcasm

      @CodeOpinion@CodeOpinion10 ай бұрын
    • HAHAH

      @atechdude@atechdude10 ай бұрын
  • I think the concept was "with" Milan, he just didn't manage to demonstrate it in the best way. I do believe clean architecture is the best approach in most cases and I think Amichai demonstrates the gist of it terrifically: kzhead.info/sun/ZLOFg6luq4ptZJE/bejne.htmlsi=WlEuQ-dLh6I98-Jx

    @pyce.@pyce.6 ай бұрын
  • I can't believe in 2023 the C++ windows "I" prefix naming convention for interfaces are still there... This I don't like of C# !!! 😂😂😂😂

    @pguti778@pguti77810 ай бұрын
  • I wonder when people started adding indirection and abstractions only for the "what if" reasons. How difficult would it be to change every handler because you decide to use Dapper instead of EF Core, in comparison to creating another persistence project that does this and changing every existing query?

    @moke_codes@moke_codes10 ай бұрын
  • I would love to hear why you don't approve of sending back your data schema object. I sometimes find myself mapping DTO on domain entities and they have the same exact properties as the entity itself.

    @nickolaki@nickolaki10 ай бұрын
    • Well, they change for different reasons. There really are times when your dto has many properties of your domain model.. however, there will be times when you don't want to return all the data that is in your domain model, some sensitive data.. But in general, I don't like to put rules, it depends a lot on the type of application you are developing.

      @juniorzucareli@juniorzucareli10 ай бұрын
  • Dear Martin, I like your videos and Milan have got nice video contents as well. What would be your approach in this scenario ? And, do you have "Clean Architecture" or "Some Architecture" example, skeleton or something like this ? Maybe you have already shared a video about this concept that i have missed )) If you annswer, i will be glad. Thanks in advance 👍

    @eminovperviz@eminovperviz10 ай бұрын
  • Curious about your pet peeve, what do you call things that others would call a 'service'? In the context of this 'orderService', I think I would have called it 'orderStorage'.

    @furious2563@furious256310 ай бұрын
    • I wouldn't create it in the first place so I wouldn't need to think about naming it. But to answer somewhat, let's say I had to make a call for currency exchange. I'd call it CurrencyExchange.

      @CodeOpinion@CodeOpinion10 ай бұрын
    • Names should convey meaning. Calling something ProductService, for example, doesn't tell me anything about what the service does. I know it works with products and that's about it. ProductCatalogService is a better name - now I know that the service is responsible for listing and displaying products, but I could figure that out if it was just named ProductCatalog. The Service part is superfluous.

      @CoreCommander2@CoreCommander210 ай бұрын
    • @@CoreCommander2 But would you put it ina a Services folder?

      @wertrager@wertrager10 ай бұрын
    • @@wertrager no, definitely not. I'd put it in a folder for the feature or bounded context. Namespacing by technical concerns is an anti-pattern IMO and it drives me nuts when I see it. Don't even get me started on project-per-layer...

      @CoreCommander2@CoreCommander210 ай бұрын
    • @@CoreCommander2 I've come to find that "services" provide the one-liners for "handlers" (making testing easier), "managers" provide useful functions for "services" (aggregating data), "repositories" provide data access for "managers" (connecting directly to storage). So, for me, when I see a "service" I know exactly what scope of the problem it's solving.

      @Galakyllz@Galakyllz10 ай бұрын
  • I'm only halfway where you don't see the value. There is indeed a value, there is no EFCore dependancy now in the application layer. It enables to change the ORM provider somewhere down the line with no impact to your application layer. Do we really need this level of abstraction ? it is debatable but not valueless.

    @huguesviens@huguesviens9 ай бұрын
  • Is using an interface extensible (everywhere) a common practice in C#? If no other implementations are needed for a class, I don't really see why you want to use an interface for it. Would like to hear others' comments.

    @ahokai@ahokai10 ай бұрын
  • I love this crossover =d

    @cheeseburger1884@cheeseburger188410 ай бұрын
  • love the video !

    @elpe21@elpe2110 ай бұрын
  • lol i'd just throw everything into the command/query handler or maybe even the controller directly. YOLO

    @edmonddantes587@edmonddantes58710 ай бұрын
  • Indirection is bad. I pondered the same questions and came to the conclusion the query side can use all the EF goodness as is. Persistence agnostic / clean architecture should not become a cult, especially if you want to use CQRS. Anyway, how often do you change your persistence level? Do it per query then. If you have a generic GetOrder service + a specific mapper then you're presumably sharing code between handlers, and we know code sharing is bad: SRP principle, right? It's better to be specific, stick to the problem at hand. Besides, don't throw exceptions for flow control, and pass down those cancellation tokens whereever you can: spare resources!

    @Fred-yq3fs@Fred-yq3fs10 ай бұрын
  • I hate this abuse of single interface to single class craziness! at very least just have a single class implement many interfaces and use that...either way to me this indirection and "single abstractions" is crazy

    @markcampbell2491@markcampbell249110 ай бұрын
  • always makes me cringe when people abstract away from the database/datastore, which are often highly optimized data container/manipulators that often are treated as dumb storage services. Backends need to be as thin as practical!

    @keithnicholas@keithnicholas10 ай бұрын
  • Yeah I stopped witching the moment this guy created a service. The whole point of vertical slice architect is to get away from those GOD services.

    @DamageSoftware@DamageSoftware9 ай бұрын
KZhead