3 .NET "Best Practices" I Changed My Mind About

  Рет қаралды 105,462

Nick Chapsas

Nick Chapsas

Күн бұрын

Check out my courses: dometrain.com
Become a Patreon and get source code access: / nickchapsas
Hello, everybody, I'm Nick, and in this video, I will talk about the 3 practices that I used to consider good in the past, but I actually don't use in my own code anymore.
Workshops: bit.ly/nickwor...
Don't forget to comment, like and subscribe :)
Social Media:
Follow me on GitHub: bit.ly/ChapsasG...
Follow me on Twitter: bit.ly/ChapsasT...
Connect on LinkedIn: bit.ly/ChapsasL...
Keep coding merch: keepcoding.shop
#csharp #dotnet

Пікірлер: 329
@dsuess
@dsuess Жыл бұрын
(1) 0:43 - 3:07 - Magical "DI Installer" using Reflection vs. Extension Method (Visibility, Order of Execution) (2) 4:46 - 6:57 - Not using a Mapper vs. manual approach (3) 7:50 - Guard clause This is a great video breakdown. Happy to see you take on some old fashion, more manual processes. Yes, i'm old fashion
@bradenb
@bradenb Жыл бұрын
A few things I used to evangelize that I no longer do myself: 1) "Interface all the things!" - I used to ALWAYS start with the interface. This lead to having a lot of interfaces that didn't really need to exist. I still frequently end up with an interface, but by starting with implementation first it gives me a better sense for what my interface actually does and does not need. 2) "Clever" code. Unless I have a very unique problem to solve that is best solved by a clever solution I try to avoid cleverness now. Even if I could maintain it well myself, I too often wasted time helping other developers understand code that was needlessly complicated. A major part of that was ditching the use of reflection in my own code almost entirely. 3) Multiple projects. I picked up this habit from some senior engineer many years ago. He would break everything up into very very small projects. He always claimed doing so was future-proofing the code, helping with build time, making it more maintainable, etc. I never saw those benefits materialize. I now try to always start with a single project and use folders (and even those I use sparingly). 4) Making everything internal. This was a habit I had in place when I started with #1. Every implementation had to be internal. The only type that could be public was a single master factory class for creating instances of other factory implementations for the various types I would need. In that way, consumers would always deal only with public interfaces and a single static public factory class. It felt right at the time, but the unnecessary headaches it caused make me feel bad that I pushed it on others. Basically, I now write code again like when I was first learning. But I do so with much more confidence and critical thinking and never write anything I don't have to write.
@xlerb2286
@xlerb2286 Жыл бұрын
Agree with many things there. I still code to interfaces but certainly not "all" things. If I feel there's nothing there that will ever need a different implementation I'll skip the interface. It's just code, if I'm wrong an interface can be added. And certainly agree on avoiding clever code. Obvious code is understandable code, understandable code is maintainable code. Clever code is almost by definition unobvious. I do still make everything I can be private/sealed, and increase visibility only when there is a reason to do so. But I'm not as hardcore as exposing only interfaces and a factory.
@Ashalmawia
@Ashalmawia Жыл бұрын
1) yeah you should have a reason for declaring an interface. two of them I can think of are: dependency injection (hiding a dep behind an interface and allowing it to be substituted with a mock), and just general abstraction. (for example you can share arithmetic between the domain and the UI if both implement the same interface that the arithmetic is written against.) 2) yes I avoid reflection if I can too because it is usually not compile-time safe. it's pretty much always better to design things to break at compile-time when they're wrong, even if it's more verbose. an exception is unit tests where I find reflection can sometimes help when extracting testing logic. 3) projects should be organized by dependency 4) it's "better" to make a class internal when possible. making members of classes internal seems to not be that great most of the time unless there is an important reason to.
@bradenb
@bradenb Жыл бұрын
@@Ashalmawia mostly I agree with you, but the thing I’ve learned after 17 years in this industry is that there’s never a general right way to do anything. It’s always going to depend on the project.
@acasualviewer5861
@acasualviewer5861 Жыл бұрын
KISS.. Your worst enemy is overengineering. The best code is no code.
@pfom
@pfom 6 ай бұрын
Is there some good piece of documentation, article or a book which helps defending the #3 point? I saw some solution projects which only contained a single installer from this video.
@minnesotasteve
@minnesotasteve Жыл бұрын
I think you summed a lot of this up nicely with... "I like visibility more than magic". My coworker has been experimenting with using CoPilot as his code generator to automation the initial creation of some things, like the mapping of domain objects to DTOs. We were trying to interface with a third party, and the auto-generated code from importing the WSDL was not working and because it was auto-generated it was hard to edit. So we took the specific parts of the calls we needed, grabbed XML samples and then used Copilot to generate some objects and mappings and a client around them. It worked fairly well. Very easy to read code, not so much auto-generated as generated once and then maintained.
@evancombs5159
@evancombs5159 Жыл бұрын
I agree, visibility over magic hits it on the head. I am mostly a self-taught programmer, and when learning I was always so frustrated when I would encounter "magic" code because it was rarely explained how the magic code worked. It was almost always accepted as is, and never explained. This almost always led to the biggest pain points in learning. That drove me away from "magic" code very quickly.
@Baby4Ghost
@Baby4Ghost Жыл бұрын
Do not take example of magic or unreadable codes. Code quality is not solely based on, if it works. There are multiple roads to Rome, in code as well, but its about the right solution for the problem. Devs watching QA test the product: kzbin.info/www/bejne/mJK8ZIaXfs2ZopI
@DryBones111
@DryBones111 Жыл бұрын
I also write my own mappers as extension methods and the process was tedious until I started using Copilot as a smart autocomplete. To me it's got much of the benefit of code generation without the downsides.
@Fred-yq3fs
@Fred-yq3fs Жыл бұрын
For the guard clauses: the domain code is responsible of its internal state, therefore it must check its inputs. Of course the application code must perform validation so that it does not even attempt to misuse the domain objects, but if it does then the domain objects should complain via exceptions (which are then exceptional and show to the caller that the program is wrong)
@Coburah
@Coburah Жыл бұрын
This is my approach as well
@sandervanteinde9352
@sandervanteinde9352 Жыл бұрын
Easy to catch however with creating static Create classes which can return a OneOf or Result object to notify if it succeeded or validation failed. Add a private constructor and done, safety + validation without exceptions
@CesarDemi81
@CesarDemi81 Жыл бұрын
Same approach over here as well. If it reached the domain layer, it's the last line of defense to keep the state correct.
@Coburah
@Coburah Жыл бұрын
@@sandervanteinde9352 this is my preferred approach as well, but C# has such poor support for it. No currying and No pipe operator. Forces you to write code backwards sometimes. On top of that, the extra allocations of result objects (or structs) has made me favor exceptions in the domain. I still use result objects for IO though or other service-type functions that can fail.
@robsosno
@robsosno Жыл бұрын
Same approach. Plus I don't need to write unit test for "wrong" branch (corporate policy to cover most of code paths).
@Pookzob
@Pookzob Жыл бұрын
I'm so glad I'm not alone in the camp of "I had to deal with my own shenanigans that I thought was cool and clever 6 months ago, and it was hell"! It has been feeling very barren here for some time :) To me, this is a sign you've truly grown and matured as a programmer. Thank you for this video 🙏
@joshpatton757
@joshpatton757 Жыл бұрын
Alone in the camp? It's nearly a universal truth.
@Pookzob
@Pookzob Жыл бұрын
@@joshpatton757 it is, but I rarely see people online talk or write about it. Maybe I've been surrounded by expert beginners :)
@nmarcel
@nmarcel Жыл бұрын
Personally, what I dislike the most is grouping code by non-business funcionality, like having all mappers, validators, exceptions, etc. together in some folder. Even the idea of having separated commands from queries or requests from responses looks to me wrong (i.e. I prefer to have together in a folder UpdateCustomerRequest with UpdateCustomerResponse than UpdateCustomerRequest with UpdateProviderRequest just because the two are requests). The suffix (request,/response, command/query, mapper or validation) already separates them, so folders are not needed for that.
@Cesar-qi2jb
@Cesar-qi2jb Жыл бұрын
What do you do when your response is composed by multiple responses from different folders? Do you reference them? By following the feature-sliced approach, it may appear that you are isolating folders based on business capabilities, but you are actually not. Once you define your context, it is better to enforce "best practices" with folders (Requests, Responses, Handlers/Services, Mappers etc) I know many people may jump on me with what I just said. But, I really find your approach better suited for front end and not for backend REST APIs.
@nmarcel
@nmarcel Жыл бұрын
@@Cesar-qi2jb it depends on the resulting complexity. Either I reference them as you suggest, or I keep separate classes for the compound response (i.e. my Product response class is not necessarily the same for GetProduct() than the Product [with simpler content] for GetInvoice()). I don't see how machinery (validation, mapping, req/resp, handlers) grouped enforces good practices, because I could say the same about grouping by business-case (each one is required to have its own machinery).
@Cesar-qi2jb
@Cesar-qi2jb Жыл бұрын
​@@nmarcel Are you building REST APIs? Having two representations of the same resource is fair. Just take into account that swagger does not distinguish between namespaces. Therefore, you better name them differently. Product and InvoiceProduct.
@nmarcel
@nmarcel Жыл бұрын
@@Cesar-qi2jb no, in fact I work doing that machinery grouping I hate, just because is the standard of my employer. And I know that limitation of Swagger, hoping to be fixed or evolved in the future.
@shaicohen2
@shaicohen2 Жыл бұрын
"Back in the day" of MVC where Controllers, Views, etc. are wired up "by convention", most of my time was spent scrolling up and down the Solution Explorer moving between Controllers and Views. One day I stumbled upon a "hack" that allowed you to organize related Controllers and Views under one folder, and still have everything wired up automatically. Changed. My. Life. I frequently advocate this approach.
@StephenMoreira
@StephenMoreira Жыл бұрын
Love seeing how other developers evolve in their practices. Thanks for sharing.
@quetzalcoatl-pl
@quetzalcoatl-pl Жыл бұрын
It's also quite reassuring to see others evolving their views/beliefs in the same direction as oneself ;)
@rzaip
@rzaip Жыл бұрын
I agree with all except guard clauses, when doing DDD I still think guards have some value in ensuring your aggregates aren't instantiated in a bad state. That besides your database is basically your last line of defense for data corruption. Obviously any input validation should have happened prior to this, so these exception are still exceptionall. I find it cluttering the code if you need to pass a result object back from your domain model all the way back to the Controller.
@pavelyeremenko4640
@pavelyeremenko4640 Жыл бұрын
Not using guard clauses doesn't prevent you from maintaining valid state. Just make the empty constructor private and make it a static `Create` or to be explicit `CreateAndValidate` method that returns either a succesfully built object or an error/errors.
@adambickford8720
@adambickford8720 Жыл бұрын
You're almost certainly obscuring some other bug and creating a very tight coupling that's inherently inflexible. Fix the calling code.
@PelFox
@PelFox Жыл бұрын
@@adambickford8720 Easier to track a custom domain exception and it's stack trace than passing result objects. By not throwing exceptions you could potentially hide a bug that keeps returning 400 to clients.
@domints
@domints Жыл бұрын
@@PelFox passing result objects is trivial when you've got proper result objects. And exceptions? Used like that they are just a bit more fancy goto. I think we all agreed gotos shouldn't be used outside assembler?
@rzaip
@rzaip Жыл бұрын
@@domints Would love to see some example that isn't cluttering your code with a bunch of if-elses and passing result objects several layers.
Жыл бұрын
I also started using automapper a while back but after having had to deal with the frustration from debugging when property names no longer match, I just wrote my own generic interface IMapper with an abstract class on top to wrap null-handling and it works great. There's almost no magic at all except from some reflection to actually find and register all the mappers. Works great! Something else I've stopped doing is following the idea that you might as well combine data-access classes with domain classes. And then you have scenarios where you *have* to load a big-ish "aggregate" just to use a really small part of it. When I first tried writing really simple data-access classes for EF core with almost no navigation properties and simpler domain-classes it just clicked. It's sooo much easier having that natural separation between the layers. When I don't need a whole aggregate to do something, I just don't load it. And I still don't have to mess with lazy loading or explicit loading that turns into situations where you have no idea if the property is not loaded or just loaded but actually null.
@Palladin007
@Palladin007 Жыл бұрын
1. Installer-Interface: I also utilize extension methods. I have experimented with various complex approaches, and the extension methods work flawlessly and offer simplicity - a win-win situation. In certain cases, I also create methods that work with a configure action and a builder class as parameters. This approach ensures that even in intricate scenarios, no "magic values" are concealed within the method. Every configuration belongs in the startup. My preferred method of passing configuration is by utilizing the OptionsBuilder class. Although the JSON property names are not explicitly present in the code, it immediately becomes apparent which class's property names are being used. 2. Mapper: I also have reservations about mapping frameworks. However, my solution is straightforward: the constructor. When I need to map an entity object to a DTO, the DTO is equipped with a constructor that accepts the corresponding entity. For additional data, additional parameters are provided, or I make use of the new required init parameters. This approach is simple yet effective. Whenever I add a property to the DTO, it becomes immediately apparent where the mapping needs to be adjusted, and it works seamlessly with nullable reference types. 3. Guard Clauses: I appreciate guard clauses, but only in cases where I require checks at multiple points in the code, and they do not rely on knowledge of the business logic. However, in most situations, I opt for using if+throw, aiming for simplicity. 4. Validators: I am also not fond of validation frameworks like FluentValidation. There are two aspects that bother me: - Business logic (including validation) is separated into a minimum of three files (implementation, model, validation), making the direct context less apparent. - Complexity, for instance, FluentValidation can be quite intricate. Why not stick to simple if statements and corresponding methods? While I have accumulated several years of experience, a newcomer would likely encounter difficulties.
@aj0413_
@aj0413_ Жыл бұрын
I agree with all of this, but with the caveat that I find validators can be incredibly handy when the same validation is applicable to multiple code paths or when dealing with nested objects and you want to kick off the validation from the top level. Basically, if the problem is already complex enough, then I might as well use a validator to make my life easier; code will be messy either way
@Palladin007
@Palladin007 Жыл бұрын
@@aj0413_ That's true, of course, but why so complicated? All these advantages can also be achieved with a simple interface and a method that classically checks what needs to be checked. I would just implement a normal class with a Validate method that takes a context and provides more complex functions like ValidateEmail(). Maybe also in the model directly, so the context is obvious. Instead of: RuleFor(x => x.Discount).NotEqual(0).When(x => x.HasDiscount); Simple: if (HasDiscount) { if (Discount == 0) context.AddError(nameof(Discount), "..."); } Or something like that, that would be much easier and probably also more performant? With an interface I can cast a request object to it and validate it, this can also be done centrally. Or, if you absolutely want to have it in its own class, a generic variant that I search for and can validate. Same result, but much simpler. Or am I missing something?
@aj0413_
@aj0413_ Жыл бұрын
@@Palladin007 FluentValidtor really just solves the problem of onboarding and maintenance across teams and team members buy keeping the validation code consistent and easily parsable. I’m not in love with it, but I can see how it at least enforces a standard that isn’t tied to tribal knowledge You could hand roll your own framework, but you’re reinventing the wheel a bit at that point And we all know how well it goes to just have “standards documentation”
@Palladin007
@Palladin007 Жыл бұрын
@@aj0413_ Yes, that is also true. A standard is worth a lot, but currently that's the only real advantage I see. To me, the project seems extremely over-engineered. The goal is good, but the result is way beyond that. And of course, an alternative only makes sense as its own framework, for exactly the reason you mentioned.
@aj0413_
@aj0413_ Жыл бұрын
@@Palladin007 We're pretty much of the same mind. FluentValidator is over-engineered, but it is what it is /shrug Best of a bad set of options, far as I can tell
@ettiennedebeer9174
@ettiennedebeer9174 Жыл бұрын
I have never been a fan of automated handlers for repetitive work, they create an area where control is lost into a magic world of dependency. Especially for code where you can still be in control. Sometimes rapid development leads to tedious support. You have to always consider that you did not cater for every aspect a user might exploit and if you lose visibility of your data inside an invisible process it becomes difficult to find and fix the root cause. Be considerate of those that will maintain your code, they do not have the privilege of seeing your thought process.
@orterves
@orterves Жыл бұрын
All excellent advice - it takes experience to see those magic approaches as not simplifying the code, but in fact just hiding complexity. Bite the bullet and handle the complexity as close to the compile step as possible, as explicitly as possible, and wherever you can make invalid state unrepresentable. (Guard clauses being a hack to handle the possiblity of invalid state)
@olegvorkunov5400
@olegvorkunov5400 Жыл бұрын
I rejected mapping frameworks from the beginning. I use normal code to map between objects property by property. I never use Guards in constructors. I still might validate method parameters from time to time where I do not control if it is null or empty. I provide a complete code for DI registration and you said it correctly: Open code vs Magical code. Magical code will save you time to write open code but in the end, it will be very hard to maintain and might lead to a lot of problems and performance issues.
@DryBones111
@DryBones111 Жыл бұрын
Reflection is one of those "clever" tools that so many developers fall into the trap of abusing. I also had a phase where I would use reflection magic to make the code "clever" but maturing as a developer is realising that code that makes you look clever isn't necessarily the best code.
@Vinxian1
@Vinxian1 Жыл бұрын
With never using guards in your constructors, how do you deal with someone passing null for a required dependency? Catching that immediately rather than when an attempt is made to use the dependency seems better right?
@DryBones111
@DryBones111 Жыл бұрын
@@Vinxian1 You can use a private constructor and a static factory method.
@bfg5244
@bfg5244 Жыл бұрын
Nick, provide an example of how nowadays you validate invariants instead of guard clauses (of any kind).
@mDoThis
@mDoThis Жыл бұрын
I personally like FluenValidation, we've changed all of the controllers from using Guards to FluenValidation. Action filters are also nice.
@nemanja.djordjevic
@nemanja.djordjevic Жыл бұрын
@@mDoThisYou can not use fluent validation to validate invariants for domain object.
@valera924
@valera924 Жыл бұрын
I totally agree! Just have an observation about #3. Throwing an exception brings us a StackTrace which is very useful sometimes. When we use a Result pattern or something like discriminated unions we might have lost an original source of the error
@MetalKid007
@MetalKid007 Жыл бұрын
If you validate ahead of time, you wouldn't need to know it. :) Exceptions are actually pretty expensive to throw and can bog down your system if they are thrown all the time.
@bfg5244
@bfg5244 Жыл бұрын
and how does stack trace helps in very specific case of validation?
@adambickford8720
@adambickford8720 Жыл бұрын
@@bfg5244 Its null and it shouldn't be. What chain of code got us to this point?
@paulkoopmans4620
@paulkoopmans4620 Жыл бұрын
@@adambickford8720 Then you are arguably using the wrong type. If your method does not want a null.... then why did you make it nullable in the first place? Sometimes it looks like people think guarding and validation is the same thing, where I think they are not. If you use value objects and either do validation in there or are using builder pattern and validate there, something quite powerful happens because now you can trust those instances right away. I would even argue that most people end up with guarding because they are in fact always using base types in the first place.
@adambickford8720
@adambickford8720 Жыл бұрын
​@@paulkoopmans4620 Value objects solve the problem but they create an enormous amount of work/friction. I genuinely can't think of a more expensive and overengineered approach.
@dxs1971
@dxs1971 Жыл бұрын
I see a lot of things happened lately to simplify our developers life. We are using more simple things and increase readability of the code and in the same time maintainability. I wish a lot of people should look at your videos and also using common sense during coding. I hope this trend will continue in the future.
@nickandrews1985
@nickandrews1985 Жыл бұрын
Spot on, Nick. I have transitioned away from these same practices as well for the exact same "manual" alternatives. I find it hard to trust the "magic", even though it's cleaner, and faster to implement. Until you need to debug. Thanks for video!
@jfpinero
@jfpinero Жыл бұрын
You can always do your own manual mapping in converters (automapper term) and use the basic mapping for contracts you know won't change.
@gveresUW
@gveresUW Жыл бұрын
I completely agree. I started using Automapper, but once they went to the paradigm where it was supposed to be a DI item, I changed my mind and started using it less. And once I started using it less and started building my own mapping functions (usually on the DTO classes as either the constructor or a conversation operator / function), I realized I liked it better. It also allows me to do a find reference and find every case where a class variable is used. No longer are those hidden behind automapper calls. And I really do not like exception handling for errors. I use it where I have to, but most of the functions I write do not throw exceptions, they handle the exceptions. I find try/catch just too clunky to produce elegant code. But I am one that likes to use Promises on the front end rather than await /try/catch as well.
@angelowolff7127
@angelowolff7127 Жыл бұрын
100% literally do the exact same things. And my code is healthier as a result, especially with mapping directly with ef core. Another thing I do is build responses for layers of the application, e.g. all my services would return a service response, something like a controller response could inherit service response and only give relevant data to client machines while the service response could be handled internally for clearer logging. Initially I had mappers with built in logic from services, and they would return a mapper response which forms part of the response pipeline of a request, then I realised that my mapper code started getting way too bloated and difficult to maintain or test properly, and a lot of the time I'm mapping simple things that should never fail but having to handle and create a state response was just unnecessary. Good video.
@RobinHood70
@RobinHood70 Жыл бұрын
Nick: Hello, everybody. I'm Nick and in this video... Captions: hello everybody I'm naked in this video...
@twinkle-and-bosh
@twinkle-and-bosh 4 ай бұрын
I can't tell you how happy I am to see someone else advocating for manually writing mapping code 😅 I've been fighting that fight with colleagues for what feels like decades!
@AnythingGodamnit
@AnythingGodamnit Жыл бұрын
Avoiding magic and being explicit, moving runtime errors to compile time, using types to represent expected failures. These were all part of my journey from C# to F# and I've never looked back (I watch some of Nick's content because he talks about general .NET concepts). Thanks for the video!
@nanvlad
@nanvlad Жыл бұрын
I do not debug... mostly. Since I've discovered declarative approach (with a functional-style flavor) I don't need to handle custom exceptions or to iterate over a for/foreach loops to investigate an issue. Logging can be helpful sometimes if you want to track technical errors, but Debug.Assert is also fine, like it's shown in a video.
@dnwheeler
@dnwheeler Жыл бұрын
It's a subtle distinction, but I only use guard clauses to catch programming errors, not for data validation. This also implies that callers should never be catching guard clause exceptions. I want them to crash the application so I can fix it. I use a completely different strategy if I'm working on safety-critical code and want to fail "safe", in which case exceptions are reserved for unrecoverable errors. Like the other case, these shouldn't be caught by the caller but are usually caught at the application level to log the error and present some sort of error message to the user.
@Cethris
@Cethris Жыл бұрын
I've been preaching this at my current job for a year now, with everyone ignoring me. I started to think that maybe I'm the crazy one. And here I found your video. Thank you) 👍
@CarlosRodriguez-gh1ow
@CarlosRodriguez-gh1ow Жыл бұрын
Nice video! Agree with them all. It's nice to see that we evolved in similar ways
@roderick9665
@roderick9665 Жыл бұрын
Agree with all your suggestions especially manual mapping! That is where one can introduce backwards and future compatibility with intelligent defaults where required. Obviously logged once only if applied.
@dannykempkes4957
@dannykempkes4957 Жыл бұрын
Thank you for another excellent video. I've encountered similar challenges in the past, except for the guards. I had a significant issue with a legacy application that utilized Automapper with business models and DTOs having the exact same name but different namespaces. To resolve this, I developed my own mappers to untangle the complexity, and I've been utilizing them ever since. One thing I moved away from is creating a file for every C# class, especially when using MediatR. Now, I put the request and response (records) in the same class as the handler. This change also made navigation easier because it created a lot of concerns with colleagues when introducing MediatR. See you tomorrow @Techorama
@andreistelian9058
@andreistelian9058 Жыл бұрын
I saw that thing with installers at you. I didn't really liked the idea and I knew that if I would have a layered architecture, every layer should take care of it's dependencies. Now, what I do is making a static class with an extension method for IServiceCollection and I put the dependencies of that layer in that method. For example, if my Infrastructure layer has dependencies to the mapping layer, I add the dependencies for the mapper in the method from the infrastructure layer. Guard clauses weren't really my thing and I advocate for using mappers made by us, but when making a small service or a simple app, a mapper i think it will be better suited if i want to finish up fast
@codesafariDev
@codesafariDev Жыл бұрын
For you question: 90% of the time I see MediatR used, it is used to process simple function calls like "AddUser Endpoint gets called -> sends Event AddUser -> UserService gets the event and adds user" in a pure webapi. Just makes it harder to navigate from the endpoint to the implementation and solves non of the problems MediatR tries to solve.
@Sunesen
@Sunesen Жыл бұрын
At my work we solve this by using vertical slice architecture where we have the controller endpoint, the service that handles the request, and the POCO objects all live within the same file, even though they are different classes. They are all part of the same feature though and is not used anywhere else so this saves us having to hunt down the implementations all over the place.
@CesarDemi81
@CesarDemi81 Жыл бұрын
@@Sunesen Exactly, most of the detractors of this practice is because they usually have practiced it wrong. Also, there are a variety of VS extensions that help just that: jump from the place where the MediatR request is being called straight to its handler, so the navigation argument is at the very least, weak.
@evancombs5159
@evancombs5159 Жыл бұрын
@@CesarDemi81 I feel like once you get into vertical slice architecture you basically have no need for MediatR.
@moneymaker7307
@moneymaker7307 Жыл бұрын
Most of you all are doing too much. If I remember correctly the mediator pattern help services communicate without the services having reference to each other. For a request over http a simple pub sub will work just fine. What is getting vertical slice and what are you guys mediating 😂
@CesarDemi81
@CesarDemi81 Жыл бұрын
@@evancombs5159 well, you can always implement your own stuff, but that would meant maintaining it and make sure it works for all your use cases plus any other you require in the future. I prefer to reuse something that has already been developed and is actively maintained. It helps a lot for VSA.
@MariosKimonos
@MariosKimonos Жыл бұрын
I never really used guard clause, What I do is for a constructor I create a configuration class as the requirements for my services. The program needs to and add the in the DI as singletons during startup. It allows it to be caught at the beginning. It also makes it where I can use inheritance if different Services use the same fieldslike timeouts or retry policies.
@Brendan2Alexander
@Brendan2Alexander Жыл бұрын
Good video. I have looked into mappers, but agree the "magic" almost always causes headaches and runtime errors. When I change domain entities, my mappers will lightup to show mapping problems, which can easily be fixed.
@philosophersam
@philosophersam Жыл бұрын
A team I was on went in this direction (not exactly, but pretty close) on every issue he covers here. And, just like Nick, they were doing the same things he was in the past. I agree with every move he makes here.
@harbor.boundary.flight
@harbor.boundary.flight Жыл бұрын
I also write my own mappers most of the time - glad I'm not the only one :D I also use guard clauses, but not in constructors - mainly as shortcuts to throwing exceptions depending on a value.
@tomazkoritnik4072
@tomazkoritnik4072 Жыл бұрын
Hi Nick, good point. I agree regarding DI container registration visibility. This is exactly why I don't like any of DI container nowadays where by looking at the class, I cannot see if it's registered or not and have to browse through a long list of registrations to find it. From the class I also don't know if I should get it from DI container or create it on my own since again, I need to check if it is registered. Maybe developer forgot to register it and amazingly, many/some popular DI containers are still not checking the dependency graph upon starting the application producing run-time errors later on. That's why I really liked MEF approach with Export and Import attributes and was all clear. I started to use my own Export attribute on classes that are then automatically registered into .NET 6 DI container by scanning through assemblies. Regarding registration order, I think that if order is important then this is a code smell and something is wrong with the design. It should not be.
@joshpatton757
@joshpatton757 Жыл бұрын
Lately I've been in the habit of writing a quick integration test using an automapper, and then coding the actual mapper by hand using the test as a guide. Often that's sufficient, and helps to prevent some of the easier copy/paste, missed property, or other mistakes that can happen. If the auto-mapper can no longer handle the task, then I'll remove it and change the test, but I've found it helps me make a manual map both faster and a bit better quality than I can just by starting out manually making the map. One reason to do this is if the structure of one object changes and the other object does not, you don't want the mapper to be enforcing the structure unnecessarily. So treat it as a disposable tool, not as the solution.
@CraigBoland
@CraigBoland Жыл бұрын
Automapper has a validation feature that handles the case of newly added properties on target object. I do like your approach of using my own mappers, within which I can use Automapper along with my own code rather than relying solely on Automapper to do all the work.
@lmoelleb
@lmoelleb Жыл бұрын
And that validator can run in your unittest on the merge to master. Had a project with this setup on database to domain entities and manual mapping on domain to web API. Automapper was the most reliable of the two as we never missed a property - but it was also the hardest to read.
@Dustyy01
@Dustyy01 Жыл бұрын
I almost agree with everything. The exception handling in ctors with the guard clauses is a good point. It's validation logic, it should not be a concern of the class if users provide values that make literally no sense. E.g. from a request the input data should be handled with model validation like FluentValidation. However I do see the use case of guard clauses in other spots where you need to throw multiple exceptions if something enters (as the name suggests) an exceptional state.
@marcusmajarra
@marcusmajarra Жыл бұрын
I understand your point about guard clauses and agree with it on principle. They are meant to intercept improper uses of an API and, as such, should not be necessary for API usages that are entirely self-contained within an API you control as you can otherwise guarantee that you're using the API adequately through thorough review processes. And I can recognize that debug assertions do allow you to have your performance cake and eat it too. That being said, as much as we would like to constantly produce bug-free code, reality doesn't quite get there, and without a guard clause, the runtime will not only fail, but will do so at a point in time where the stack trace will not indicate how the API was improperly used. Rather, it will most likely raise an error regarding a null pointer where it needs to be consumed, making it a puzzle and a half to figure out exactly which API call introduced the illegal value to begin with. As such, I still find value in preserving guard clauses, especially in more elaborate codebases that did not consistently follow a thorough review process and public API calls that would prevent client code from improperly using your API.
@Andrei-gt7pw
@Andrei-gt7pw Жыл бұрын
Writing your own mapping sounds good. When you have 3 or 4 classes to map. But how about when you have a ton of data classes to map with data coming from various sources (apis, database, etc) and you also need some validation so you know that your mapping is consistent after source structure updates?
@minnesotasteve
@minnesotasteve Жыл бұрын
Software is usually built 3 to 4 classes at a time.
@robertmrobo8954
@robertmrobo8954 Жыл бұрын
@@minnesotasteve Exactly, all the complex systems I have worked on had 4 classes at max.
@minnesotasteve
@minnesotasteve Жыл бұрын
@@robertmrobo8954 I guess to clarify, my point was the systems may have thousands of classes when complete, but you work on them in small chunks. So it's not like you have to do all of this at once, it takes place over many months, years, etc. Not saying 3-4 classes max, but each workitem evolves that way.
@Rizzan8
@Rizzan8 Жыл бұрын
Looks like some tasks for interns!
@anreton
@anreton Жыл бұрын
Hey Nick. The video reveals important things. I, too, over time came to the same decisions and conclusions, because they are more effective.
@NeddySmithzzzz
@NeddySmithzzzz 5 ай бұрын
Good video. I personally like Guard clauses, Fail Fast where possible. Its obvious to the caller (may not be your code if its an API for example) what the issue is and the exception is explicit in its meaning rather than the caller getting some NullReferenceException downstream buried in a stacktrace that may be difficult to troubleshoot.
@brucerosner3547
@brucerosner3547 Жыл бұрын
FWIW I agree, I've now prefer to handle validation and other errors in a result object. This facilitates "rail-track" error handling so I only have to check a result once at the end of a sequence of methods. However a result object is a little like const in C/C++; you need to use it a lot to benefit.
@aj0413_
@aj0413_ Жыл бұрын
Hmm. I agree on manual mappers and visibility > magic, but disagree on guard clauses. The main thing here is that throwing exceptions is fine, because, if the ctor of a domain object is being called with invalid data, that still constitutes an exceptional case. If you have a good understanding of how exceptions work and the flow of your application, than one stacktrace and error message can reveal alot of what went wrong with a request. That does not mean not to use validators and so on, it just means that a combination of both is what I personally prefer.
@loganyoung228
@loganyoung228 3 ай бұрын
I'm moving away from logical structuring in my projects (so no more Interfaces, Services, Models, etc. folders) in favor of vertical slicing. I don't even mind repeating code in each vertical slice, for instance, for my result objects, because I realized that doing so can actually give me a lot more flexibility in case I want to handle results differently in a particular area of my project. I did like the idea of the methodology for service installers, but then you talked about extension methods and I realized I'm already doing that anyway so I won't be trying to take on the installers approach. As for guard clauses, I normally just use conditional statements to verify state and then handle different scenarios or simply return a failed result back to the calling method.
@Bundynational
@Bundynational Жыл бұрын
As a general rule of thumb, I try to minimize the use of libraries in my code. It makes upgrading and maintaining the codebase a lot easier. I'll keep on using Mapper, though! 😅
@yezinia
@yezinia Жыл бұрын
I tried this as well but this will let you run into reinventing the wheel. Try to encapsulate them by defining interfaces, wrapping the lib code into implementations of these interfaces, inject them via DI and you are good to go with library code. Using some kind of facade will keep your code base controlled and makes maintenance easier
@majormartintibor
@majormartintibor Жыл бұрын
Same as 2. I don't use auto map(pers). Writing my own maps force me to always think through what I am mapping and why. A few times I ran into it, that it is not a simple 1:1 conversion.
@ronsijm
@ronsijm Жыл бұрын
I'm not using AutoMapper anymore either, but the only thing is miss is projecting an entity to a DTO where it selects a bunch of stuff from different tables. With ProjectTo AutoMapper would create a specific sub-select on only the columns that you needed to project the lazy loaded entity into a DTO. Writing your own specific selects without over-selecting just all the properties manually is somewhat tedious I haven't really found a good alternative for that
@shreyasjejurkar1233
@shreyasjejurkar1233 Жыл бұрын
Extension methods are love ❣. Raw, Native, No reflection and no majic! For 3point I guess those Nuget package motivation is "Rust" programming language, Result enum is ❣.
@robadobdob
@robadobdob Жыл бұрын
I went for the extension method approach for wiring up services and it is simple and readable.
@boguslawkudyba6034
@boguslawkudyba6034 Жыл бұрын
1. I didn't know of that "magic" trick, I've been always using extension methods :) 2. I don't trust automappers. I need to have full controll of whats beeing mapped, for example I'm making sure I do enum to string when saving to database or reading it back 3. My IntelliJ says "Exceptions should not be thrown by users code". These guards are probably a workaround to not directly type `throw new Exception` - I agree that exceptions should be exceptional. And to controll null/empty values I'm using [Required] or FluentValidator instead. Nice video!
@Les1aw82
@Les1aw82 Жыл бұрын
Also resigned from automapping long time ago. Like you say: something changes and you can't see it in compile time. But I really like using interfaces instead of extension methods. Call it mappers, builders, factories. They just make writing unit test so much simplier when you don't have to prepare objects that will map correctly but instead you just mock the the mapping interface and test the mepper in seperate tests (or not :D )
@CarlosWashingtonMercado
@CarlosWashingtonMercado Жыл бұрын
Man, that was a wonderful video. Thank you for making it.
@Imploser
@Imploser Жыл бұрын
1- Mapping: I personally prefer builders instead of mappers. Especially for DDD, it is more manageable approach. I am manual mapping in implementation code most of the time. When i need same mapping more than once, then I create a manual Mapper component. 2- Guard: I tried monad based approaches and i like it in Application or Domain Services. But, in an aggregate or value object guard clauses is better for me. 3- Installers: I was using Installers before DotNet Core. I prefer extension methods in one static class for each related component. This approach also more manageable for optionality. 4- LoggingMiddleware: I used to handling exceptions in LoggingMiddleware with try catch. Now, I still have Logging Middleware but, this has just for info logging. I am using ExceptionHandler now.
@billy65bob
@billy65bob Жыл бұрын
On Guard clauses... I'm not a fan of exceptions either, especially where API controllers are concerned. My preferred approach is to instead generate validation errors (and either returning null from the controller or skipping it entirely), and have a global filter generate a standardised 'validation error' response payload for my entire API. Outside of APIs - both Web and traditional - I don't do much internal validation at all. You have way more control in these scenarios, so it's much better and far easier to preemptively deal with them before they make it into the system. Of course, you will screw up and introduce bugs, at which point you'll need to add more and more internal guards to deal with old data...
@xlerb2286
@xlerb2286 Жыл бұрын
I suppose it depends on the type of code one writes. I do mainly class libraries meant to be called directly by other .NET logic, no web services, etc. So for me a robust set of guard conditions on anything public is useful. Call one of my library methods passing in garbage and you'll get a big fat exception with information on what was bad and, when applicable, why. That's the kindest thing I can do to help you realize you've got a problem and figure out what's wrong. For anything internal/private I'll have a robust set of Debug.Assert "guards" as well because I want that same level of support for myself when I do something stupid. ;)
@mrcarlpaton
@mrcarlpaton Жыл бұрын
Totally agree on the "no magic" brother! Any auto magic resolution or reflection is out for me. It's too hard to debug when things break.
@alphaios7763
@alphaios7763 Жыл бұрын
Wow, I also switched similar to you. I had an obsession with expressions and overcomplicated my code in the past… for my latest project I simply have a handler that is defined for a route, I keep validation magical, but my validator is right above the handler so I know what is going to happen. One route = one file, easily copy pasted, easily reusable (I register handlers in DI). Simplicity and readability over fancy code all the way for me these days
@PierreGadea
@PierreGadea Жыл бұрын
There are some tradeoffs to not using a mapping library. Mappers help with productivity if there is lots of mapping done in your application and may reduce thousands of lines of code. In addition, they can help avoid typo bugs like mapping the wrong properties to each other or forgetting to map a property. Both are common bugs I have seen. Another issue is that projects that don't use mappers sometimes skip mapping altogether and expose the database contract as the API contract. Probably because it's such a tedious task.
@yezinia
@yezinia Жыл бұрын
I'd argue that using mappers is an anti pattern all together, because usually it just promotes mutable objects which is always a bad idea (eliminating all illegal invariants with tests is just not possible). Good objects are immutable and encapsulate behaviour, so most mappers just do not work for these objects. Usually other creational design patterns like builders or factories are the way to go because they can contain logic that is necessary to translate between different concerns (e.g. persistence -> domain or domain -> service bus). Making heavy use of mappers that utilize setters based on some annotations or property names just creates close coupling and enforces mutable state, which is imho the worst and most used anti pattern in OOP Languages like Java or C#. They seem to make life easier, but the more complex an application gets, the more hard to find bugs you will run into because of illegal invariants that mess up your code.
@nemanja.djordjevic
@nemanja.djordjevic Жыл бұрын
@@yeziniait is wrong usage of mappers. Only map from domain to dto, never other way around. Mapping from dto to domain is like using hammer 🔨 to cut the tree 🌲
@a-s733
@a-s733 Жыл бұрын
Totally agree. AutoMapper, especially when I map Dtos and needs expressoins was kind of headache. At least for me. As Germain said, "kleiner aber meiner" is the best approach:)
@rapzid3536
@rapzid3536 Жыл бұрын
Recently I've been starting small with mapping; FromEntity ToEntity static methods on my DTOs. If that starts getting out of hand I'll organize them differently. KISS.
@fullstackdotnet
@fullstackdotnet Жыл бұрын
Good to see that i have had the same sort of journey
@b4ux1t3-tech
@b4ux1t3-tech Жыл бұрын
Man, for the first one, I started saying "that's a good idea... Except it ends up being hard to guarantee an order, which could be super problematic" Then, bam, you said basically that. We use extension methods in our app, which is something that I think most nuget packages have adopted over the years.
@DemoBytom
@DemoBytom Жыл бұрын
I envy you writing mappers once :O In our code base models tend to change, either on the storage side, or the DTO side, and mappers constantly need to be updated.. Well not every day, but it's still a PITA sometimes. There's always a mapper somewhere, someone forgets to update when new properties are added... Now AutoMapper or similar has introduced other pain points, so weren't a silver bullet either... :D
@dovh49
@dovh49 Жыл бұрын
I've created a unit testing library that will test for truthy for mapping and will make sure that all those changes will be handled if something is changed. I have to see if my work will let me open source it.
@bfg5244
@bfg5244 Жыл бұрын
Probably, following CQRS Nick validates only write model. When reading from reliable DB we may assume all objects are valid (given versioning done right) so there would be no need to spent time validating them.
@osamaal-rashed3589
@osamaal-rashed3589 Жыл бұрын
Finally 😍 manual mapping is the best
@Sunesen
@Sunesen Жыл бұрын
One the best practices that I've more or less thrown away is "Clean Architecture" in favor of "Vertical Slice Architecture." The whole concept where you build your program around features where each feature has its own classes and objects and logic that it doesn't share with other features like in clean architecture is a major gamechanger for me. It means I have cut way down on the amount of unit tests that I need, I do not need to worry that refactoring or implementing new features or changing features will randomly break other features. In the same way, I've also become a lot more lenient on the DRY principle of "Don't repeat yourself". I'm perfectly happy with making helper methods fore mundane things that multiple parts of the code can make use of as mundane things usually don't change. But core logic of features, how they get and handle data, I am perfectly fine with implementing multiple times, using almost the same code and almost the same objects, just so I know that I can alter a feature all I want in the future without having to worry about breaking a ton of other features by accident.
@KhaUh
@KhaUh Жыл бұрын
is there a resource for "Vertical Slice Architecture"? i would like to know more about it
@liski12
@liski12 Жыл бұрын
@@KhaUh I think they mean that they prefer Clean Architecture over Vertical Slice Architecture. Could be wrong, though.
@Velociapcior
@Velociapcior Жыл бұрын
This is the way. Amount of people fixated around DRY is astounding. To the point when they try to apply DRY to things which are from a different context. Vertical slices all the way
@bfg5244
@bfg5244 Жыл бұрын
To be fair, in general, these are non contradictory to each other. You can have a slice organized in clean arch. style - as long as you see DDD is applicable and beneficial for a particular project.
@adambickford8720
@adambickford8720 Жыл бұрын
I think this is terrible advice. If you have a need for this then its unlikely you have '1 authoritative definition of a concept in a system'. I'm not saying DRY is the end-all be-all, but if you find yourself doing it more than a couple times in a project you likely botched an abstraction.
@benjaminschug5572
@benjaminschug5572 Жыл бұрын
Writing your own mapper comes with its own pitfalls though: If you add a property to the object and forget to update the mapper, it may fail in a very subtle way when that property is simply not being set in the target type. This can happen especially easily when you need to map between two protobuf messages (e.g. when you want to bridge between two separate grpc services). One thing you can do to catch these errors is to do a round-trip-test: Map from A to B and back to A and assert that the two As are identical.
@tommaple
@tommaple Жыл бұрын
1. I don’t use AutoMapper. 2. I don’t use Mediator. 3. I group classes by context instead of type. 4. I don’t use interceptors. 5. I use mostly read-only collection interfaces for method parameters and output types. 6. I usually use .ToArray() instead of .ToList(), unless the collection is going to be modified. 7. I don’t use DI container auto-registrations (I used before an abstract interface like IAutoRegister, so all the derived interfaces were automatically registered in container with their implementations). 8. I don’t use exceptions for expected cases. 9. I avoid dockerize project by default, especially to run the projects in docker locally. 10. I avoid to use external libraries for very simple functionalities that I can code myself (you never know how long they will be supported).
@Time21
@Time21 Жыл бұрын
can you extend a bit more 6?
@tommaple
@tommaple Жыл бұрын
​@@Time21 Sure! List is a wrapper on an array T[] that gives the ability of easily adding and removing elements, by doing the operations like copying items to a bigger array or moving items affected by Insert() or Remove() operations. If you don’t need any of these, there’s no point of using this wrapper but just array directly. This saves some extra memory and the time for initialization of this wrapper (List)-.ToArray() is slightly faster then .ToList() It also limits slightly what can be done with that collection Example: var deletedUsers = allUsers.Where(x => x.IsDeleted).ToArray(); returns a collection that (most likely) shouldn’t be modified (no items should be added or removed). Of course, array doesn’t prevent of replacing some of their items with others or with null, but at least it looks more suspicious than a regular .Add() or Remove(). But, I still use List, HashSet or other collections when it makes sense.
@Kingside88
@Kingside88 Жыл бұрын
Hey Nick. Good video! I just say something to the last point. I saw many code where if conditional fails, they jusz return null. So you never know why. Than you get the point, throughing exceptions is bad because maybe the user does'nt provided correct order values. Iny opinion the best think is as you mentioned Fluentvalidation together with oneOf and cultureCode. So you also can provide an user-friendly message
@saeedbarari2207
@saeedbarari2207 Жыл бұрын
totally agree with the "More explicit, less magic" 👍👍👍
@amrosamy8232
@amrosamy8232 Жыл бұрын
Totally agree with mappers and di approaches
@Michel000000001
@Michel000000001 Жыл бұрын
Whoot! the first time in decades I hear someone not being happy with (auto*)mapper. For all the reasons you say! Welcome to mapper-library free world! (unfortunately, this world only contains you and me :-)). On every project everyone is lyrical about (mostly) automapper. And more often than not; no tests.
@alexandernava9275
@alexandernava9275 Жыл бұрын
Exceptions all the way. Using OneOf I can often fix what is going on bye handling that specific return type. Where throwing Exception makes it branch off to much. Even if it is a simple Debug.Assert for that return type.
@theyur
@theyur Жыл бұрын
I think, if you are not throwing in the constructor in case of inappropriate parameters, you create object with unpredictable state. Consumers will rely on that object but it will not work as expected, so the fail will not be fast. Cause of error will be dig inside the stack trace.
@MetalKid007
@MetalKid007 Жыл бұрын
That's why you have validation in front so the class never gets created if the data was invalid to begin with.
@theyur
@theyur Жыл бұрын
@@MetalKid007 Agree, however validation and guards have different purposes
@bfg5244
@bfg5244 Жыл бұрын
Yes, but since Nick haven't yet described what objects he stop protecting by guards with, we could only guess. For example, a DTO that is coming from a database and served only as source for http response body presumable could be treated as being in a "valid" state, so guards might be omitted.
@adambickford8720
@adambickford8720 Жыл бұрын
@@theyur I see no difference whatsoever besides some dogmatic 'intent'.
@Cool-Game-Dev
@Cool-Game-Dev 9 ай бұрын
For your installers, you could probably use some sort of attribute to sort it. Like a [RegistrationOrderAttribute]
@ricmrodrigues
@ricmrodrigues Жыл бұрын
Agree on everything but a bit divided on the guard clause. I do agree exceptions are a bit heavy given you need to ensure handling, have performance costs, etc. But it is the idiomatic way, how do you ensure your class has all the dependencies it needs and fail if not without throwing?
@nickchapsas
@nickchapsas Жыл бұрын
You just make sure that it can only be created if it goes through a very explicit pipeline. In my case it always HAS to go through a validator to exist, and it's immutable so I don't have to worry about it changing
@Endomorphism
@Endomorphism Жыл бұрын
These 3 point sums up to Just one "little magic, more discoverability". Registering services automatically // overiously autoMapper // by default automatically maps properties, if a property is missing, you wouldn't know, no control Exceptions // hidden interface After coming up with the same conclusions, in 2022 I moved to FP🙂
@namewastaken360
@namewastaken360 6 ай бұрын
Exceptions should not be used when validating input as this is a regular flow, but guard clauses (ie asserts) are good when a parameter really shouldn't null null or whatever to fail early.
@BinaryNexus
@BinaryNexus Жыл бұрын
Nick, i would love to know how you do telemetry or track performance in a production environment. Just a neat video idea i hope.
@jameshancock
@jameshancock Жыл бұрын
The issue with mapping is taking client filtering and converting it to the original source. Mapping isn't just object to dto and visa-versa. It's also query/order/group dto to object.
@evaldaszmitra7322
@evaldaszmitra7322 Жыл бұрын
I have a confession to make. I started loving code generation. To the point where I actually prefer it over generics, interfaces, actions, functions, reflection, casting to object or whatever type of workaround is normally used to avoid it. It's crazy, but I was writing a library utilizing all of these methods. I spent so much time trying to fit all of the pieces together, constraining types, casting to object to make the pieces fit together and then once the complicated beast was running debugging it was really difficult. The worst part - all of the error were runtime errors! Then I basically said fk it and wrote generated code solution. Surprisingly, code just worked first time. And it not only worked, it was: Really fast. Easier to debug. Easier to make changes to the generator than normal code. Super simple. There were no complicated abstraction. If someone knows functions, classes, variables, arithmetic and loops, they can understand the code! I know that in theory this is really bad to do. But idk why does it work so well in practice?
@lmoelleb
@lmoelleb Жыл бұрын
I like it as well. The source generators are so much better than the old T4 templates. But it does add another layer of abstraction that can explode complexity - you are no longer coding a model of your domain, you are coding a model of a model of the domain. As long as it is really simple stuff (mapping one to one'ish) it is doable. Transforming one model (the domain) to a different structure (the UI which had sub-screens etc not present in the domain) was still doable, but you had to keep your concentration up, and juniors where struggling, so for me that is probably the limit.
@chrisnuk
@chrisnuk 9 ай бұрын
Thanks for the advice ❤
@JohnEBoldt
@JohnEBoldt Жыл бұрын
So the theme of this is: prefer visibility over magic. I agree with that. The one place I disagree with is throwing exceptions in the constructor. I agree that there should be validation before the class is constructed, and these validations should return a response instead of an exception. Domain classes need to protect their state. This means that any invalid state values passed in the constructor or initialization method should throw an exception AS A LAST RESORT. The domain class should not assume that previous logic will ensure that everything's ok. To me, this violates the temporal coupling principle, meaning that some other class will take care of the validation that should reside in the domain class. I equate it to how the .Net framework works: if I try to access an object that is null, the framework will throw a null reference exception, which is messy, but it's a last resort that addresses my failure to properly ensure that the object was not null in the first place. It's better to throw an exception than to continue in an invalid state.
@davemasters
@davemasters Жыл бұрын
Agree with all, but curious about your last example of Guard clauses. Presumably the Order object is a domain entity? How else can you ensure the object only exists with valid invariants without the clauses? I tend to use result objects in my command handlers/validators, so that the Guard clauses in entities only throw by exception - i.e. there is a bug with validation.
@nickchapsas
@nickchapsas Жыл бұрын
The only way the object can be created in by going through a validator. There is no other path
@superpcstation
@superpcstation Жыл бұрын
Would love to see a demo on that guard clause thing
@Michel000000001
@Michel000000001 Жыл бұрын
Good insight: visibility over magic
@the.ansarya
@the.ansarya 7 ай бұрын
Thank you for answering a question I've always had: "During registration why do I have to enumerate every service? The framework can see all my DI injectable classes, why isn't this automated?"
@ardavaztterterian1211
@ardavaztterterian1211 Жыл бұрын
I like the first one, but I do it using extension methods which provides the visibility you are talking about.
@carldaniel6510
@carldaniel6510 Жыл бұрын
As a developer for over 40 years, I have to agree with each of these. For me, it comes down to this: less magic, more explicit. Magic makes it easier for a new developer to build something of reasonable quality, but in the long run, explicit is better. I maintain and extend code that's 20+ years old all the time and guess what - all of the magic solutions that are invented over the decades fall by the wayside over time and are replaced with newer, fanicer magic, but straightforward explicit code remains servicable forever - without having to remember how 5 generations of magic worked.
@wknight8111
@wknight8111 Жыл бұрын
I've never really liked Mapper libraries. Automap or Mapperly or whatever. Like you said the errors in mapping logic get transferred from Compile Time to Run Time. But there is another problem that I think is even worse: Because configuring custom mapping logic for non-identical representations is so much harder with these libraries developers are incentivized to keep the separate classes closer in structure than they otherwise would be. Domain entities cannot evolve separately from data models or DTO/contract types in the way that they should. Some of the worst systems end up with multiple copies of what is basically the same exact class, none of which properly serves the needs of it's layer. Mapping classes are generally so easy to write and mappers of aggregate types can be so easily composed from mappers of child types, that there's really no reason not to do it.
@michalbolehradsky9709
@michalbolehradsky9709 Жыл бұрын
I really like this statement "More visibility, less magic!"
@dennycrane2938
@dennycrane2938 Жыл бұрын
I went through the same evolution on my own and I mostly agree except, ctors cant return discriminated unions. And an object should fully usable after it is initialized so you should protect that. The only other way I can think of solving that is factories and that might be overkill
@megadesty
@megadesty Жыл бұрын
You spoke to my soul on all points!
@lykeuhfox4597
@lykeuhfox4597 Жыл бұрын
I've cooled from mappers as well. Doing it myself is fast and I know exactly what's happening.
@Spirch
@Spirch Жыл бұрын
automapper also break the "find all reference" pattern, i hate seeing 1 property with 0 reference while it's being used all over the place
@DavidČubela
@DavidČubela Жыл бұрын
Concearning mapping: Personally I think manual mapping is a huge hastle. You have to manually add properties when you extend objects, manually change logic and so on. In most cases, except renaming properties, the problems are the same as AutoMapper. No compile time error, even worse, runtime the property gets ignored. Another thing is: AutoMapper has ProjectTo IQueryable extension method which optimazes selects and selects only what is needed for an object. That is a huuuge bonus compared to doing every optimization manually. Also, mappers all work in the same way, and the configurations should all be in the same place ( 1 config per model, 1 entity model to multiple DTO/VM) so you can see it all. In any case, each style requires some proper setup to be effective
@StrandedKnight84
@StrandedKnight84 Жыл бұрын
Agree with all of these! Less magic is good. I do dependency injection the same way. I have a bunch of DB repositories that I mock for my unit tests, and I can just do .AddMockedRepositories() in my XUnit test fixture setup. It makes the code both clean and explicit. I have used Mapster in previous projects, but only as a convenience when doing 1-to-1 mapping for DTOs. In my current project I have AutoMapper for everything, but I'm in the process of replacing it with regular constructors for the more complex mapping tasks. Not a fan of guard clauses in constructors either.
@maherhujairi1601
@maherhujairi1601 Жыл бұрын
I introduced the he custom static mappers methods approach to the company I work at 10 years ago . they liked it and it streamed lined the work although it does take more time to code than auto mapper but it defiantly help . the only draw back I heard was because it was a static extension method find the code can be trick at some times. So I shifted to use an IMapper and inject the mappers in .. this is my own interface that I can use any implementation method I want such as an Auto Mapper or a custom mapping code.
What's New in .NET 9 with Examples
25:02
Nick Chapsas
Рет қаралды 60 М.
Settling the Biggest Await Async Debate in .NET
14:47
Nick Chapsas
Рет қаралды 147 М.
Арыстанның айқасы, Тәуіржанның шайқасы!
25:51
QosLike / ҚосЛайк / Косылайық
Рет қаралды 653 М.
Thank you Santa
00:13
Nadir Show
Рет қаралды 59 МЛН
黑天使只对C罗有感觉#short #angel #clown
00:39
Super Beauty team
Рет қаралды 31 МЛН
Reviewing your "Best Practices" in C#
12:14
Nick Chapsas
Рет қаралды 43 М.
"Stop Using Async Await in .NET to Save Threads" | Code Cop #018
14:05
.NET and C# are in trouble. Here is what I'd do.
10:57
Ed Andersen
Рет қаралды 112 М.
The Logging Everyone Should Be Using in .NET
15:34
Nick Chapsas
Рет қаралды 87 М.
how Google writes gorgeous C++
7:40
Low Level
Рет қаралды 966 М.
Don't Use AutoMapper in C#! Do THIS Instead!
16:17
Codewrinkles
Рет қаралды 71 М.
Awesome new features in C# 13 for .NET 9!
10:50
Ed Andersen
Рет қаралды 12 М.
Microservices are Technical Debt
31:59
NeetCodeIO
Рет қаралды 674 М.
My 10 “Clean” Code Principles (Start These Now)
15:12
Conner Ardman
Рет қаралды 298 М.
The New Way of Parsing ANY Type in .NET
13:03
Nick Chapsas
Рет қаралды 70 М.
Арыстанның айқасы, Тәуіржанның шайқасы!
25:51
QosLike / ҚосЛайк / Косылайық
Рет қаралды 653 М.