Stop using trivial Guard Clauses! Try this instead.

Ғылым және технология

Guard clauses, on the surface, sound like a great idea. They can reduce conditional complexity by exiting a method or function early. However, I find how guard clauses are used in the real world to be of little value. Often polluting application-level code for trivial preconditions. I will refactor some code to push those preconditions to the edge of your application so your domain focuses on real business concerns.
🔗 EventStoreDB
eventsto.re/codeopinion
🔔 Subscribe: / @codeopinion
💥 Join this channel to get access to source code & demos!
/ @codeopinion
🔥 Don't have the JOIN button? Support me on Patreon!
/ codeopinion
📝 Blog: codeopinion.com
👋 Twitter: / codeopinion
✨ LinkedIn: / dcomartin
📧 Weekly Updates: mailchi.mp/63c7a0b3ff38/codeo...
0:00 Intro
0:48 Refactor
2:38 Edge
3:38 Application Core
5:35 Web
7:30 Application Request
9:19 Tests
#softwarearchitecture #softwaredesign #codeopinion

Пікірлер: 180

  • @nicktech2152
    @nicktech2152 Жыл бұрын

    I love how you addressed an issue of primitive obsession and Value objects without actually mentioning both of them, because Guard clauses in this case are exactly the byproduct of primitive obsession

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    I feel like I toss around enough lingo and terms that I try and purposely avoid it sometimes... as you just noticed!

  • @allyourfuturebelongstochina
    @allyourfuturebelongstochina Жыл бұрын

    There's another benefit to this which is if you're doing OrderId type, UserId type, etc you wont accidently pass OrderId into a UserId argument.

  • @MiningForPies

    @MiningForPies

    Жыл бұрын

    When I worked at Sage one of their system passed around key value pair of guids. One held the customer Id and one had the account Id. You didn’t know which was which so you had to try both when calling the data access layer 😂

  • @deverbaasdebaas3212

    @deverbaasdebaas3212

    Жыл бұрын

    @@MiningForPies how does my coffee machine even work

  • @flobuilds

    @flobuilds

    Жыл бұрын

    @@MiningForPies holy shit 😂😂 how is this in production 😂 i mean for fast testing it might be pretty good but for production and teams nahhh man 😂

  • @asdfghyter
    @asdfghyter Жыл бұрын

    This reminds me of the “make invalid states unrepresentable” principle from functional programming. Types should be used to represent as many preconditions as possible, so you know for sure they are satisfied even without redundant checks. In particular, non-nullable types should be the default everywhere, so you don’t have to worry about null pointer exceptions ever again. Why should the programmer need to worry about things that the compiler can check for us?

  • @user-wv1in4pz2w

    @user-wv1in4pz2w

    Жыл бұрын

    yeah, nullable types are stupid.

  • @Zeero3846

    @Zeero3846

    Жыл бұрын

    They never should've been the default, but let's blame that on Java. Java made a big assumption about how the programmer should be conscious that all references are actually pointers, and C# thought that was good idea, especially in how it made a convenient "empty" value for any object, but we're all stuck with it now. It may have allowed for performant code in the days when virtual machines were slow, but these days, we're more concerned with correctness, because modern compilers and VMs are way better at optimizing than we are.

  • @asdfghyter

    @asdfghyter

    Жыл бұрын

    @@Zeero3846 and you can still be fast and efficient while separating nullable and non-nullable pointers. for example, rust automatically turns the None case of an Option type into a null pointer, so it’s zero cost at runtime

  • @Templarfreak

    @Templarfreak

    10 ай бұрын

    certain things, it doesnt matter if they are nullable or not. if it's not nullable, you'll still have to check if it's of a certain value and not of another. it being nullable in this case simply gives you a straight forward concept instead of using a magic value like 0. all null really is is a constant named null that has a value of 0, when you think about it, and, actually, in older languages, like C and C++, this is exactly what null is, a compiler-time constant that basically has a value of 0. in other cases, like Lua, "nil", is often used to inform you of a failure state. something didnt go as planned, somehow a function could not spit out a more proper value, so it returns nil. most of the time, you can expect this to not happen, especially with functions you wrote yourself, but many of Lua's built-in functions will give you nil and this is basically just to let you know that something went wrong, and this is especially important in cases where say you want to use 0, or negative numbers, so you cant compare with those values as special information values in place of nil. all-in-all, null is just a mathematical / functional tool like any other. using it aggressively for every single problem is never necessary, but there are cases where it is useful to use. and this is the same for a lot of other things, like exactly what this video is about, guard clauses can be a useful tool when used correctly but using it for most of your problems should be an indication that you are doing something wrong. same thing with null. if you find yourself having to use null all the time for something, than there is probably a different technique you should be using for problem A, and another for problem B, but maybe for problem C it's fine

  • @asdfghyter

    @asdfghyter

    10 ай бұрын

    @@Templarfreak This is why concepts like Option in Rust are really useful. When you check if it's None (which is internally encoded as a null-pointer) you will get an evidence of that check by getting a non-nullable value out. You do indeed still need to check it, but your compiler will guarantee that you can never forget to check it and also allow you to avoid redundant checks, since you get that evidence of having a valid value. In other words, having nullable values as such isn't necessarily bad, as long as you have an explicit distinction between nullable and non-nullable and an easy way to convert a nullable value to a non-nullable one by performing a check. The issue is when everything is nullable by default, since then you can never know if a value will be null or not and will have to sprinkle all of your code with null checks

  • @allannielsen4752
    @allannielsen4752 Жыл бұрын

    This is one of the first times I've disagreed with you. I understand what you are trying to say, however, if you expose a method as public in a public class, then you must guard against improper use especially with your dependencies. Whilst this is somewhat a question of primitive obsession and where to use such object replacements there is still nothing stopping a "user" passing a null Username? To me you still have the same issue whereby the dependent method still need to guard against a null Username since it STILL depends on that instance (regardless of whether it's a string or a struct/record/class). To me there is a simple rule here, if you have a dependency and you open your method to public, then you need to guard against it's incorrect use because something will call it incorrectly if you let them. Especially with reference types in C#. The real question in this video is should the method be public? private methods, i totally agree, internal/protected is up for debate, but public, ALWAYS.

  • @anthonylevine8783

    @anthonylevine8783

    Жыл бұрын

    I think you're missing the point. You DON'T need validation that far down if you've handled it at the edges. Are you authoring a library in which you have absolutely no control over what is passed to it? In that case, then your point stands. But this video, in my opinion, isn't about building libraries. It's about removing the duplicate and more importantly NOISY code that is redundant.

  • @juliancyanid

    @juliancyanid

    Жыл бұрын

    Since nullable reference types (with "warnings as errors" directive), i trust my compiler with null. The typesystem and compiler "guarantees" null-safety, as long we stay "Within" - any IO now requires either calling a valueObject constructor that guarantees the invarians, or mapping from valueObject (driver/driven side, ports and adapters).

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Well some of this is if you prescribe to layered/hex/clean, which I'm not sold on which I've talked about in other videos. In my example, I'm leveraging a value object to remove guard clauses within the domain. It's pushing those to the integration boundary (edge) so those inputs are forced to use the types defined by the domain (out to in, just as you mentioned). Not entirely sure how you'd be copy/pasting invariant checks?

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    That sample project did. I'm not against it per se, I just find more value in slimmer logical boundaries and then using layers within that if required (which often isn't)

  • @anatolia23

    @anatolia23

    Жыл бұрын

    @Andre SCOS To be honest, I believe that the Clean/Hex/Onion architectures are overengineered. In my first eight years of working, I always used these architectures. Then I realized all of these abstractions and layers were superfluous. I can think of several disadvantages: abstractions over abstractions, difficult to maintain, more boilerplate code, difficult to debug, difficult for junior developers to understand the flow, and so on. When you want to add just one column to your table, you must touch all of the layers. We've been using vertical slice architecture with DDD and discriminated unions with my team for the last three years. Both stakeholders and developers are now happier. The most important lesson I've learned in my career is the significance of simplicity. We don't really need these fancy architectures. Simply adhere to the SOLID principles.

  • @steve-ardalis-smith
    @steve-ardalis-smith Жыл бұрын

    Using record types instead of primitives is definitely a good design improvement! Nice video!

  • @robertmrobo8954

    @robertmrobo8954

    Жыл бұрын

    I waited the whole video to hear him mention "Primitive Obsession"

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    I few people have mentioned it, and I kind of agree, I just don't think of it that way. There are many different reasons for guard clauses and different solutions. I just showed a common one of null and using a value object as a solution.

  • @MrGarkin

    @MrGarkin

    Жыл бұрын

    Great not just for nulls, but for other explit invariants, like size, length, charset, etc.

  • @harambetidepod1451

    @harambetidepod1451

    Жыл бұрын

    No

  • @strictnonconformist7369
    @strictnonconformist7369 Жыл бұрын

    By pushing out the guard clauses purely into constructors you have achieved DRY as well as a desired side-effect. If the type disallows invalid construction, and you can enforce each type to disallow invalid construction via constructors, life is far simpler and less error-prone.

  • @kis.stupid
    @kis.stupid Жыл бұрын

    Loving the content! Do you often start empty projects that become medium to large projects including event sourcing and all the good stuff you talk about? Do you have projects or templates ready as starting point so you don't have to re-do all the necessary setups?

  • @LunaticEdit
    @LunaticEdit Жыл бұрын

    So now we're going to use exceptions as control flow? I guess in specific conditions this pattern makes sense. But what if, on the condition that a basket is null, you want to do a somewhat different codepath instead of just blowing up? If you apply this style to everything you're going to design yourself into a corner, or force try/catch blocks where if/else blocks would exist -- and try/catch blocks are _way_ more expensive. This is an interesting tool, but this doesn't "fix" guard clauses. It's just another way you can implement things.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    As I mentioned it's about trivial use cases. It's not about nullability but forcing callers to provide valid values. In my example that was using a type to do so. In your example of the basket, I'd prefer to return an option/maybe rather than null.

  • @marco.defreitas
    @marco.defreitas Жыл бұрын

    Simple yet effective, couldn't agree more! Good stuff 😄

  • @ransandu
    @ransandu Жыл бұрын

    I have this question regarding the service implementation in the application project. This is more or less a question related to Clean Architecture.Shouldnt we require to define the interfaces and implementation require to be in the infrastructure project. Happy to hear your thoughts. Thank you.

  • @MRApht
    @MRApht Жыл бұрын

    Nice video :) I saw you were navigating to errors by clicking on them with your mouse. In Rider, you can navigate to next error in file with F2, or next error globally with alt+F2 (especially useful when you are changing signatures like you did). Then you don't need to reach for the mouse in these cases. Hope it's helpful.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Thanks!

  • @Galakyllz
    @Galakyllz Жыл бұрын

    That makes sense in some cases where this check would be true everywhere (since we would never want a null/empty user identifier), but for completeness wouldn't every method's arguments become instances of this wrapper struct concept?

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Could be anything where you want to be valid and push that logic further up the stack

  • @kylekeenan3485

    @kylekeenan3485

    Жыл бұрын

    Ideally your keeping arguments to 1 or 2 max for a method by design, where often the argument passed is a complex object consisting of many related arguments. The guard clause for these can then be held in the originating class so you don't need to pullute the application code as shown in the video. I worked in a place where some methods had 25 arguments! The approach in this video would be a nightmare to use without massive refactoring in these cases it's just better to find a new job lol.

  • @seltox6320
    @seltox6320 Жыл бұрын

    Forgive me if this isn't the case in C#, but can you still call these functions expecting a `Username` and explicitly pass a null instead of a username? Guarding against it in the record construction gets you a lot of the way there, but languages that support nulls can never truly escape null checking if you want to be completely safe - we just have to do our best and resign ourselves to the fact someone could still misuse the code. This is one of the reasons I am really drawn to languages that don't support nulls (like Rust), or where nulls are opt-in per variable and consequently obvious that they should be handled (like Kotlin)

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Agreed. Structs aren't nullable but they're still issues in my example related to default.

  • @xpoftik
    @xpoftik Жыл бұрын

    I really like the idea to use records with preconditions instead of abstract primitives. But it's really frightening to imagine the number of classes that might appear in your code. Does it worth it? Potentially, it might produce even more polluted code than the Guad-styled code.

  • @lukassinkus6162

    @lukassinkus6162

    Жыл бұрын

    I guess it's a good way to separate data from behavior. Data validation is not really behavior, so if you can use types to enforce the data integrity all you have left to deal with is logic.

  • @Zeero3846

    @Zeero3846

    Жыл бұрын

    The code pollution isn't in the middle of the business logic though. It's in the file explorer pane, and you don't have to write them more than once per type, which most types are used in more than one place, then you save yourself from a lot more guard clauses. Further, having all these type definitions allow for easier synchronization with database type constraints. Plus, record types always present an opportunity for reducing the number of parameters a method or constructor might contain, though that really depends on the method.

  • @marna_li
    @marna_li Жыл бұрын

    I have come to the conslusion that most errors are not exceptions. So I instead have Result objects that are returning defined Error objects. Exceptions should only be the exception - when something unexpected and bad happens. For every error that you can conceive of you should treat it as an error to be returned from the method or command. And if you know that there is an immediate exception. Catch it and turn it into an Error object that you return directly in a Result. In that way you don't have to worry about filtering Exceptions at the top, and that throwing has an impact on performance due to rewinding the stack. Fail fast and cheap.

  • @heikenem

    @heikenem

    Жыл бұрын

    Great comment

  • @heikenem

    @heikenem

    Жыл бұрын

    I agreed pretty much with you @Marina Sundström. But if u have to create a valid value object do you also return a Result object or throw an exception? because most of the implementation I have saw is throwing exceptions. I'm curious now about ur vision.

  • @marna_li

    @marna_li

    Жыл бұрын

    @@heikenem My ccmmands and queries return Result. In a controller action, I can handle the result in a standardized way using an extension method that takes two delegates, one for success and another for error. Basically, the method returns IActionResult.

  • @heikenem

    @heikenem

    Жыл бұрын

    @@marna_li Got it. Thanks.

  • @arunnair7584
    @arunnair7584 Жыл бұрын

    Why not use a factory class/method inside the domain assembly to ensure domain classes are always created in a valid state (i.e. seperate the class creation code from the class usage code)? Same thing goes for the "do not use primitive types" guideline.

  • @Jabberwockybird

    @Jabberwockybird

    10 ай бұрын

    Its not a complex object

  • @corey1426
    @corey1426 Жыл бұрын

    Hi Derek, I’ve had a couple outstanding questions from some of your other great previous videos around moving away from synchronous api request/response rpc-type calls and moving more toward asynchronous communication via messaging & events and was wondering if this concept of “edge services” is the answer… Let me elaborate… I have a system in which an external producer can submit an http post to my rest api and the service within my api validates the payload (e.g. mandatory fields have been populated, correct format/type, length, etc.), puts the message onto a message broker/queue (e.g. pub/sub) then the corresponding consumer/subscriber picks up the message and processes the request. I guess my first question is: In your videos, you depict a service putting a message into a queue and the consumer picking-up that message and processing it but in my example above, I can’t accept just arbitrary json from the external producer and am I breaking the pattern you’re advocating by having the external producer communicating with me via a synchronous rest api request? Is the validation service I described above that parses the payload before putting the message into the queue an example of an “edge service” as you’ve described in this video? I hope that made sense and happy to clarify further if needed. Thanks, Derek and keep up the great videos!

  • @juliancyanid

    @juliancyanid

    Жыл бұрын

    Sounds like an Anti-Corruption layer 👏

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Yup. kzread.info/dash/bejne/dqOflKukk83HotI.html

  • @Zeero3846
    @Zeero3846 Жыл бұрын

    As a fan of strong static typing, I love this, but in a dynamic language like JavaScript, you can only accomplish this via clever use of JSDoc and a lot of discipline. At the very least, even if you don't use the technique in the implementation, you should be able to make use of it in the documentation. It helps reduce the amount of minutia you have to write to get the point across.

  • @worldspam5682
    @worldspam56828 ай бұрын

    You are really edging with those guard clauses 😂

  • @mohammadabedi3463
    @mohammadabedi3463 Жыл бұрын

    hi. how to map the Title to database as column?

  • @mrcsjvc
    @mrcsjvc Жыл бұрын

    Hi, Derek, as always, thank you for the video. Question: what are your thoughts on using the Execute / CanExecute pattern to avoid throwing exceptions from constructors? Is it worth?

  • @anatolia23

    @anatolia23

    Жыл бұрын

    To avoid throwing exceptions, there is a far better approach. It's known as discriminated unions, and it's commonly associated with functional programming. But, as with DDD, once you start using it, there is no turning back. You will feel like you've filled the void in your programming career:) C# does not natively support discriminated unions, but don't worry! You can make use of libraries such as ErrorOr and OneOf.

  • @mrcsjvc

    @mrcsjvc

    Жыл бұрын

    @@anatolia23 Thank you.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Yes! OneOf, Either, Maybe/Option etc.. I should create more videos on these for folks in C# land, but I suspect the replies/comments will be to just use F#!

  • @anatolia23

    @anatolia23

    Жыл бұрын

    @@CodeOpinion Probabily they will :)

  • @CallousCoder
    @CallousCoder Жыл бұрын

    Always check your inputs. Even after you checked it after creation. And now during code auditing, there’s no easy validation whether error cases are explicitly handled. With guard clauses or dedicated validation methods, you can instantly see its not forgotten. And it’s loosely coupled from another piece of code so I can pick it up and move it to a library, being sure inputs are checked. And I always realize that there’s lots of ways things can still become “inconsistent”. Most notably hackers who fuzz the arguments.

  • @AnthonyZboralski
    @AnthonyZboralski Жыл бұрын

    Relying solely on edge validation is problematic as the edge may change, or a new team may implement it, assuming that the domain is already enforcing and validating input. Failing to validate input at each trust boundary could result in a false sense of security and increase the risk of vulnerabilities in the system. Ultimately, input validation at every trust boundary is crucial to ensure the system's security and reliability.

  • @andrewcolleen1698

    @andrewcolleen1698

    Жыл бұрын

    Exactly

  • @HoD999x

    @HoD999x

    10 ай бұрын

    there is no protection against evil coworkers

  • @GustavoEhrhardt
    @GustavoEhrhardt Жыл бұрын

    That's one of the reasons I'm loving TypeScript. The guard clauses is embedded on type definition. See ya. Thanks for the content.

  • @anthonylevine8783

    @anthonylevine8783

    Жыл бұрын

    Isn't that true for all typed languages?

  • @GustavoEhrhardt

    @GustavoEhrhardt

    Жыл бұрын

    @@anthonylevine8783 In TS the "number" declaration implies that you will always have a value (transpiler checks it). When you have an optional variable the type is "number | undefined"

  • @s0psAA
    @s0psAA Жыл бұрын

    Value objects themselves can be null though. You know their value is always correct, if they aren't null. For example you can have an address that is optional, so null or valid address are both acceptable. Also would you consider the Application layer being the edge that does all the transformation. When you have multiple places as input, you have one place for it. I like to keep Value objects to the domain layer and any logic you had in you application layers like adding to the basket, there ought to be domain service for it, application layers purpose is to do the transform/authorization and invoke the right use case. In this scenario you can have Web API controller take in the Mediator request(deserialize it) and send that towards application layer, otherwise I would need another DTO that has just string and maps between application/web layer.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    The username as a struct. In C# that cannot be null. The Username resided in the application core. Creation of it was pushed to the outer edge (web controller in this case).

  • @DaminGamerMC
    @DaminGamerMC Жыл бұрын

    I believe .Net 7 is comming with a new "required" keyword wich can be used in properties so it will become a compile time error instead of runtime error.

  • @Joooooooooooosh

    @Joooooooooooosh

    Жыл бұрын

    Yeah but all the required keyword does is require that a property be assigned at initialization. It doesn't prevent you from initializing the property with an explicit null or invalid value.

  • @Joooooooooooosh
    @Joooooooooooosh Жыл бұрын

    The community toolkit has a much cleaner guard class in my opinion. It uses new compiler attributes to infer the parameter names so you only need to do Guard.NotNull(value). I don't really think it's a good idea to make assumptions about the caller though. Functions should always do basic validation on parameters.

  • @kamransaeedi

    @kamransaeedi

    11 ай бұрын

    Public ones, of course.

  • @leftjabrighthook
    @leftjabrighthook Жыл бұрын

    I might have missed something, but why call it Username if its used for string arguments everywhere? Wouldnt it be better off called ThrowIfNullString? Or something generic like that?

  • @luan_maik

    @luan_maik

    Жыл бұрын

    Username it's a Domain Value Object, it has own validation, for example, email can be a Email class, because can not be null, but also must be a valid email format.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Caught on that it's a value object and I never even mentioned it in the video 😉

  • @leftjabrighthook

    @leftjabrighthook

    Жыл бұрын

    @@luan_maik I see that but he used Username for everything like "buyerId" etc? I still must be missing something.🤷‍♀

  • @luan_maik

    @luan_maik

    Жыл бұрын

    @@leftjabrighthook Username can be the user identifier, what is the problem? Could be the email, or a UUID, or a integer auto generated by database.

  • @luan_maik

    @luan_maik

    Жыл бұрын

    @@leftjabrighthook if the user Identifier was a integer, could be a value object called UserId, or more generic as Indetifier

  • @edgeofsanitysevensix
    @edgeofsanitysevensix Жыл бұрын

    This works if the services are not used elsewhere. I don't like adding too much validation at controller level (only data annotations) and I like the services we pass that request too be in control on what is a valid input or not. Secondly in tests, if we don't add guard clauses at service level we then have to test for exceptions rather than parameter validation.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    The validation doesn't live in the controller. The validation lives within value objects within the domain. You can't pass anything into the domain without it being valid. Where you're constructing those objects are likely at the edge in your controller.

  • @edgeofsanitysevensix

    @edgeofsanitysevensix

    Жыл бұрын

    @@CodeOpinion I agree. I just pass the request to a service which will map it to a usable and valid business object which in turn provides the validation. Controller code is practically one line.

  • @xtrembaker7337
    @xtrembaker7337 Жыл бұрын

    Thanks Derek ! One kind of "Guard" I find difficult to "get out of the domain", is security check. For example, I’m connected as a user A, but trying to access resource that belongs to user B. How do you deal with that without having them in the Domain ?

  • @anthonylevine8783

    @anthonylevine8783

    Жыл бұрын

    Is security a domain or infrastructure concern? Should a request really get to the domain itself if the user isn't even authorized to perform it, or should that be handled by the infrastructure?

  • @MiningForPies

    @MiningForPies

    Жыл бұрын

    @@anthonylevine8783 not always that simple. There’s security (user is allowed to make this call) and security (user is allowed to access this record)

  • @moonbiscuit8742

    @moonbiscuit8742

    Жыл бұрын

    From my understanding of DDD, Authorization is encouraged to be kept at Application Layer, letting the Domain handle the business only. I've worked on projects that started to do a lot of Authorization at the API/Web Framework Layer just because it was an easy win. But what if the Application wasn't hosted behind an API? You wouldn't do Authorization? I do indeed think it's pragmatical to have it this way for simple scenarios, but starts to break down when you add requirements like the one you're saying. So in the Application Layer, I would load the Resource, do Authorization based on the attributes of the Resource and then either throw exceptions or be explicit in the return type of the Application, you would need something similar to an Either/Result monad to represent this. The way you design the code that does Authorization? I don't know proven ways, have seen this done so many ways. But sounds like you need to do Attribute Based Access Control.

  • @anthonylevine8783

    @anthonylevine8783

    Жыл бұрын

    @@MiningForPies But should either of those things be part of the domain? If its general auth/auth, then no. Is it a business rule or is it just your standard authorization?

  • @MiningForPies

    @MiningForPies

    Жыл бұрын

    @@anthonylevine8783 it’s a business rule. There are strict rules over who can do what to whom.

  • @donnyroufs551
    @donnyroufs551 Жыл бұрын

    I do recall not being "allowed" to expose your domain entities / value objects outside of your core though. Why made you decide on doing it anyway?

  • @MiningForPies

    @MiningForPies

    Жыл бұрын

    Where have you heard that?

  • @mabdullahsari

    @mabdullahsari

    Жыл бұрын

    Where have you heard that, exactly? I instantiate dozens of ValueObjects within Infrastructure.

  • @donnyroufs551

    @donnyroufs551

    Жыл бұрын

    @@MiningForPies pretty much any article I read says something about not exposing your domain objects outside of your core. I know there are exceptions in hexagonal where outbound adapters like your repo are allowed to work with them though. But something like your controller definitely shouldnt? -- edit not to mention that if you allow anyone to use your domain objects outside of your core, you will really shoot yourself in the foot when you want to change something compared to having duplication and strict contracts

  • @MiningForPies

    @MiningForPies

    Жыл бұрын

    @@donnyroufs551 but you cannot for instance populate a view model with the data from a domain model without either A: Exposing your domain model outside of your domain. B: having view models in your domain.

  • @donnyroufs551

    @donnyroufs551

    Жыл бұрын

    @@MiningForPies nothing wrong with having {insert here} domain object in your application that then gets translated to a dto which the outer layers can use? dont see why a or b matter here

  • @breakpoin80
    @breakpoin80 Жыл бұрын

    That's really cool! Would you still do model validation with something like FluentValidation for more complex types?

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Sure, just forcing that further up the stack at creation time.

  • @matthewhartz9235
    @matthewhartz9235 Жыл бұрын

    Seems backwards if you want to put the guard clauses on the edge of your code. What if you have many input layers? That means you have to do that check X times when you could just check in the domain logic?

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    In my example, I had the guard once within the Username itself. The integration boundary (edge) has to convert its input into something valid (the username) in order to even being to make a request within the domain. The domain doesn't need guards to be defensive.

  • @matthewhartz9235

    @matthewhartz9235

    Жыл бұрын

    @@CodeOpinion Yeah i apologize, i was too quick to respond. I love this solution!

  • @travisarndt853
    @travisarndt853 Жыл бұрын

    Naming convention for Username is strange and misleading in my opinion. What about going with something like abstract Required where the derived class is Username : Required and BuyerId : Required

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Sure. I think they should just actually be consistent within the app, but they weren't.

  • @troelsmortensen9914
    @troelsmortensen9914 Жыл бұрын

    This is certainly an interesting idea, I like the concept. But how would you solve something like this: You need to create a new User, with username, password, email, phone, etc. Each of these have some validation rules, so we could create value objects for each piece of data. Now, if the client messed up multiple values, they would first get an exception for failed username. Then they try again, and then get an exception for the password, etc. They have to try multiple times. I would like to validate all, and return a collection of all errors. How would one achieve that with this approach to value objects?

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Instead of throwing, provide a validation method on various types (similar to a TryParse) that you can aggregate and return.

  • @9SMTM6
    @9SMTM6 Жыл бұрын

    I mean, that's just a logical continuation of the guard clause argumentation. A guard clause cleans up your function by exiting early from states you deem wrong, moving the guard clauses to the entrypoints is doing the same thing for your whole application.

  • @gordondaniel
    @gordondaniel Жыл бұрын

    I solve it with a method 'Validate' in the class 'GetMyOrders', which checks if its fields are valid. Can be enhanced into an interface 'IValidate' with the method 'Validate'. *. And btw, my 'Validate' method returns a string with a meaning full text if the validation failed, because I don't like exceptions.

  • @georgehelyar
    @georgehelyar Жыл бұрын

    Anonymous ID seems to be a guid, judging by the TryParse, so that could just be a guid type, which is a value type so never null. Something to watch out for when doing this with types though is default values of those types. A reference type has a default value of null, and a value type like a struct will have a default property value of null (e.g. if you make an array of them without assignment, or use the default keyword) Where I work we solve this with asp net core request validation on model binding instead, which also lets you return nice ProblemDetails responses to the client rather than having to throw and catch exceptions etc.

  • @niveliyahu146
    @niveliyahu146 Жыл бұрын

    Hi Derek, I like the idea of guard only on the application edges, but on the other hand I use to initialize my object using object initialize precedence (set the arguments inside the {}) because this more scalable and the code won’t break if you extend the object (in contrast to constructors). I wonder if it’s good idea to use a object (like initializer) that will be the only argument of the constructor and do all the guards of the object inside the constructor. I will appreciate to hear your opinion. Thanks ahead.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Or some type of "builder" that can do the preconditions.

  • @Tony-dp1rl
    @Tony-dp1rl Жыл бұрын

    Nice video. I often find people take the same approach with exception handling too, and put way too many try/catch blocks in core code instead of higher up the stack.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Agreed.

  • @MiningForPies

    @MiningForPies

    Жыл бұрын

    Some people seem to think exceptions are free.

  • @miran248
    @miran248 Жыл бұрын

    4:50 Another option would be a `TransferBasket` record, which could be a command.

  • @mariorobben794
    @mariorobben794 Жыл бұрын

    Instead of creating a custom value type (which holds a primitive type), I use the StronglyTypedId library.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Agree, in this example. There are many others beyond this where guard clauses are used that ultimately it's about removing the defensiveness by forcing the caller to pass something valid.

  • @SyKot
    @SyKot Жыл бұрын

    Why record struct instead of record class?

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    it's not nullable. however using default(T) will cause an issue still.

  • @user-kc8uh9ve4j
    @user-kc8uh9ve4j Жыл бұрын

    Problematic, if someone finds your api they vould send there own requesr with null

  • @goisenate
    @goisenate Жыл бұрын

    I agree with you on most of what you're saying - but not on the video's title and your recurring interpretation of "stop using guard clauses". I can already hear junior developers talk about stopping guard clauses or moving them up to controllers again. My interpretation of what you're saying is this: Stop using guard clauses in your function bodies if you can solve the problem in a more elegant and transparent way, e.g. by using the power of your language's type system. Or via a decorator, annotation, AOP etc for that matter.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    I updated the title to hopefully reflect the videos content better.

  • @ThugLifeModafocah
    @ThugLifeModafocah Жыл бұрын

    I believe that the Username/Address objects that should be responsible to validate their own state.

  • @MaxPicAxe
    @MaxPicAxe Жыл бұрын

    I definitely agree... and hopefully as much of that guarding can be done by the type system itself, e.g. "unsigned integer" instead of an greater than zero guard, etc.

  • @anthonytrad
    @anthonytrad Жыл бұрын

    I agree with your idea, however i think it's issued from a broader concept which is Primitive Types obsession... This is one of the problems of this concept. If it wasn't something trivial such as null checks, do you still use the same validation inside your value objects using exceptions? Exceptions can make it hard to understand your code flow and may be expensive if you're fishing for performance so I would love to have your take on that ! For me, if it's something the user can do (known and expected sometimes), I usually try to avoid exceptions

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    A lot of people have mentioned primitive obsession. While that is a use-case, it's not the only really. The point of the video, or my hope that it would convey, is that you want to have your core API not to be defensive but rather develop it in such a way that consumers/callers are forced to call it in a valid way.

  • @genechristiansomoza4931
    @genechristiansomoza4931 Жыл бұрын

    Edge is the Controller in MVC

  • @gustavnilsson6597
    @gustavnilsson6597 Жыл бұрын

    Exceptions are a developer feature and should only be thrown from library code. You are right about wanting to refactor the guard clauses, but you should be moving them inwards, and not outwards. Discriminating unions are much friendlier to use as return types over exceptions.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    They were moved inward. The username was within the application-core. The construction of the username type was only ever done outwards in aspnetcore however.

  • @CoderCoronet
    @CoderCoronet Жыл бұрын

    Hum… Not sure if I like it more than guards. I have to try it out.

  • @kormuss
    @kormuss Жыл бұрын

    Like this quote. Uncle Bob Martin on Twitter: "Defensive programming, in non-public APIs, is a smell, and a symptom, of teams that don't do TDD."

  • @pdevito
    @pdevito Жыл бұрын

    So pretty much a value object?

  • @SnOrfus

    @SnOrfus

    Жыл бұрын

    In this scenario, yeah, but the general pattern of moving input validation to the perimeter instead of validating it everywhere it gets used, is the core idea to take away - not just value objects.

  • @johnmcleodvii
    @johnmcleodvii Жыл бұрын

    That's fine until you have a hacker that succeeds in accessing your internal micro services.

  • @hubrismaxim
    @hubrismaxim Жыл бұрын

    WTF? String can be null in C#?!? That seems to be pretty bad language design. What’s the point of having types if they are not enforced?

  • @karelhrkal8753
    @karelhrkal8753 Жыл бұрын

    Is only there was a compile-time type for a non-null or a non-null and non-empty string. Oh wait, there is. In Rust :D

  • @badderborroda4972
    @badderborroda497210 ай бұрын

    This sounds like a terrible idea at scale, you'd have 100s of structs for all string cases alone. A function guarantees correctness of it's own scope, checking for validity of the inputs (this can include things other than IsNullOrEmpty) is not an issue that needs fixing.

  • @anatolia23
    @anatolia23 Жыл бұрын

    Yet another great video! ValueObjects are great for always valid domains. Personally, I prefer to use abstract classes for value objects which are more flexible over equality checks and provide more control. We can't also hide the default parameterless constructor in record struct which is very annoying. Probably not the main point of the video, however you can use the implicit operator instead of ToString().

  • @Mvision123
    @Mvision123 Жыл бұрын

    Nullabillity can't help here?

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    In this specific example it could. It's not about nullability, more about being in a valid state from the start from the inbound request into the app.

  • @allannielsen4752

    @allannielsen4752

    Жыл бұрын

    @@CodeOpinion then why not say that in you click bait title and title page? It display a public method where you guard against incoming nulls. Perfectly valid use case imo. Even if its a username instead of string.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    I really do believe the title "don't use guard clauses" was accurate because in the example of the video, that's how 99% of the time I see them being used. It was not intended to be click-bait at all. in the vast majority of cases, I actually think of the title before I even create the video. It's kind of on a whim when I see/read something. There are many reasons for trivial guard clauses, the most common I see is nullability. There are many different solutions, I just showed using a value object as one of the solutions. If you were creating a public API that had to consume say an inbound HTTP request, I think using those types of guards are totally reasonable. But not how I explained in the video of how they were being used.

  • @FengHuang13
    @FengHuang13 Жыл бұрын

    This is one of the very few times I disagree with you. By moving your guards on the "edge of the application" as you call it, you are actually coupling your app to a web interface. So if tomorrow you are listening to messages and react to those messages by calling some service functions, you will not benefit from those guards. But I do agree that having them everywhere is polluting your code. So what I do for a few years now, is using guards only on object's methods that are parts of the public api, not on the network interface. By contract the "internal" method's arguments must not be null unless specified via Maybe/Optional. PHP has this behavior built in btw, funny huh ? My network interfaces transforms those errors into appropriate error code, specific to them.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    I've got a similar comment a few times now, and I think I might have explained it wrong. It's not that the guard is defined at the edge, it's that you're creating input types apart of your application, and those are used/created at the edge (integration boundary) that has I/O. So exactly as your stating, if you have a HTTP API or driven by a message queue, you're ultimately using that I/O to generate an application request that contains those guards. You're in fact removing the coupling from the underlying inbound request (http, message, etc).

  • @FengHuang13

    @FengHuang13

    Жыл бұрын

    @@CodeOpinion yes you're talking about Value Objects. Depending on the language they are hard to construct (requiring builders, boilerplate etc), or constructors are skipped because some framework use reflection to build objects, some languages even have weird constructor call order that let you capture exception during object initialization. Unrelated but I have a video request : how to you deal with api versioning from the storage perspective. When you cannot pause & upgrade your entire db to avoid interruption of service, how do you deal with have different versions of data in your db. I've seen & used a few approaches but I've love to have your thoughts on that particular problem.

  • @YamiSuzume
    @YamiSuzume Жыл бұрын

    So... the message is "don't use guard clauses, when doing it wrong"? lol

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Don't use guard clauses for trivial preconditions since you can eliminate them entirely in various ways. One illustrated was simply using a type (struct) that cannot be null and is pushed to it's creation at the web layer. There are many different ways to accomplish getting rid of trivial preconditions out of your domain code so it doesn't have to be so defensive over nonsense. So ya, you're statement is pretty accurate :)

  • @adambickford8720
    @adambickford8720 Жыл бұрын

    It's completely obnoxious to need to instantiate an object for every field so please provide a `Builder` using the regular platform types if you take this route for anything beyond a trivial wrapper. I don't care if we use types that defend themselves or a service to validate structs, they both have pros and cons. Just pick one and be consistent.

  • @MiningForPies

    @MiningForPies

    Жыл бұрын

    Obnoxious? Not sure what you’re going for there, but that’s an odd place for that word to appear unless I’m missing something?

  • @adambickford8720

    @adambickford8720

    Жыл бұрын

    @@MiningForPies where you end up with something like `new User( new UserFirstName("foo"), new UserLastName("bar") )` vs a simple: `new User("foo", "bar")`

  • @MiningForPies

    @MiningForPies

    Жыл бұрын

    @@adambickford8720 I don’t get how that’s obnoxious.

  • @adambickford8720

    @adambickford8720

    Жыл бұрын

    @@MiningForPies You have an explosion of trivial types and more boilerplate than information. Then people end up writing a Factory/Builder that just takes the 2 strings to abstract it all back! There are far less 'noisy' ways to control preconditions.

  • @MiningForPies

    @MiningForPies

    Жыл бұрын

    @@adambickford8720 like? How would you do it?

  • @VaragornX
    @VaragornX Жыл бұрын

    Thx, I hate it. ;) Jokes aside, I think this has some big downsides, and a flawed philosophy. Everyone that uses the Username object needs to just KNOW, that it can't be null. A method should not need to rely on some deeper knowlage of the object it consumes. Ontop of that, Username can in theory still be null.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    Consumers calling the Basket/Order or anywhere else the guard existed for username, also need to know null wasn't valid or they get the same exception. So there are more places that consumers would need to know username can't be null when passing it as an arg.

  • @diligencehumility6971
    @diligencehumility6971 Жыл бұрын

    IMO most of the things you guarded against was actually validation logic. With a proper validation pipeline, you can isolate your validation logic, for each separate request into the application. Personally I use MediatR with Fluent Validation, inspired from Clean Architecture

  • @tiredlyinsane
    @tiredlyinsane9 ай бұрын

    Not really a good example to use nullable guards clauses on non nullable types

  • @zimcoder
    @zimcoder Жыл бұрын

    So its How to use Guard clauses in a proper and smart way rather than "Stop using Guard Clauses!"😏

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    I see how it can come across that way, however I don't view it like that. In the example the guard clauses for the username to me are typical trivial uses that are riddled within an application. To me with little value in that way. One solution is to use a value object like I did in the example to eliminate the need to the same guard clause in multiple places.

  • @beemerrox
    @beemerrox3 ай бұрын

    This is stupid. You use guard clauses because of user input! In a WebAPI for example, where you in a Repo or Service look for items given some Id, how the * would do that using this technique where you actually want to return NotFound() etc? Nah, dont listen to this..

  • @Yotanido
    @Yotanido Жыл бұрын

    While this does improve the particular code shown in the video, I can't agree with the premise of "stop using guard clauses". And it seems like neither do you, so the entire title of the video is just pure click bait. Newtypes are good, but not everyone is using a statically types language. Personally, I hate using dynamic typing, but my job is in Python. It does have optional static typing, but it's lacklustre at best. I'm not going to be using many newtypes in python, as THAT is something that will pollute my code. The language just isn't made for it. In Haskell, on the other hand, I newtype as much as I can, since the language makes it incredibly easy. In Rust, I don't use as many as I probably should, but the language also makes it fairly easy. But you know what they all make fairly easy? Guard clauses. I don't use them for null (None) checks, usually, but there are many instances in which I only want to continue if certain preconditions are met. Preconditions the caller either can't - or shouldn't be expected to - check. Often this will be indicated by returning None instead of an actual value. Would I prefer to return an Option, or Result, as you do in Rust? Absolutely. Unfortunately, algebraic data types are exceedingly rare and Python doesn't have them either. I don't understand why, since they are so amazingly useful, but alas.

  • @CodeOpinion

    @CodeOpinion

    Жыл бұрын

    I've updated the title to hopefully better reflect the video.

  • @DevMeloy
    @DevMeloy10 ай бұрын

    Thank god MS didn't buy Nintendo.. hopefully it stays that way.

  • @zekeanthony
    @zekeanthony Жыл бұрын

    Yes stop using them, they were an unnecessary creation to begin with.

  • @kashqahy6171
    @kashqahy6171 Жыл бұрын

    IMHO, with all due respect for your channel and the time you have spent on this video, it is truly pointless because you picked something bad and made it worse :)

  • @waytospergtherebro
    @waytospergtherebro Жыл бұрын

    Blah blah blah Millennial needs attention because nobody notices him at work.

Келесі