Learning Golang: Functional Options / Default Configuration Values Pattern

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello my name is mario welcome to another learning go video in today's episode i'm going to be sharing with you a pattern called functional options as usual the link to the code will be in the description of this video it includes the final implementation what i'm going to be showing you in this video is the actual step-by-step how i refactor a current implementation to support this banner and why i'm and why am i doing it in the first place so let's look at this example that i have right here is your typical http server implementation you can take the same idea and do something similar with literally any type that you have in your domain in your project and to show you how this it works actually to show that it works first of all is that i can take and run it and then i can do a curl x get http localhost 8080 and then it will print out hello nothing spectacular crazy so the idea with this is that when we have a type that we want to provide default values the few different ways to do this is to extract this into a new constructor or initial initializer or something similar if you remember in go there is no such thing as constructors so the closest will be to literally instantiate the type using literally an instantiation similar to what i have right here which doesn't have arguments and it's not a specifically specifying the fields that are applicable to that type or literally just passing in something like this now if i want to make and this literally works i mean you can return this and i can use it right here and literally that serves the same purposes and basically talking in the context of the feature that i'm trying to implement is practically the same right so if i compile this it will build it says max is undefined okay so we're going to be moving this right here and s equals max so i'll compile everything is fine everything is working the thing it didn't change everything is fine and dandy now the whole point of this pattern is i i want to provide default values or configuration values to what i have in the server currently one way to think about this is that hey i have these three fields i want to provide a way to configure those fields from outside of my function typically the way we do it we define three arguments with the time the finite time address duration read timeout and another write timeout and then we can change this to be other we can change this to be read t and we can change this to be right t and then when we go around here i can just literally do the pass in the values that i have that i had before right everything pretty straightforward so far nothing nothing really overly complicated now the issue with this is that the more arguments we all write the more fields we need to be available for configuration the more arguments we're going to be adding and that could be problematic the other problem with this is that when we are passing in fields that happen to be the same type because the convention and the the idiomatic way to implement functions in go is to somehow er cons or keep those types together to each other for example in this case i have a read d and write t next to each other because i'm using the time duration type typically this is the way you should be doing it but you can argue with me and say you know what i want to move read t and make it the first argument others the second one and so on so forth however it's not clear in here exactly what these values mean in the first place it looks like a string some sort of iteration and some sort of duration but i don't know exactly what this means in the context of a calling new server typically what comes next would be to define another sort of some sort of a struct that take these fields and now we can pass it as an option and we can call it server ops and make these fields exported you can call it read timeout and the same will be with the right timeout so now you have this we go and modify uh the code to match what we define above so now instead of passing in a bunch of arguments we change it in a way that it becomes what other programming languages or when you are using a an inheritance-based programming language is called a dto or data transfer object typically a configuration kind of a class which again in this case because go doesn't have classes and doesn't have these kind of things it it i mean it works for sure and you can define and we'll call this address oh it's called other okay leave it the way it is i will rename it in a moment we have read timeout and then we have write timeout and so far everything seems to be working um it makes sense to for what we're trying to do however this still doesn't solve what we were trying to implement in the first place when we were trying to think hey now i already have values right here we can make this explicit let me rename this to address so now we have explicit values that need to be available when we instantiate a new server what about those cases when we want to provide a default configuration defining a struct for those values does not solve the problem because you will still need to perhaps maybe define a new server ops that indicates hey not that but rather a new server ops that indicates hey the default values for this thing will be what i have down here which is i'm going to use literally copy and paste everything right here paste it return it and now you can define some default values right you can say new server ops and now you have a function an initializer of a type that is an options argument or a configuration argument that you can pass in to another type that is overly complicated in my opinion um one way to solve this problem is to start adding introducing the concept of a functional option and the way it works is by defining a new type called that we can call it whatever you want but indicate in this case we are going to be called option that receives that it declares declare as a function that receives an http server and if you remember the video when i was covering and discussing about types in go you remember that you can define types like this that happen to be actual functions and you can even add methods to those types we're not going to be doing that but this is one of those crazy things that is available in go is really powerful though and so you can take that option and then for each option you can indicate and create functions that apply that follow this signature and at the same time make them as arguments as default values of your type let me give you an example so we have a function that says with address that receives an address type and returns a function it returns the option which in this case will be an http server function so we do this and what we are going to be having is because we are receiving that in the function that we are being executed as we can actually assign the address right here let me rename this and and and let's look at this before i add the other two values for read time out and right amount so we have an option type that declares a function so this right now this option is actually a function what is happening right here is that i'm returning the option which is the signatures that i have right here is it's the same as this one and the way we're going to be using it is by calling it right here so let me remove all of this we will not need any of these things so we will do something like this with address s so what is going to be happening here is that now i'm calling the option that is defined previously and passing in the address of the http server this is a bit hard to maybe comprehend in the beginning but you will see how this everything makes sense in a moment if we continue with this you will notice that i don't need this anymore i will clean this up in a moment i will let me comment this out just for the sake of making sure that my editor doesn't go crazy so i'm going to be defining three functions or yeah three functions to implement the option type that will allow us to define arguments that we can use for the initializer that then we can use for configuration values for example we have one for function with address we are going to be calling out with read timeout that is going to be a time duration and it's going to be doing something similar to what we had be above which was going to be returning the option and and let me copy and paste this to make it quicker we have the read timeout equals to the t and literally we're going to be doing something similar with the right timeout so we have the right amount t instead of using the read timeout field we're going to use the right time of field and if we run this it will compile everything is fine but really we haven't used that new code in the initializer that we defined here so what we're going to be adding is we're going to be adding a new argument to the constructor or initializer and this is where the functional options comes in and plus the default configuration values i will show you that in a moment we're going to be using something called variatic arguments which is sort of a way to sort of it's like a slice behind the scenes that happens to be one argument of your of any of your arguments usually i think it is only the last one so it allows you to pass in values of the same type as command as arguments in the method i will show you it's a bit hard to explain but i'll show you that now all of this will make sense so we have our server uh with you know configuration and this is actually the opt right here is actually an is a slice so we can do a range and what i was showing you before is i can take opt pass in the server and as soon as bypass in the server what is going to happen is depending on the option that is implemented the function that implements the option type will be called and we will be receiving a pointer to the http server and therefore applying the configuration that i have right here okay so if i go and modify this and i say hey with address i don't know 9191 with rate timeout 100 times millisecond with right timeout 100 times millisecond now let me move this to arrange this a little bit so you can see what's going on oops it looks like oh i made a mistake i'm missing usually it shouldn't be in there so like this so i have this i compile it i run it and if i try to run my example again it will be running obviously because now the port is different this will give me an error so i need to change it to the port i just added so 1991 and now i'm explicitly indicating the values right here the cool thing about this instead of using for example a struct is that you can define default values default configuration options uh another good thing is that we can add validation to this so if we go back and modify with option and we say you know what we are going to be returning an error and therefore we're going to be validating oops what the heck we're going to be validating the values that we're going to receive in the argument wow what's going on with my editor you know sometimes i have this issue with go please which is the thing that is power and all this autocomplete it's just i don't know what's going on it goes crazy my my neo beam goes crazy so again we are updating the signature of each one of the functions and now everything it's okay oops i forgot one thing that's why it keeps complaining so i do error i do error right here and i do error right here so everything now is following the signature that is defined in the option type what comes next is to take this error and if error is different than nil then return http server and we can fix this typo and we can do an uh error s print f we can wrap it because i don't know option failed we can wrap it return the error we can return nil obviously this has to be an initialized type and here we change it to the signature now it's different so we are adding error all over the place to make sure that we are now honoring what we are supposed to be doing now we can change it here if err is different than nil we can say a typical let me move this up log fat fatal l new server cooldown initialize cooldown initialize server i will fix that typo in a moment so everything seems to be working a i oops obviously this incorrect apologies cool thing that we have uh an editor that tells you more or less what's going on right much more or less what's going on so what what what's happening here is printf fail uh why is complaining let's see so type is off sorry is error f obviously you cannot use a sprint f for returning an error you need to use error ref so everything seems to be working everything is to be fine we don't have any validation yet but let's say i want to indicate a if t is greater than one second i'm not allowing it to be greater than one second so i want to return errors new max time mac timeout value not allowed let's call it keep it simple so we can define this and we can do something similar to to the right timeout as well we can say hey if we have a timeout but if we have a timeout that is greater than one second it's not correct in the context of what we're trying to do now all of this works so far and i didn't really change anything except i added validation but if you recall the configuration that i have is still the same the logic let me open this in a different way the values that i have here do not trigger the validation errors or rather the validation itself if they don't return errors but if i say if i change this 100 millisecond to second this will immediately fail because it's exceeding the validation that i have because we are not allowing a timeout that is greater than one second this is really powerful because not only you can have default values that i haven't added yet but also you can add configuration options and validations to those configuration options now about the default values what i like doing is define constants and the whole point of defining the constants is to make explicit the default values that i have so i have read timeout time duration equals let's see 100 times time millisecond we have a read timeout which in this case will not be our rate amount the right will be at right amount and finally we have let's see a server address we can call it string equals 8080. another thing that i want to explicitly indicate we can call it default so we have default values right here let's say hey these are the default values for the configuration for our server and whatnot and how this is implemented when we are initializing the server we can literally define this which in this case in the case of when the values are not passed in as part of the ops argument they will not be overwritten for example if i go back here and i comment out the line with address what is going to happen is it's going to be using the configuration the default value which in this case will be 8080. so i change back to one second so we can compile and actually run so right now 80 1991 will be gone and we are going we're back to 80 80. really cool stuff right so i hope all of these you found it useful i really like this way of defining default configuration options uh using the this pattern because it allows you like i said it allows you to define validations as well as default configurations and depending on the api api that you have implemented you can keep adding options without breaking the existing api and with that what i mean is that if i release this as a let's say as a package and go and if i want to add another with function whatever users were using before it will not break their compilation when we're they're using my package we i can still add new options without breaking whatever they have already existed well that's it thank you for watching and i hope you find this use of any comments any questions just let me know take care see
Info
Channel: Mario Carrion
Views: 755
Rating: undefined out of 5
Keywords: golang, golang design pattern, golang design patterns, golang functional option, golang default configuration values, golang configuration values, golang functional options, Golang tutorial, Golang tutorial beginners, Golang beginners, golang design pattern tutorial, golang constructor
Id: fe8vJSIzWss
Channel Id: undefined
Length: 20min 7sec (1207 seconds)
Published: Fri Nov 05 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.