Тёмный

Stop using trivial Guard Clauses! Try this instead. 

CodeOpinion
Подписаться 87 тыс.
Просмотров 48 тыс.
50% 1

Опубликовано:

 

29 сен 2024

Поделиться:

Ссылка:

Скачать:

Готовим ссылку...

Добавить в:

Мой плейлист
Посмотреть позже
Комментарии : 180   
@nicktech2152
@nicktech2152 2 года назад
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 2 года назад
I feel like I toss around enough lingo and terms that I try and purposely avoid it sometimes... as you just noticed!
@miran248
@miran248 2 года назад
4:50 Another option would be a `TransferBasket` record, which could be a command.
@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.
@YamiSuzume
@YamiSuzume 2 года назад
So... the message is "don't use guard clauses, when doing it wrong"? lol
@CodeOpinion
@CodeOpinion 2 года назад
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 :)
@zimcoder
@zimcoder 2 года назад
So its How to use Guard clauses in a proper and smart way rather than "Stop using Guard Clauses!"😏
@CodeOpinion
@CodeOpinion 2 года назад
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.
@asdfghyter
@asdfghyter 2 года назад
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?
@ДаниилРабинович-б9п
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 11 месяцев назад
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 11 месяцев назад
@@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 2 года назад
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 2 года назад
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 2 года назад
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 2 года назад
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 2 года назад
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 2 года назад
@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 2 года назад
Using record types instead of primitives is definitely a good design improvement! Nice video!
@robertmrobo8954
@robertmrobo8954 2 года назад
I waited the whole video to hear him mention "Primitive Obsession"
@CodeOpinion
@CodeOpinion 2 года назад
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 2 года назад
Great not just for nulls, but for other explit invariants, like size, length, charset, etc.
@harambetidepod1451
@harambetidepod1451 2 года назад
No
@LunaticEdit
@LunaticEdit 2 года назад
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 2 года назад
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.
@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.
@seltox6320
@seltox6320 2 года назад
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 2 года назад
Agreed. Structs aren't nullable but they're still issues in my example related to default.
@xpoftik
@xpoftik 2 года назад
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 2 года назад
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.
@MRApht
@MRApht 2 года назад
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 2 года назад
Thanks!
@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 Год назад
there is no protection against evil coworkers
@beemerrox
@beemerrox 5 месяцев назад
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..
@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.
@badderborroda4972
@badderborroda4972 Год назад
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.
@allyourfuturebelongstochina
@allyourfuturebelongstochina 2 года назад
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 2 года назад
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 2 года назад
@@MiningForPies how does my coffee machine even work
@flobuilds
@flobuilds 2 года назад
@@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 😂
@DaminGamerMC
@DaminGamerMC 2 года назад
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.
@arunnair7584
@arunnair7584 2 года назад
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 Год назад
Its not a complex object
@georgehelyar
@georgehelyar 2 года назад
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.
@kashqahy6171
@kashqahy6171 2 года назад
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 :)
@hubrismaxim
@hubrismaxim 2 года назад
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?
@VaragornX
@VaragornX 2 года назад
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 2 года назад
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.
@donnyroufs551
@donnyroufs551 2 года назад
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 2 года назад
Where have you heard that?
@mabdullahsari
@mabdullahsari 2 года назад
Where have you heard that, exactly? I instantiate dozens of ValueObjects within Infrastructure.
@donnyroufs551
@donnyroufs551 2 года назад
@@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 2 года назад
@@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 2 года назад
@@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
@goisenate
@goisenate 2 года назад
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 2 года назад
I updated the title to hopefully reflect the videos content better.
@Galakyllz
@Galakyllz 2 года назад
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 2 года назад
Could be anything where you want to be valid and push that logic further up the stack
@kylekeenan3485
@kylekeenan3485 2 года назад
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.
@ליעדדמבין-ש8ד
@ליעדדמבין-ש8ד 2 года назад
Problematic, if someone finds your api they vould send there own requesr with null
@johnmcleodvii
@johnmcleodvii 2 года назад
That's fine until you have a hacker that succeeds in accessing your internal micro services.
@DevMeloy
@DevMeloy Год назад
Thank god MS didn't buy Nintendo.. hopefully it stays that way.
@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 Год назад
Public ones, of course.
@leftjabrighthook
@leftjabrighthook 2 года назад
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 2 года назад
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 2 года назад
Caught on that it's a value object and I never even mentioned it in the video 😉
@leftjabrighthook
@leftjabrighthook 2 года назад
@@luan_maik I see that but he used Username for everything like "buyerId" etc? I still must be missing something.🤷‍♀
@luan_maik
@luan_maik 2 года назад
@@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 2 года назад
@@leftjabrighthook if the user Identifier was a integer, could be a value object called UserId, or more generic as Indetifier
@s0psAA
@s0psAA 2 года назад
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 2 года назад
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).
@tiredlyinsane
@tiredlyinsane 10 месяцев назад
Not really a good example to use nullable guards clauses on non nullable types
@marco.defreitas
@marco.defreitas Год назад
Simple yet effective, couldn't agree more! Good stuff 😄
@zekeanthony
@zekeanthony 2 года назад
Yes stop using them, they were an unnecessary creation to begin with.
@CoderCoronet
@CoderCoronet 2 года назад
Hum… Not sure if I like it more than guards. I have to try it out.
@mariorobben794
@mariorobben794 2 года назад
Instead of creating a custom value type (which holds a primitive type), I use the StronglyTypedId library.
@CodeOpinion
@CodeOpinion 2 года назад
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.
@worldspam5682
@worldspam5682 10 месяцев назад
You are really edging with those guard clauses 😂
@Yotanido
@Yotanido 2 года назад
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 2 года назад
I've updated the title to hopefully better reflect the video.
@mohammadabedi3463
@mohammadabedi3463 2 года назад
hi. how to map the Title to database as column?
@CallousCoder
@CallousCoder 2 года назад
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.
@xtrembaker7337
@xtrembaker7337 2 года назад
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 2 года назад
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 2 года назад
@@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 2 года назад
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 2 года назад
@@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 2 года назад
@@anthonylevine8783 it’s a business rule. There are strict rules over who can do what to whom.
@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.
@genechristiansomoza4931
@genechristiansomoza4931 Год назад
Edge is the Controller in MVC
@Tony-dp1rl
@Tony-dp1rl 2 года назад
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 2 года назад
Agreed.
@MiningForPies
@MiningForPies 2 года назад
Some people seem to think exceptions are free.
@9SMTM6
@9SMTM6 2 года назад
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.
@karelhrkal8753
@karelhrkal8753 2 года назад
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
@ransandu
@ransandu 2 года назад
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.
@ThugLifeModafocah
@ThugLifeModafocah 2 года назад
I believe that the Username/Address objects that should be responsible to validate their own state.
@travisarndt853
@travisarndt853 2 года назад
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 2 года назад
Sure. I think they should just actually be consistent within the app, but they weren't.
@kormuss
@kormuss 2 года назад
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."
@FengHuang13
@FengHuang13 2 года назад
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 2 года назад
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 2 года назад
@@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.
@gustavnilsson6597
@gustavnilsson6597 2 года назад
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 2 года назад
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.
@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.
@matthewhartz9235
@matthewhartz9235 2 года назад
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 2 года назад
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 2 года назад
@@CodeOpinion Yeah i apologize, i was too quick to respond. I love this solution!
@GustavoEhrhardt
@GustavoEhrhardt 2 года назад
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 2 года назад
Isn't that true for all typed languages?
@GustavoEhrhardt
@GustavoEhrhardt 2 года назад
@@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"
@corey1426
@corey1426 2 года назад
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 2 года назад
Sounds like an Anti-Corruption layer 👏
@CodeOpinion
@CodeOpinion 2 года назад
Yup. ru-vid.com/video/%D0%B2%D0%B8%D0%B4%D0%B5%D0%BE-Dok2Ikcjaro.html
@SyKot
@SyKot 2 года назад
Why record struct instead of record class?
@CodeOpinion
@CodeOpinion 2 года назад
it's not nullable. however using default(T) will cause an issue still.
@pdevito
@pdevito 2 года назад
So pretty much a value object?
@SnOrfus
@SnOrfus 2 года назад
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.
@breakpoin80
@breakpoin80 2 года назад
That's really cool! Would you still do model validation with something like FluentValidation for more complex types?
@CodeOpinion
@CodeOpinion 2 года назад
Sure, just forcing that further up the stack at creation time.
@mrcsjvc
@mrcsjvc 2 года назад
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 2 года назад
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 2 года назад
@@anatolia23 Thank you.
@CodeOpinion
@CodeOpinion 2 года назад
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 2 года назад
@@CodeOpinion Probabily they will :)
@diligencehumility6971
@diligencehumility6971 2 года назад
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
@Mvision123
@Mvision123 2 года назад
Nullabillity can't help here?
@CodeOpinion
@CodeOpinion 2 года назад
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 2 года назад
@@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 2 года назад
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 2 года назад
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 2 года назад
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 2 года назад
@@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 2 года назад
@@adambickford8720 I don’t get how that’s obnoxious.
@adambickford8720
@adambickford8720 2 года назад
@@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 2 года назад
@@adambickford8720 like? How would you do it?
@anatolia23
@anatolia23 2 года назад
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 2 года назад
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.
@waytospergtherebro
@waytospergtherebro 2 года назад
Blah blah blah Millennial needs attention because nobody notices him at work.
@anthonytrad
@anthonytrad 2 года назад
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 2 года назад
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.
@niveliyahu146
@niveliyahu146 2 года назад
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 2 года назад
Or some type of "builder" that can do the preconditions.
@kis.stupid
@kis.stupid 2 года назад
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?
Далее
Design Patterns: Who gives a 💩?
8:50
Просмотров 12 тыс.
荧光棒的最佳玩法UP+#short #angel #clown
00:18
Don't throw exceptions in C#. Do this instead
18:13
Просмотров 259 тыс.
CRUD API + Complexity = Death by a 1000 Papercuts
12:40
How Guard Clauses Can Make Your Code Better
13:54
Просмотров 13 тыс.
Why You Shouldn't Nest Your Code
8:30
Просмотров 2,7 млн
Free your API. Misconceptions are holding you back!
9:07
Cleaner Code: 3 Ways You Can Write Cleaner Code
7:41
"I NEED data from another service!"...  Do you really?
10:41
Winglang in 10 minutes
11:39
Просмотров 10 тыс.
荧光棒的最佳玩法UP+#short #angel #clown
00:18