Stop using trivial Guard Clauses! Try this instead.

  Рет қаралды 47,550

CodeOpinion

CodeOpinion

Күн бұрын

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 😂
@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.
@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 7 ай бұрын
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 7 ай бұрын
@@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
@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
@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.
@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.
@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!
@marco.defreitas
@marco.defreitas Жыл бұрын
Simple yet effective, couldn't agree more! Good stuff 😄
@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.
@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.
@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.
@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.
@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.
@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.
@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. kzbin.info/www/bejne/eqDOY3yhmM-UqNE
@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.
@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 11 ай бұрын
Exactly
@HoD999x
@HoD999x 8 ай бұрын
there is no protection against evil coworkers
@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 8 ай бұрын
Its not a complex object
@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.
@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 :)
@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).
@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"
@miran248
@miran248 Жыл бұрын
4:50 Another option would be a `TransferBasket` record, which could be a command.
@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.
@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 9 ай бұрын
Public ones, of course.
@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.
@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.
@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.
@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.
@worldspam5682
@worldspam5682 6 ай бұрын
You are really edging with those guard clauses 😂
@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
@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.
@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.
@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.
@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!
@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.
@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.
@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.
@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
@mohammadabedi3463
@mohammadabedi3463 Жыл бұрын
hi. how to map the Title to database as column?
@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.
@ThugLifeModafocah
@ThugLifeModafocah Жыл бұрын
I believe that the Username/Address objects that should be responsible to validate their own state.
@genechristiansomoza4931
@genechristiansomoza4931 10 ай бұрын
Edge is the Controller in MVC
@johnmcleodvii
@johnmcleodvii Жыл бұрын
That's fine until you have a hacker that succeeds in accessing your internal micro services.
@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.
@CoderCoronet
@CoderCoronet Жыл бұрын
Hum… Not sure if I like it more than guards. I have to try it out.
@user-kc8uh9ve4j
@user-kc8uh9ve4j Жыл бұрын
Problematic, if someone finds your api they vould send there own requesr with null
@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?
@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."
@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
@badderborroda4972 8 ай бұрын
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().
@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.
@SyKot
@SyKot Жыл бұрын
Why record struct instead of record class?
@CodeOpinion
@CodeOpinion Жыл бұрын
it's not nullable. however using default(T) will cause an issue still.
@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.
@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 :)
@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.
@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.
@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?
@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
@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.
@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.
@tiredlyinsane
@tiredlyinsane 6 ай бұрын
Not really a good example to use nullable guards clauses on non nullable types
@beemerrox
@beemerrox Ай бұрын
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..
@DevMeloy
@DevMeloy 8 ай бұрын
Thank god MS didn't buy Nintendo.. hopefully it stays that way.
@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.
@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.
Design Patterns: Who gives a 💩?
8:50
CodeOpinion
Рет қаралды 11 М.
Pray For Palestine 😢🇵🇸|
00:23
Ak Ultra
Рет қаралды 33 МЛН
ONE MORE SUBSCRIBER FOR 6 MILLION!
00:38
Horror Skunx
Рет қаралды 14 МЛН
"Clean Architecture" and indirection. No thanks.
25:06
CodeOpinion
Рет қаралды 41 М.
The HIDDEN Challenge of Microservices: UI Composition
11:11
CodeOpinion
Рет қаралды 12 М.
Refactor "Senior" PHP Code with Early Returns
12:09
Laravel Daily
Рет қаралды 25 М.
CRUD API + Complexity = Death by a 1000 Papercuts
12:40
CodeOpinion
Рет қаралды 17 М.
My WORST Mistakes as a Software Developer
7:52
CodeOpinion
Рет қаралды 9 М.
Nesting "If Statements" Is Bad. Do This Instead.
1:00
Flutter Mapp
Рет қаралды 3,5 МЛН
Naming Things in Code
7:25
CodeAesthetic
Рет қаралды 1,9 МЛН
"I NEED data from another service!"...  Do you really?
10:41
CodeOpinion
Рет қаралды 30 М.
Nesting "If Statements" Is Bad. Do This Instead.
1:48
Flutter Mapp
Рет қаралды 1,4 МЛН
The List Best Practice That .NET 8 Makes Possible
8:40
Nick Chapsas
Рет қаралды 43 М.
ЭТОТ ЗАБЫТЫЙ ФЛАГМАН СИЛЬНО ПОДЕШЕВЕЛ! Стоит купить...
12:54
Thebox - о технике и гаджетах
Рет қаралды 132 М.
Nokia 3310 versus Red Hot Ball
0:37
PressTube
Рет қаралды 3,8 МЛН
Цифровые песочные часы с AliExpress
0:45
wyłącznik
0:50
Panele Fotowoltaiczne
Рет қаралды 23 МЛН
👎Главный МИНУС планшета Apple🍏
0:29
Demin's Lounge
Рет қаралды 505 М.