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

แชร์
ฝัง
  • เผยแพร่เมื่อ 26 พ.ย. 2024

ความคิดเห็น • 329

  • @dsuess
    @dsuess ปีที่แล้ว +11

    (1) 0:43 - 3:07 - Magical "DI Installer" using Reflection vs. Extension Method (Visibility, Order of Execution)
    (2) 4:46 - 6:57 - Not using a Mapper vs. manual approach
    (3) 7:50 - Guard clause
    This is a great video breakdown. Happy to see you take on some old fashion, more manual processes. Yes, i'm old fashion

  • @bradenb
    @bradenb ปีที่แล้ว +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 11 หลายเดือนก่อน +3

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

    • @pfom
      @pfom 6 หลายเดือนก่อน

      Is there some good piece of documentation, article or a book which helps defending the #3 point? I saw some solution projects which only contained a single installer from this video.

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

  • @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 ปีที่แล้ว +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 ปีที่แล้ว +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:
      th-cam.com/video/baY3SaIhfl0/w-d-xo.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.

  • @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 ปีที่แล้ว +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 ปีที่แล้ว +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).

  • @nmarcel
    @nmarcel ปีที่แล้ว +100

    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 ปีที่แล้ว +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 ปีที่แล้ว +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.

  • @StephenMoreira
    @StephenMoreira ปีที่แล้ว +40

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

  •  ปีที่แล้ว +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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • @RobinHood70
    @RobinHood70 ปีที่แล้ว +9

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

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

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

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

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

  • @twinkle-and-bosh
    @twinkle-and-bosh 3 หลายเดือนก่อน

    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!

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

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

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

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

  • @osamaal-rashed3589
    @osamaal-rashed3589 ปีที่แล้ว +1

    Finally 😍 manual mapping is the best

  • @CarlosRodriguez-gh1ow
    @CarlosRodriguez-gh1ow ปีที่แล้ว +2

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

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

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

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

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

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

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

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

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

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

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

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

  • @loganyoung228
    @loganyoung228 2 หลายเดือนก่อน

    I'm moving away from logical structuring in my projects (so no more Interfaces, Services, Models, etc. folders) in favor of vertical slicing. I don't even mind repeating code in each vertical slice, for instance, for my result objects, because I realized that doing so can actually give me a lot more flexibility in case I want to handle results differently in a particular area of my project.
    I did like the idea of the methodology for service installers, but then you talked about extension methods and I realized I'm already doing that anyway so I won't be trying to take on the installers approach.
    As for guard clauses, I normally just use conditional statements to verify state and then handle different scenarios or simply return a failed result back to the calling method.

  • @Bundynational
    @Bundynational ปีที่แล้ว +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

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

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

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

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

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

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

  • @NeddySmithzzzz
    @NeddySmithzzzz 4 หลายเดือนก่อน

    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.

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

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

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

  • @carldaniel6510
    @carldaniel6510 ปีที่แล้ว

    As a developer for over 40 years, I have to agree with each of these. For me, it comes down to this: less magic, more explicit. Magic makes it easier for a new developer to build something of reasonable quality, but in the long run, explicit is better. I maintain and extend code that's 20+ years old all the time and guess what - all of the magic solutions that are invented over the decades fall by the wayside over time and are replaced with newer, fanicer magic, but straightforward explicit code remains servicable forever - without having to remember how 5 generations of magic worked.

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

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

  • @robadobdob
    @robadobdob ปีที่แล้ว

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

  • @a-s733
    @a-s733 ปีที่แล้ว

    Totally agree. AutoMapper, especially when I map Dtos and needs expressoins was kind of headache. At least for me. As Germain said, "kleiner aber meiner" is the best approach:)

  • @alphaios7763
    @alphaios7763 ปีที่แล้ว

    Wow, I also switched similar to you. I had an obsession with expressions and overcomplicated my code in the past… for my latest project I simply have a handler that is defined for a route, I keep validation magical, but my validator is right above the handler so I know what is going to happen. One route = one file, easily copy pasted, easily reusable (I register handlers in DI). Simplicity and readability over fancy code all the way for me these days

  • @PierreGadea
    @PierreGadea ปีที่แล้ว +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 🌲

  • @saeedbarari2207
    @saeedbarari2207 ปีที่แล้ว

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

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

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

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

  • @amrosamy8232
    @amrosamy8232 ปีที่แล้ว

    Totally agree with mappers and di approaches

  • @Baby4Ghost
    @Baby4Ghost ปีที่แล้ว

    Always remember, readable and understandable code is the hallmark of a good developer. Good code doesnt needs explanation or contains magic. Every path you follow (call you make) should let you know what you are doing. Thats why good naming, conventions and interfaces are so important on high level development.
    1. Use Extension-classes instead of custom classes. Always prefer extensions classes over custom if possible.
    2. Prefer Automapper, even for the custom things you are doing, there are ways to do it. Im not saying dont use manual, thats always used when automapper doesnt seems the be the right fit. Manual mapping is what we always did/do, before automapper, but if you use both, now you have mapping logic in 2 placing done in 2 different ways. Less nerving if you put the automapper in its own project lib like me but still.. dont like it. I would rather put more research time in automapper as its really a good package [even conditions (see .Must()].
    4. I do understand the annoyance when properties change, I had this with the project of my previous employer. We had the Entity-model, and 4 ViewModels (ViewModel, CreateModel, UpdateModel, FormModel), so 5 different classes that are automapped. This project was still in development so imagine the annoyance I had when I had to change/add properties. My solution? Interfaces!!! I created ModelContracts to enforce that the entity and viewmodels had certain properties. E.g. IUserContract, this will mostly look like the Entity model but will make sure your viewmodels always have the right properties. Easy.. let intellisense help you.
    5. Guards, briefly heard something about that long ago, but seems like inventing the wheel again if you already have native support for most of the things (I used to have a Mandate class, not anymore).
    6. Also, I dont like classes with parameterized constructors to initiate them as data models. How many constructors will you have per scenario? And you cant read what the code is initializing with new(params). And what if a parameter changes (position in signature)? sounds like a nightmare to maintain. You will need to know how the code works first, before you know which constructor to call (hello magic!). I rather use object initializing, again, readability. I can read what Im assigning, for every scenario/state I want, unlimited to the constructors (needing to add more constructors for a state thats used on even 1 place). Now lets say I need the same state often: comes in the factory pattern but still! using the object initializer. E.g.
    UserModelFactory.CreateUserForRegistration() =>
    new UserModel {
    IsActive= false,
    Prop1 = "value1",
    Prop2 = "value2",
    }
    Now calling the factory you know exacly what your getting, with intellisense helping, and going into the factory, you can read what properties are set defining the State. So following every calls tells you with good naming and readablity exacly what you are doing. No magic.

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

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

  • @leerothman2715
    @leerothman2715 7 หลายเดือนก่อน

    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.

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

  • @ardavaztterterian1211
    @ardavaztterterian1211 ปีที่แล้ว

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

  • @Sunesen
    @Sunesen ปีที่แล้ว +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.

    • @KhaUh
      @KhaUh ปีที่แล้ว

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

    • @liski12
      @liski12 ปีที่แล้ว

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

    • @Velociapcior
      @Velociapcior ปีที่แล้ว +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 ปีที่แล้ว +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 ปีที่แล้ว +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.

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

  • @namewastaken360
    @namewastaken360 5 หลายเดือนก่อน

    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.

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

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

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

  • @Cool-Game-Dev
    @Cool-Game-Dev 8 หลายเดือนก่อน

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

  • @DavidČubela
    @DavidČubela ปีที่แล้ว

    Concearning mapping: Personally I think manual mapping is a huge hastle. You have to manually add properties when you extend objects, manually change logic and so on. In most cases, except renaming properties, the problems are the same as AutoMapper. No compile time error, even worse, runtime the property gets ignored.
    Another thing is: AutoMapper has ProjectTo IQueryable extension method which optimazes selects and selects only what is needed for an object. That is a huuuge bonus compared to doing every optimization manually. Also, mappers all work in the same way, and the configurations should all be in the same place ( 1 config per model, 1 entity model to multiple DTO/VM) so you can see it all.
    In any case, each style requires some proper setup to be effective

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

  • @CarlosWashingtonMercado
    @CarlosWashingtonMercado ปีที่แล้ว

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

  • @the.ansarya
    @the.ansarya 7 หลายเดือนก่อน

    Thank you for answering a question I've always had: "During registration why do I have to enumerate every service? The framework can see all my DI injectable classes, why isn't this automated?"

  • @Michel000000001
    @Michel000000001 ปีที่แล้ว

    Good insight: visibility over magic

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

  • @Spirch
    @Spirch ปีที่แล้ว +2

    automapper also break the "find all reference" pattern, i hate seeing 1 property with 0 reference while it's being used all over the place

  • @evaldaszmitra7322
    @evaldaszmitra7322 ปีที่แล้ว +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 ปีที่แล้ว

      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.

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

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

  • @MrRobin4444
    @MrRobin4444 ปีที่แล้ว

    With the Time, i prefer to not use private fields, classse or sealed classes in my API.
    If another Developer can utilize a Field or a factory calss to make it work better or use it differently than expected, then I think that's good.
    Why should I prevent someone, especially if it's not my end product anyway.

  • @everyonesview
    @everyonesview ปีที่แล้ว

    Manual mapping is absolutely the way to go.

  • @francismarasigan108
    @francismarasigan108 ปีที่แล้ว

    I stop using auto generator test data framework such as AutoFixture and Bogus. Although they have settings to make a deterministic generation, I prefer now making my own test data manually using class project level method that generate common test data for the same reason, I have complete control of what data I'm testing and easy discoverability. Still makes my test cleaner and can easily define what test data that might affect the outcome of my test vs runtime for those auto generator.

  • @megadesty
    @megadesty ปีที่แล้ว

    You spoke to my soul on all points!

  • @therealantiarchitect
    @therealantiarchitect ปีที่แล้ว +3

    I have become more and more hostile toward DRY. Most devs view DRY as dogma in the purest sense, but I believe that it drives us to prematurely optimizing, which often results in code that is hard to understand and difficult to maintain. My guidance to devs is "repeat yourself until you know enough about the problem to create reuse." RYUYKEATPTCR isn't as catchy as DRY, tho.

    • @nikogj9495
      @nikogj9495 ปีที่แล้ว +1

      Sandi Metz in her book phrased it as "Duplication is far cheaper that a wrong abstraction". Still more catchy than RYUYKEATPTCR.

    • @therealantiarchitect
      @therealantiarchitect ปีที่แล้ว

      @@nikogj9495 I'd say that in many organizations most developers spend most of their time shoehorning a business problem into a "wrong abstraction"

    • @nikogj9495
      @nikogj9495 ปีที่แล้ว

      @@therealantiarchitect I tend to think that is partly because developers reason about their code with a technical approach too often, rather than put focus on the business. You find better abstractions when you know better the domain you're modeling.

    • @therealantiarchitect
      @therealantiarchitect ปีที่แล้ว

      @@nikogj9495 Maybe, but sometimes the abstraction is technical. For example writing a library that helps simplify securing http connections in your environment. There can be a lot of boilerplate that is specific to how your company works and the temptation is to go DRY right away.

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