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

Sdílet
Vložit
  • čas přidán 14. 05. 2023
  • 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/nickworkshops
    Don't forget to comment, like and subscribe :)
    Social Media:
    Follow me on GitHub: bit.ly/ChapsasGitHub
    Follow me on Twitter: bit.ly/ChapsasTwitter
    Connect on LinkedIn: bit.ly/ChapsasLinkedIn
    Keep coding merch: keepcoding.shop
    #csharp #dotnet

Komentáře • 321

  • @bradenb
    @bradenb Před rokem +36

    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 Před 11 měsíci +2

      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.

    • @Sahuagin
      @Sahuagin Před 10 měsíci +2

      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 Před 10 měsíci +3

      @@Sahuagin 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 Před 5 měsíci +2

      KISS..
      Your worst enemy is overengineering. The best code is no code.

    • @pfom
      @pfom Před 9 dny

      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.

  • @Fred-yq3fs
    @Fred-yq3fs Před rokem +173

    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 Před rokem +17

      This is my approach as well

    • @sandervanteinde9352
      @sandervanteinde9352 Před rokem +14

      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 Před rokem +6

      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 Před rokem +4

      @@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 Před rokem +2

      Same approach. Plus I don't need to write unit test for "wrong" branch (corporate policy to cover most of code paths).

  • @dsuess
    @dsuess Před 6 měsíci +7

    (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

  • @nmarcel
    @nmarcel Před rokem +96

    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 Před rokem +6

      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 Před rokem +1

      @@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 Před rokem +1

      ​@@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 Před rokem

      @@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 Před rokem +6

      "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.

  • @Pookzob
    @Pookzob Před rokem +33

    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 Před 7 měsíci +1

      Alone in the camp? It's nearly a universal truth.

    • @Pookzob
      @Pookzob Před 7 měsíci

      @@joshpatton757 it is, but I rarely see people online talk or write about it. Maybe I've been surrounded by expert beginners :)

  • @minnesotasteve
    @minnesotasteve Před rokem +43

    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 Před rokem +2

      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 Před rokem +1

      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:
      czcams.com/video/baY3SaIhfl0/video.html

    • @DryBones111
      @DryBones111 Před rokem

      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.

  • @StephenMoreira
    @StephenMoreira Před rokem +38

    Love seeing how other developers evolve in their practices. Thanks for sharing.

    • @quetzalcoatl-pl
      @quetzalcoatl-pl Před rokem

      It's also quite reassuring to see others evolving their views/beliefs in the same direction as oneself ;)

  • @bfg5244
    @bfg5244 Před rokem +24

    Nick, provide an example of how nowadays you validate invariants instead of guard clauses (of any kind).

    • @mDoThis
      @mDoThis Před rokem +1

      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 Před 9 měsíci

      @@mDoThisYou can not use fluent validation to validate invariants for domain object.

  • @AnythingGodamnit
    @AnythingGodamnit Před 10 měsíci +3

    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!

  • @rzaip
    @rzaip Před rokem +65

    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 Před rokem +7

      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 Před rokem +1

      You're almost certainly obscuring some other bug and creating a very tight coupling that's inherently inflexible. Fix the calling code.

    • @PelFox
      @PelFox Před rokem +6

      @@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 Před rokem +2

      @@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 Před rokem +5

      @@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.

  • @valera924
    @valera924 Před rokem +20

    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 Před rokem +1

      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 Před rokem

      and how does stack trace helps in very specific case of validation?

    • @adambickford8720
      @adambickford8720 Před rokem +4

      @@bfg5244 Its null and it shouldn't be. What chain of code got us to this point?

    • @paulkoopmans4620
      @paulkoopmans4620 Před rokem +1

      @@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 Před rokem +4

      ​@@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.

  • @ettiennedebeer9174
    @ettiennedebeer9174 Před rokem +13

    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.

  • @RobinHood70
    @RobinHood70 Před rokem +8

    Nick: Hello, everybody. I'm Nick and in this video...
    Captions: hello everybody I'm naked in this video...

  • @dxs1971
    @dxs1971 Před rokem +8

    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.

  • @orterves
    @orterves Před rokem +3

    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)

  • @roderick9665
    @roderick9665 Před rokem +1

    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.

  • @CarlosRodriguez-gh1ow
    @CarlosRodriguez-gh1ow Před rokem +2

    Nice video! Agree with them all. It's nice to see that we evolved in similar ways

  •  Před rokem +4

    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.

  • @nickandrews1985
    @nickandrews1985 Před rokem +3

    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!

  • @gveresUW
    @gveresUW Před rokem +1

    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.

  • @jfpinero
    @jfpinero Před 9 měsíci +1

    You can always do your own manual mapping in converters (automapper term) and use the basic mapping for contracts you know won't change.

  • @olegvorkunov5400
    @olegvorkunov5400 Před rokem +14

    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 Před rokem +4

      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 Před rokem

      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 Před rokem

      @@Vinxian1 You can use a private constructor and a static factory method.

  • @dannykempkes4957
    @dannykempkes4957 Před rokem

    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

  • @brendanalexander6053
    @brendanalexander6053 Před rokem +1

    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.

  • @nofatchicks6
    @nofatchicks6 Před rokem

    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.

  • @MariosKimonos
    @MariosKimonos Před rokem

    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.

  • @philosophersam
    @philosophersam Před rokem

    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.

  • @Les1aw82
    @Les1aw82 Před rokem

    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 )

  • @Dustyy01
    @Dustyy01 Před rokem +1

    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.

  • @Bundynational
    @Bundynational Před rokem +11

    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 Před 11 měsíci

      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

  • @anreton
    @anreton Před rokem

    Hey Nick.
    The video reveals important things.
    I, too, over time came to the same decisions and conclusions, because they are more effective.

  • @andreistelian9058
    @andreistelian9058 Před rokem +1

    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

  • @robadobdob
    @robadobdob Před rokem

    I went for the extension method approach for wiring up services and it is simple and readable.

  • @CarlosWashingtonMercado

    Man, that was a wonderful video. Thank you for making it.

  • @alphaios7763
    @alphaios7763 Před rokem

    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

  • @dnwheeler
    @dnwheeler Před rokem +2

    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.

  • @ardavaztterterian1211

    I like the first one, but I do it using extension methods which provides the visibility you are talking about.

  • @angelowolff7127
    @angelowolff7127 Před rokem +1

    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.

  • @tomazkoritnik4072
    @tomazkoritnik4072 Před rokem

    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.

  • @nanvlad
    @nanvlad Před rokem +1

    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.

  • @Palladin007
    @Palladin007 Před rokem +1

    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_ Před rokem

      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 Před rokem

      @@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_ Před rokem

      @@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 Před rokem

      @@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_ Před rokem +1

      @@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

  • @chrisnuk
    @chrisnuk Před 3 měsíci

    Thanks for the advice ❤

  • @Cethris
    @Cethris Před 10 měsíci

    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) 👍

  • @codesafariDev
    @codesafariDev Před rokem +8

    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 Před rokem +11

      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 Před rokem

      @@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 Před rokem

      @@CesarDemi81 I feel like once you get into vertical slice architecture you basically have no need for MediatR.

    • @moneymaker7307
      @moneymaker7307 Před rokem

      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 Před rokem

      @@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.

  • @majormartintibor
    @majormartintibor Před rokem

    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.

  • @jackp3303
    @jackp3303 Před rokem +1

    Thanks Nick!
    How do you think, is it possible that in some future Microsoft will optimize their implementation of overriding cast methods? I mean today I will wrote some 'public static explicit operator MyClass', then do map smth in the code using casting, right now it's not extremly efficient, but I hope in future it will be, because it's built in C# feature...

  • @emanuelrodriguez3155
    @emanuelrodriguez3155 Před rokem

    Love the video, quick question? How do yo manage mappers with EF Core without getting null reference to objects inside main object mapping?? Ergo a class inside the class

  • @andriiyustyk9378
    @andriiyustyk9378 Před 10 měsíci

    What do you think about type conversion operators in DTOs for the mapping?

  • @brucerosner3547
    @brucerosner3547 Před 8 měsíci

    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.

  • @CraigBoland
    @CraigBoland Před rokem +3

    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 Před rokem +1

      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.

  • @tanglesites
    @tanglesites Před rokem +1

    This was really good! I think too, your style choices also depend on where you are at in your programming walk (lol, like it's church, sorry Hozier). I am constantly creating new projects and testing things out, learning what I like better than other choices, and seeing how things work. And .NET has a lot of boilerplate so I usually go the route of whatever causes me to write less code. I don't use mappers, in my personal projects, my domain objects are not big enough to warrant a mapper. I still use markers and assembly scanning, it's more time efficient. I do use MediatR, but am looking into Wolverine and moving away from that altogether. Otherwise, I keep my ears open. One of the Realest Engineers out here. 💯 PS: When is your next live stream?

  • @raresiova5546
    @raresiova5546 Před rokem

    For hte first one, just group the stuff that needs to be ordered in the same installer?

  • @a-s733
    @a-s733 Před rokem

    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:)

  • @adrian_franczak
    @adrian_franczak Před rokem

    Yeah extension methods are great personally I would extend builder so I don’t have to pass configuration as param and have more flexibility but this is also good - if you don’t like exceptions try rust :D

  • @Kingside88
    @Kingside88 Před rokem

    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

  • @fullstackdotnet
    @fullstackdotnet Před rokem

    Good to see that i have had the same sort of journey

  • @joshpatton757
    @joshpatton757 Před 7 měsíci

    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.

  • @shreyasjejurkar1233
    @shreyasjejurkar1233 Před rokem

    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 ❣.

  • @aj0413_
    @aj0413_ Před rokem +2

    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.

  • @megadesty
    @megadesty Před rokem

    You spoke to my soul on all points!

  • @ronsijm
    @ronsijm Před rokem +1

    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

  • @boguslawkudyba6034
    @boguslawkudyba6034 Před rokem

    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!

  • @mrcarlpaton
    @mrcarlpaton Před 11 měsíci

    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.

  • @rapzid3536
    @rapzid3536 Před rokem

    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.

  • @BinaryNexus
    @BinaryNexus Před rokem

    Nick, i would love to know how you do telemetry or track performance in a production environment. Just a neat video idea i hope.

  • @florianmaul9983
    @florianmaul9983 Před rokem

    Wow the first two i also learned with in my carrer.
    Visibily always over magic. And i totally removed automapper. 👍
    Automapper also becomes a hell when the properties cannot be mapped one to one.

  • @superpcstation
    @superpcstation Před rokem

    Would love to see a demo on that guard clause thing

  • @StrandedKnight84
    @StrandedKnight84 Před rokem

    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.

  • @amrosamy8232
    @amrosamy8232 Před rokem

    Totally agree with mappers and di approaches

  • @maherhujairi1601
    @maherhujairi1601 Před rokem

    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.

  • @Sunesen
    @Sunesen Před rokem +10

    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.

    • @KhauTek
      @KhauTek Před rokem

      is there a resource for "Vertical Slice Architecture"? i would like to know more about it

    • @liski12
      @liski12 Před rokem

      @@KhauTek I think they mean that they prefer Clean Architecture over Vertical Slice Architecture. Could be wrong, though.

    • @Velociapcior
      @Velociapcior Před rokem +2

      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 Před rokem +2

      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 Před rokem +2

      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.

  • @jameshancock
    @jameshancock Před rokem

    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.

  • @xlerb2286
    @xlerb2286 Před 11 měsíci +1

    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. ;)

  • @marcusmajarra
    @marcusmajarra Před rokem

    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.

  • @lykeuhfox4597
    @lykeuhfox4597 Před rokem

    I've cooled from mappers as well. Doing it myself is fast and I know exactly what's happening.

  • @DemoBytom
    @DemoBytom Před rokem +3

    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 Před rokem

      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.

  • @C00l-Game-Dev
    @C00l-Game-Dev Před 2 měsíci

    For your installers, you could probably use some sort of attribute to sort it. Like a [RegistrationOrderAttribute]

  • @billy65bob
    @billy65bob Před rokem +1

    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...

  • @seroamado6283
    @seroamado6283 Před rokem

    I don't get why you wouldnt have guard clauses that check for null values in the constructor, if the class isn't set up right, it should notify that to the user,
    Am I missing something here?

  • @Andrei-gt7pw
    @Andrei-gt7pw Před rokem +6

    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 Před rokem +1

      Software is usually built 3 to 4 classes at a time.

    • @robertmrobo8954
      @robertmrobo8954 Před rokem

      @@minnesotasteve
      Exactly, all the complex systems I have worked on had 4 classes at max.

    • @minnesotasteve
      @minnesotasteve Před rokem +4

      @@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 Před rokem

      Looks like some tasks for interns!

  • @alexandernava9275
    @alexandernava9275 Před rokem

    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.

  • @jacobstamm
    @jacobstamm Před rokem

    I would like to have seen what the alternative to exception-based guard clauses looks like. I’m having a hard time picturing a design that doesn’t introduce more complexity at call sites.

  • @bfg5244
    @bfg5244 Před rokem +1

    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.

  • @ashbjorn
    @ashbjorn Před rokem

    Hello Nick, you recently did a video about the IParsable interface, I was curious if from your perspective this could tie into your number 2 caveat? Meaning, would you consider using IParsable as a way to 'document' your manual mapping between instance types?

    • @nickchapsas
      @nickchapsas  Před rokem +1

      Parsing a string and object to object mapping are different concepts

    • @ashbjorn
      @ashbjorn Před rokem

      @@nickchapsas Ah my bad, thx; somehow I totally missed the part that it was for string parsing only 😕

  • @Imploser
    @Imploser Před 11 měsíci

    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.

  • @PierreGadea
    @PierreGadea Před rokem +2

    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 Před 11 měsíci

      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 Před 9 měsíci

      @@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 🌲

  • @benjaminschug5572
    @benjaminschug5572 Před 7 měsíci

    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.

  • @davemasters
    @davemasters Před rokem

    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  Před rokem

      The only way the object can be created in by going through a validator. There is no other path

  • @StarfoxHUN
    @StarfoxHUN Před rokem

    Somewhat controlversial
    ""practice"" that i basically never see anyone use, yet it improve visibility and safety so much i think:
    (Almost) Always set arguments by name when call a method. Its just feels so much safer when i look at the method and see which argument set to what and i do not have to check manually. It especially great when a method has multiple arguments that are the same type. Only time i skip it when the method has only has one argument. (I actually even ruined a function before i started this that has a lot (10+) arguments and around 6/7 was an int and i messed up the order. It was fine on the test enviroment as we used the same values for that, but broke for the clients after release. It was a really sad mistake that caused a lot of trouble keeping clients ok before the next release fixed it)

  • @MrVarsium
    @MrVarsium Před rokem

    twice the fun for me today, getting a live workshop and a tutorial on youtube, nice ! thx

  • @wknight8111
    @wknight8111 Před 11 měsíci +1

    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.

  • @evaldaszmitra7322
    @evaldaszmitra7322 Před rokem +3

    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 Před rokem

      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.

  • @Michel000000001
    @Michel000000001 Před rokem

    Good insight: visibility over magic

  • @tommaple
    @tommaple Před rokem

    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 Před rokem

      can you extend a bit more 6?

    • @tommaple
      @tommaple Před rokem

      ​@@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.

  • @dennycrane2938
    @dennycrane2938 Před rokem

    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

  • @the.ansarya
    @the.ansarya Před měsícem

    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?"

  • @b4ux1t3-tech
    @b4ux1t3-tech Před rokem

    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.

  • @Endomorphism
    @Endomorphism Před rokem

    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🙂

  • @saeedbarari2207
    @saeedbarari2207 Před rokem

    totally agree with the "More explicit, less magic" 👍👍👍

  • @amitkumdixit
    @amitkumdixit Před rokem

    I thought I was the only one to stop using auto mapper. I always followed explicitly registration of services using extension method.
    Also mapping using extension method is more efficient