talk: finish pres and yt script
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
---
|
||||
theme: default
|
||||
layout: center
|
||||
class: text-center
|
||||
fonts:
|
||||
sans: "CaskaydiaCove Nerd Font Mono"
|
||||
---
|
||||
|
||||
# A Trick for Designing<br>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
|
||||
|
||||
<div class="grid grid-cols-2 gap-10 text-left mt-10">
|
||||
<div>
|
||||
```go
|
||||
func NewServer(host string, port int) *Server {
|
||||
return &Server{
|
||||
host: host,
|
||||
port: port,
|
||||
}
|
||||
}
|
||||
```
|
||||
</div>
|
||||
<div>
|
||||
<v-click>
|
||||
```go
|
||||
func NewServer(opts ...Option) *Server {
|
||||
|
||||
s := &Server{
|
||||
host: "localhost",
|
||||
port: 80,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
```
|
||||
</v-click>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
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.
|
||||
|
||||
<div class="grid grid-cols-2 gap-10 text-left mt-10">
|
||||
<div>
|
||||
<v-click>
|
||||
Support TLS...
|
||||
```go
|
||||
func WithTLS(cert, key string) Option {
|
||||
return func(s *Server) {
|
||||
s.tls = true
|
||||
s.cert = cert
|
||||
s.key = key
|
||||
}
|
||||
}
|
||||
```
|
||||
</v-click>
|
||||
</div>
|
||||
<div>
|
||||
<v-click>
|
||||
Breaks nothing.
|
||||
```go
|
||||
srv := NewServer(WithPort(8080))
|
||||
|
||||
srv2 := NewServer(
|
||||
WithPort(8080),
|
||||
WithTLS("cert.pem", "key.pem"),
|
||||
)
|
||||
```
|
||||
</v-click>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
layout: center
|
||||
class: text-center
|
||||
---
|
||||
|
||||
# So why use this pattern?
|
||||
|
||||
<div class="text-left mx-auto w-max text-xl leading-10 flex flex-col">
|
||||
<div>
|
||||
<v-click>
|
||||
<carbon:checkmark-filled class="text-green-500 mr-2"/> Defaults are easy (just call New)
|
||||
</v-click>
|
||||
</div>
|
||||
<div>
|
||||
<v-click>
|
||||
<carbon:checkmark-filled class="text-green-500 mr-2"/> Usage is self-documenting
|
||||
</v-click>
|
||||
</div>
|
||||
<div>
|
||||
<v-click>
|
||||
<carbon:checkmark-filled class="text-green-500 mr-2"/> New options don't break old code
|
||||
</v-click>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
layout: center
|
||||
class: text-center
|
||||
---
|
||||
|
||||
# <span class="text-[#00ADD8]">Go</span> forth and build friendly APIs!
|
||||
|
||||
@@ -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."
|
||||
Reference in New Issue
Block a user