diff --git a/slides/functional_options_pattern_in_go/index.md b/slides/functional_options_pattern_in_go/index.md new file mode 100644 index 0000000..e997140 --- /dev/null +++ b/slides/functional_options_pattern_in_go/index.md @@ -0,0 +1,216 @@ +--- +theme: default +layout: center +class: text-center +fonts: + sans: "CaskaydiaCove Nerd Font Mono" +--- + +# A Trick for Designing
Friendly APIs in Go + +--- +layout: center +--- + +# The Optional Configuration Problem + +```go +// No defaults? +// What is sensible? +// What does 'true' do? +// What is '30'? +srv := NewServer("localhost", 8080, 30, true) +``` + +--- +layout: center +--- + +# "We need to add a TLS parameter." + +```go +func NewServer(host string, port int, timeout int) *Server { + // ... +} +``` + +--- +layout: center +--- + +# "Just use a struct" + +```go +srv := NewServer(Config{ + Host: "localhost", + Port: 0, +}) +``` + +--- +layout: center +class: text-center +--- + +# "Nah bro, use functional options." + +--- +layout: center +--- + +# 1. Define the option + +```go +type Server struct { + host string + port int +} + +// Option is a function that modifies the Server +type Option func(*Server) +``` + +--- +layout: center +--- + +# 2. Create closures + +```go +// WithPort captures the 'port' value +func WithPort(port int) Option { + // It returns a closure + return func(s *Server) { + s.port = port + } +} + +func WithHost(h string) Option { + return func(s *Server) { + s.host = h + } +} +``` + +--- +layout: center +class: text-center +--- + +# 3. Get variadic + +
+
+```go +func NewServer(host string, port int) *Server { + return &Server{ + host: host, + port: port, + } +} +``` +
+
+ +```go +func NewServer(opts ...Option) *Server { + + s := &Server{ + host: "localhost", + port: 80, + } + + for _, opt := range opts { + opt(s) + } + + return s +} +``` + +
+
+ +--- +layout: center +--- + +# Almost as good as C# + +```go +srv := NewServer( + WithHost("api.google.com"), + WithPort(9090), + WithTimeout(30 * time.Second), +) +``` + +--- +layout: center +class: text-center +--- + +# Extensibility? We got you. + +
+
+ +Support TLS... +```go +func WithTLS(cert, key string) Option { + return func(s *Server) { + s.tls = true + s.cert = cert + s.key = key + } +} +``` + +
+
+ +Breaks nothing. +```go +srv := NewServer(WithPort(8080)) + +srv2 := NewServer( + WithPort(8080), + WithTLS("cert.pem", "key.pem"), +) +``` + +
+
+ +--- +layout: center +class: text-center +--- + +# So why use this pattern? + +
+
+ + Defaults are easy (just call New) + +
+
+ + Usage is self-documenting + +
+
+ + New options don't break old code + +
+
+ +--- +layout: center +class: text-center +--- + +# Go forth and build friendly APIs! + diff --git a/slides/functional_options_pattern_in_go/script.md b/slides/functional_options_pattern_in_go/script.md new file mode 100644 index 0000000..6808eb0 --- /dev/null +++ b/slides/functional_options_pattern_in_go/script.md @@ -0,0 +1,148 @@ +# Script: A Trick for Designing Friendly APIs in Go + +--- + +### The Hook (The Pain) + +**[VISUAL: Face Camera]** + +"There is nothing worse than opening a codebase, finding a constructor, and seeing a list of magic numbers and boolean flags." + +"You know the ones. `NewServer(true, 30, false)`." + +"What is true? What is 30? Is false good or bad? You have to go read the definition just to understand the call site. It’s a bad developer experience." + +--- + +### The Problem + +**[VISUAL: Slide 2 - The Optional Configuration Problem]** + +"This is the problem we are trying to solve. The 'Optional Configuration' problem." + +"Go doesn't support method overloading like C# or Java. So when you write a constructor like `NewServer`, you’re stuck with that signature." + +"If you have optional parameters—like a timeout or a verbose flag—you end up forcing the user to pass `0` or `nil` or `true` just to satisfy the compiler." + +--- + +### The Breaking Change + +**[VISUAL: Slide 3 - "We need to add a TLS parameter"]** + +"And it gets worse when requirements change. Let's say six months later, you need to add TLS support." + +"If you just add a parameter to the function, you have now broken every single place in your application where `NewServer` was called. You have to go refactor the entire world just to add one optional setting." + +--- + +### The "Okay" Alternative + +**[VISUAL: Slide 4 - "Just use a struct"]** + +"Now, the common response is: 'Just use a config struct.'" + +"And honestly? This is fine. It's better than magic numbers. But it has a flaw: Ambiguity around zero values." + +"If I pass `Port: 0` in that config... do I mean 'Please bind to a random available port'? Or do I mean 'I forgot to set this, please use the default port 80'?" + +"Distinguishing between 'default' and 'intentionally zero' is annoying with structs." + +--- + +### The Solution + +**[VISUAL: Slide 5 - "Nah bro, functional options"]** + +"So, the Go community settled on a better pattern. Functional Options." + +"It looks a little scary at first because it uses closures, but once you get it, you’ll never go back." + +--- + +### Step 1: The Type + +**[VISUAL: Slide 6 - 1. Define the option]** + +"Step one: We define our Server struct, but we keep the fields unexported." + +"Then, we define a type—let's call it `Option`. An `Option` is just a function that takes a pointer to your Server and modifies it. It returns nothing." + +--- + +### Step 2: The Closures + +**[VISUAL: Slide 7 - 2. Create closures]** + +"Step two: We create 'Constructor' functions that return that `Option` type." + +"This is where the magic happens. Look at `WithPort`. It takes an integer, and it returns a closure that *captures* that integer." + +"When that inner function eventually runs, it will apply that specific port to the server." + +--- + +### 2:45 - Step 3: The Variadic Constructor + +**[VISUAL: Slide 8 - 3. Get variadic]** + +"Step three: The Constructor." + +"Instead of taking a fixed list of arguments, we take a variadic slice of our `Option` type." + +**[CLICK to reveal the implementation]** + +"Inside the constructor, we do three things:" +1. "We initialize the server with sensible **defaults**. (Host is localhost, Port is 80)." +2. "We loop through the provided options and execute them. This overwrites the defaults with whatever the user provided." +3. "We return the server." + +--- + +### The Result + +**[VISUAL: Slide 9 - Almost as good as C#]** + +"The result is a beautiful API usage." + +"For my C# friends out there, this feels a lot like Named Parameters. It reads like a sentence: `NewServer, WithHost, WithPort`." + +"I don't need to know what the default timeout is. I don't need to pass `nil` for things I don't care about. I just declare what I want to change." + +--- + +### Extensibility + +**[VISUAL: Slide 10 - Extensibility? We got you.]** + +"But the killer feature is extensibility." + +**[CLICK to reveal WithTLS]** + +"Let's go back to that TLS requirement. I can add a `WithTLS` option function..." + +**[CLICK to reveal usage]** + +"...and I don't break a single existing line of code. `srv` (the first one) still compiles perfectly. `srv2` uses the new feature. Backward compatibility is preserved." + +--- + +### Summary + +**[VISUAL: Slide 11 - So why use this pattern?]** + +"To recap, why should you use this?" + +**[CLICK]** "1. Defaults are easy. You define them once in the constructor, and the user never has to think about them." +**[CLICK]** "2. It's self-documenting. No more `true, false, 30`." +**[CLICK]** "3. It's future-proof. You can add new options forever without breaking your consumers." + +--- + +### Outro + +**[VISUAL: Slide 12 - Go forth...]** + +"This is the standard for a reason. If you're building a library or a package in Go, treat your users nicely. Use Functional Options." + +"I'll see you in the next one."