As an alternative solution, we can probably consider not creating N classes with separate steps, but having the builder return an interface with its own method (which is implemented through the interface). Thus , you can do with 1 builder and N interfaces .
I would suggest the same approach. I implemented it on a recent project to receive a bunch of files from different sources (base64, stream, byte array), and save it on disk or compress everything into a zip file before save on disk.
Thank you Gui. I watched your excellent presentation on Tech Excellence and was watching your video out of interest. I’m a frontend developer and after watching this it made me realise I could use this pattern to build dynamic DOM trees in my test suite without having to create any HTML files. After finding that working, I realised I could also use it to build a custom TypeScript module configuration per test. This has helped me so much, and is having a massive impact on my test code right now so thank you so much for this. 👍 I had been looking for a way to do this for a while and your video provided the answer with a really simple but powerful pattern.
Very nice approach receiving an action to build nested objects. I'll start using it :) For the required properties scenario, if you're using the builders for test cases, an alternative would be to have all required properties set to default values on your builder's constructor, so it's not possible to build an empty object for example. You won't have all the control you have with your steps approach but it's easier to maintain. I would also suggest checking the AutoBogus library to initialize your builders with generated data.
It's also worth pointing out that the builder pattern isn't always the best solution, especially when the object has multiple required fields, because then you're moving compile-time errors to run-time errors.
Once you become an experinced programmer, you will notice these problems. For instance, primitive obsession, bounded context, value objects, strongly typed ids, etc.
Good demo, but why oh why did you use the builder pattern for a dto type object? I feel if you had a proper scenario the demonstration may have carried a bit more heft - what you have is an overengineered construction of an object that can/should be initialised with properties.
We currently have a DTO that is created in your suggested way, all IDE's and Linting tools go on a fit because the creation of that DTO goes over the cyclomatic complexity threshold we use (which is the default threshold IF you enable it). Why? Because the DTO is for a detail page that also contains nested collection that may or may not be empty, and the source we need to map it from can be null, so there is a lot of (list?. Where(x => x != null || x.blabla).Select(...) ?? Enumerable.Empty()). The builder pattern is great for splitting up that logic in a way that's more readable and maintainable, especially when your DTO's are immutable value objects.
thanks for the tutorial, very useful. One note on pronunciation, genre is more often pronounced zjohn-rah, rather than "jen-reh" which sounds a lot like you are saying "gender".
Good video! But how do you handle validation errors that happen during the builder member setting? Do you throw exceptions on the provided data or do validation and return error results?
Both are applicable, depends what you want to do. Throwing exceptions on the way is easier imo, because it shortcirtcuits the construction process. If you want to have all exceptions, not only the first, for some reason, you can introduce something like a private list of exceptions in the builder. Once you have the first exception inside, that builder instance is faulty and should not construct any properties further, but only append other exceptions if encountered. In the end, when you call Build, you'd get either a valid constructed object or a nonempty list of exceptions (using some Discriminated Union implementation, be it custom or with a library)
As @stefan-d.grigorescu said, both are applicable. Also, it depends on where you are using them and the strategy you have in place. Example: I often use Builders for setup Testing data, and there, an exception is perfect. If I have a builder based on user input, I might prefer to have a "TryBuild" that returns feedback if the configuration is invalid.
@@gui.ferreira Although, I think of another point with user input, that I have not seen so much in the discussions about exceptions vs result pattern: what about an API that is designed together with its FE app, in a 1-1 scenario? There, many BE pre-validations are only for security in depth, but in almost all scenarios will not be triggered, because there are already some counterpart validations on FE that do not let the flow continue to BE at all. For example, null, empty or too long strings. If the API is designed with a sepecific FE that you know it already validates its user input from forms, then meeting a null, empty or too long string on BE really becomes an exceptional situation, hence I feel right about throwing exception.
You use a factory pattern to create/get an instance of an object encapsulating details on how it is created. Usually the caller only has to know about the abstraction, e.g., an interface and it calls the factory to create the instance. Multiple implementations might coexist and the factory is responsible for getting the right one. Builder is different. It's more of a sintaxe sugar, a way to build complex objects by composing steps. In this case, the caller has to know how the object is created
I'm sorry, I just keep thinking of the "but why?" meme 😄. Yes, patterns are cool and all that, but there's little value in writing a bunch of classes if all you want to do is build reports... Chances are you are part of a team that will have to maintain your code at some point. We really need to do better as engineers and keep it simple...
Thanks you showing good stuff, can you please not move fast or jump from place to place so quick, when you learn we need couple of seconds to ingest what is happening
Great video as always. I'm a big fan of the Fluent builder style but I do find it requires a lot of coding to support. I've been working on a Roslyn library to generate the API as extension methods automatically. It's still in development but should go live soon. I think it will be very helpful to engineers and I'd love your feedback on it if you have time.
As an alternative solution, we can probably consider not creating N classes with separate steps, but having the builder return an interface with its own method (which is implemented through the interface). Thus , you can do with 1 builder and N interfaces .
I would suggest the same approach. I implemented it on a recent project to receive a bunch of files from different sources (base64, stream, byte array), and save it on disk or compress everything into a zip file before save on disk.
Awesome! You also have the aditional advantage of not being able to create (or seeing) nested objects from the client code.
Thank you Gui. I watched your excellent presentation on Tech Excellence and was watching your video out of interest. I’m a frontend developer and after watching this it made me realise I could use this pattern to build dynamic DOM trees in my test suite without having to create any HTML files. After finding that working, I realised I could also use it to build a custom TypeScript module configuration per test. This has helped me so much, and is having a massive impact on my test code right now so thank you so much for this. 👍
I had been looking for a way to do this for a while and your video provided the answer with a really simple but powerful pattern.
I used this to create invoices on a system a while back. Didn't know it was a pattern at the time... I loved that "I came up with it" 😅.
That's a proof of greatness 😅
Nicely presented! Some nice patterns to consider 😃
Very nice approach receiving an action to build nested objects. I'll start using it :)
For the required properties scenario, if you're using the builders for test cases, an alternative would be to have all required properties set to default values on your builder's constructor, so it's not possible to build an empty object for example. You won't have all the control you have with your steps approach but it's easier to maintain.
I would also suggest checking the AutoBogus library to initialize your builders with generated data.
It's also worth pointing out that the builder pattern isn't always the best solution, especially when the object has multiple required fields, because then you're moving compile-time errors to run-time errors.
The art or resolving non-existing problems
Programmers: when we don't have enough problems to solve we make an effort to create them 😂
Once you become an experinced programmer, you will notice these problems. For instance, primitive obsession, bounded context, value objects, strongly typed ids, etc.
Good demo, but why oh why did you use the builder pattern for a dto type object? I feel if you had a proper scenario the demonstration may have carried a bit more heft - what you have is an overengineered construction of an object that can/should be initialised with properties.
Yeah, in such a scenario I would just use a parameterless constructor with required properties
We currently have a DTO that is created in your suggested way, all IDE's and Linting tools go on a fit because the creation of that DTO goes over the cyclomatic complexity threshold we use (which is the default threshold IF you enable it). Why? Because the DTO is for a detail page that also contains nested collection that may or may not be empty, and the source we need to map it from can be null, so there is a lot of (list?. Where(x => x != null || x.blabla).Select(...) ?? Enumerable.Empty()).
The builder pattern is great for splitting up that logic in a way that's more readable and maintainable, especially when your DTO's are immutable value objects.
this looks great!, but how does it align with the DDD pattern. the validation and business logic should be in the domain model ryt
Nice video, this reminds me of fluent assertions. It is always useful to have a more natural way of coding.
Keep it up!
FluentAssertions, FluentValidations, Fluent everything 😁
thanks for the tutorial, very useful.
One note on pronunciation, genre is more often pronounced zjohn-rah, rather than "jen-reh" which sounds a lot like you are saying "gender".
Great video. I fear looking at the code of the people saying "but why".
Looks like your paycheck depends on number of code lines?
Good video! But how do you handle validation errors that happen during the builder member setting? Do you throw exceptions on the provided data or do validation and return error results?
Both are applicable, depends what you want to do.
Throwing exceptions on the way is easier imo, because it shortcirtcuits the construction process.
If you want to have all exceptions, not only the first, for some reason, you can introduce something like a private list of exceptions in the builder. Once you have the first exception inside, that builder instance is faulty and should not construct any properties further, but only append other exceptions if encountered. In the end, when you call Build, you'd get either a valid constructed object or a nonempty list of exceptions (using some Discriminated Union implementation, be it custom or with a library)
As @stefan-d.grigorescu said, both are applicable.
Also, it depends on where you are using them and the strategy you have in place.
Example: I often use Builders for setup Testing data, and there, an exception is perfect. If I have a builder based on user input, I might prefer to have a "TryBuild" that returns feedback if the configuration is invalid.
@@gui.ferreira Although, I think of another point with user input, that I have not seen so much in the discussions about exceptions vs result pattern: what about an API that is designed together with its FE app, in a 1-1 scenario? There, many BE pre-validations are only for security in depth, but in almost all scenarios will not be triggered, because there are already some counterpart validations on FE that do not let the flow continue to BE at all.
For example, null, empty or too long strings. If the API is designed with a sepecific FE that you know it already validates its user input from forms, then meeting a null, empty or too long string on BE really becomes an exceptional situation, hence I feel right about throwing exception.
can you make a comparison with the factory pattern? isnt this the same?
You use a factory pattern to create/get an instance of an object encapsulating details on how it is created. Usually the caller only has to know about the abstraction, e.g., an interface and it calls the factory to create the instance. Multiple implementations might coexist and the factory is responsible for getting the right one.
Builder is different. It's more of a sintaxe sugar, a way to build complex objects by composing steps. In this case, the caller has to know how the object is created
Too much unnecessary code. Steps in predefined order etc.
Well done!
Nailed it.
Thanks for de vídeo but i prefer the normal way hahahahha
Is it not less efficient to each time return the entire this?
Nope. There's no entire "this", since it is just a pointer to the actual object
First comment and like, good explanation
🏃➡️ 🙏
I'm sorry, I just keep thinking of the "but why?" meme 😄. Yes, patterns are cool and all that, but there's little value in writing a bunch of classes if all you want to do is build reports... Chances are you are part of a team that will have to maintain your code at some point. We really need to do better as engineers and keep it simple...
Thanks you showing good stuff, can you please not move fast or jump from place to place so quick, when you learn we need couple of seconds to ingest what is happening
Great video as always. I'm a big fan of the Fluent builder style but I do find it requires a lot of coding to support. I've been working on a Roslyn library to generate the API as extension methods automatically. It's still in development but should go live soon. I think it will be very helpful to engineers and I'd love your feedback on it if you have time.
If you are interested, the library is available as a package called Fluentify.
why?... this is just bloat
Like your content, but.... this is not everyone's cup of tea.
Lots of verbage.
🌟
This is terrible code object initializes exist.
C#, meet SmallTalk.
Many design patterns used today, were defined on the Smalltalk days