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

Поделиться
HTML-код
  • Опубликовано: 5 янв 2025

Комментарии • 328

  • @dsuess
    @dsuess Год назад +12

    (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

  • @minnesotasteve
    @minnesotasteve Год назад +47

    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 Год назад +3

      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 Год назад +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:
      ruclips.net/video/baY3SaIhfl0/видео.html

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

  • @Pookzob
    @Pookzob Год назад +35

    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 Год назад +1

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

  • @bradenb
    @bradenb Год назад +48

    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 Год назад +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.

    • @Ashalmawia
      @Ashalmawia Год назад +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 Год назад +3

      @@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 Год назад +3

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

    • @pfom
      @pfom 7 месяцев назад

      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.

  • @smorebytes
    @smorebytes Год назад +41

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

  • @nmarcel
    @nmarcel Год назад +101

    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 Год назад +7

      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 Год назад +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 Год назад +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 Год назад

      @@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 Год назад +7

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

  •  Год назад +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.

  • @Fred-yq3fs
    @Fred-yq3fs Год назад +182

    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 Год назад +17

      This is my approach as well

    • @sandervanteinde9352
      @sandervanteinde9352 Год назад +15

      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 Год назад +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 Год назад +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 Год назад +2

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

  • @Palladin007
    @Palladin007 Год назад +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_ Год назад

      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_ Год назад +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

  • @orterves
    @orterves Год назад +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)

  • @nickandrews1985
    @nickandrews1985 Год назад +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!

  • @dxs1971
    @dxs1971 Год назад +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.

  • @AnythingGodamnit
    @AnythingGodamnit Год назад +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!

  • @gveresUW
    @gveresUW Год назад +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 Год назад +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 Год назад +15

    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 Год назад +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 Год назад

      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.

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

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

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

  • @ettiennedebeer9174
    @ettiennedebeer9174 Год назад +14

    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 Год назад +9

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

  • @rzaip
    @rzaip Год назад +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 Год назад +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 Год назад +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 Год назад +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 Год назад +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 Год назад +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.

  • @roderick9665
    @roderick9665 Год назад +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 Год назад +2

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

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

  • @nanvlad
    @nanvlad Год назад +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.

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

  • @angelowolff7127
    @angelowolff7127 Год назад +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.

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

  • @andreistelian9058
    @andreistelian9058 Год назад +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

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

  • @valera924
    @valera924 Год назад +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 Год назад +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 Год назад

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

    • @adambickford8720
      @adambickford8720 Год назад +4

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

    • @paulkoopmans4620
      @paulkoopmans4620 Год назад +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 Год назад +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.

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

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

  • @dnwheeler
    @dnwheeler Год назад +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.

  • @NeddySmithzzzz
    @NeddySmithzzzz 6 месяцев назад

    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.

  • @Brendan2Alexander
    @Brendan2Alexander Год назад +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.

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

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

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

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

  • @bfg5244
    @bfg5244 Год назад +24

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

    • @mDoThis
      @mDoThis Год назад +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 Год назад

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

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

  • @robadobdob
    @robadobdob Год назад

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

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

  • @Dustyy01
    @Dustyy01 Год назад +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.

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

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

  • @CraigBoland
    @CraigBoland Год назад +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 Год назад +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.

  • @Cool-Game-Dev
    @Cool-Game-Dev 10 месяцев назад

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

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

  • @aj0413_
    @aj0413_ Год назад +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.

  • @CarlosWashingtonMercado
    @CarlosWashingtonMercado Год назад

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

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

  • @codesafariDev
    @codesafariDev Год назад +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 Год назад +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 Год назад

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

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

  • @billy65bob
    @billy65bob Год назад +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...

  • @Andrei-gt7pw
    @Andrei-gt7pw Год назад +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 Год назад +1

      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 Год назад +5

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

  • @xlerb2286
    @xlerb2286 Год назад +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. ;)

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

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

  • @DemoBytom
    @DemoBytom Год назад +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 Год назад

      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.

  • @PierreGadea
    @PierreGadea Год назад +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 Год назад

      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 🌲

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

  • @digitalpacman
    @digitalpacman Год назад +1

    For mappers - what's wrong with configuration testing that automatically verifies that _every_ field is mapped in both directions? Obviously only for when it's applicable.

  • @fullstackdotnet
    @fullstackdotnet Год назад

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

  • @ronsijm
    @ronsijm Год назад +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

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

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

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

  • @the.ansarya
    @the.ansarya 8 месяцев назад

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

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

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

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

  • @ardavaztterterian1211
    @ardavaztterterian1211 Год назад

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

  • @bfg5244
    @bfg5244 Год назад +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.

  • @adrian_franczak
    @adrian_franczak Год назад

    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

  • @Bundynational
    @Bundynational Год назад +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 Год назад

      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

  • @amrosamy8232
    @amrosamy8232 Год назад

    Totally agree with mappers and di approaches

  • @seroamado6283
    @seroamado6283 Год назад

    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?

  • @saeedbarari2207
    @saeedbarari2207 Год назад

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

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

  • @theyur
    @theyur Год назад +1

    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 Год назад +1

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

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

  • @florianmaul9983
    @florianmaul9983 Год назад

    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.

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

  • @lykeuhfox4597
    @lykeuhfox4597 Год назад

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

  • @raresiova5546
    @raresiova5546 Год назад

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

  • @emanuelrodriguez3155
    @emanuelrodriguez3155 Год назад

    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

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

  • @osamaal-rashed3589
    @osamaal-rashed3589 Год назад +1

    Finally 😍 manual mapping is the best

  • @ahmedabd-a6
    @ahmedabd-a6 2 месяца назад

    I stopped use mapping framework long timr ago when a ptoperty auto-mapped in a way it shouldn't and caused a bug

  • @NigelStratton
    @NigelStratton 3 месяца назад

    I like the manual mapping that you only do once, but are you generating the initial run of that code? That stuff is so tedious to write.
    If so, how?

  • @leerothman2715
    @leerothman2715 8 месяцев назад

    Yep, agree with everything there. Stopped using mapping packages in favour of writing my own a while back. I just found that you ended up having lots of code when you didn’t want straight forward mapping. Another downside was that devs don’t often write unit tests when using packages for mapping and then when somebody just renamed a property then… well you know the result of that.
    As for the guarding it feels like defensive code to me? I don’t think you need defensive code in most scenarios if you’re testing properly. As explained validate before and don’t use exceptions for application flow.

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

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

  • @Michel000000001
    @Michel000000001 Год назад

    Good insight: visibility over magic

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

  • @wknight8111
    @wknight8111 Год назад +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.

  • @chrisnuk
    @chrisnuk 10 месяцев назад

    Thanks for the advice ❤