golang实现函数式选项模式API设计的插件库options-gen的使用
Golang实现函数式选项模式API设计的插件库options-gen的使用
options-gen是一个代码生成器,可以帮助你创建类似于Dave Cheney提出的函数式选项模式API。它能自动为你的服务/客户端等生成选项配置代码。
安装
go install github.com/kazhuravlev/options-gen/cmd/options-gen@latest
使用示例
基本用法
package mypkg
import (
"io"
"log"
)
//go:generate options-gen -out-filename=options_generated.go -from-struct=Options
type Options struct {
logger log.Logger `option:"mandatory"` // 必填选项
listenAddr string `option:"mandatory" validate:"required,hostname_port"`
closer io.Closer `validate:"required"`
}
运行生成命令:
go generate ./...
这会生成options_generated.go
文件,包含选项构造函数:
// options_generated.go
package mypkg
import (
"log"
)
func NewOptions(
// 必填选项,作为参数强制要求
logger log.Logger,
listenAddr string,
// 可选选项
other ...Option,
) {
// ...
}
// Validate会检查所有选项是否符合要求
func (o *Options) Validate() error {
// ...
}
使用生成的选项
package mypkg
import "fmt"
type Component struct {
opts Options // 嵌入选项结构体
}
func New(opts Options) (*Component, error) { // 构造函数
if err := opts.Validate(); err != nil { // 验证选项
return nil, fmt.Errorf("cannot validate options: %w", err)
}
return &Component{opts: opts}, nil // 嵌入选项
}
在main.go中使用:
package main
func main() {
c, err := mypkg.New(mypkg.NewOptions( /* ... */))
if err != nil {
panic(err)
}
}
泛型支持
//go:generate options-gen -from-struct=Options
type Options[T any] struct {
addr string `option:"mandatory" validate:"required,hostname_port"`
ch <-chan T `option:"mandatory"`
}
真实世界的HTTP客户端示例
//go:generate options-gen -from-struct=Options
type Options struct {
httpClient *http.Client `option:"mandatory"`
baseURL string `option:"mandatory" validate:"required,url"`
token string `validate:"required"`
timeout time.Duration `default:"30s" validate:"min=1s,max=5m"`
maxRetries int `default:"3" validate:"min=0,max=10"`
userAgent string `default:"options-gen/1.0"`
}
type Client struct {
opts Options
}
func NewClient(opts Options) (*Client, error) {
if err := opts.Validate(); err != nil {
return nil, fmt.Errorf("validate options: %w", err)
}
return &Client{opts: opts}, nil
}
// 使用:
client, err := NewClient(NewOptions(
httpClient,
"https://api.example.com",
WithToken("secret-token"),
WithTimeout(10*time.Second),
))
配置选项
字段排除
//go:generate options-gen -from-struct=Options -exclude="internal*;debug*"
type Options struct {
addr string `option:"mandatory" validate:"required,hostname_port"`
internalConn net.Conn // 排除:匹配"internal*"
debugMode bool // 排除:匹配"debug*"
logLevel string // 包含
}
自定义选项类型名称
//go:generate options-gen -from-struct=Options -out-setter-name=HTTPOption
type Options struct {
timeout time.Duration
headers map[string]string
}
// 生成的代码将使用HTTPOption而不是OptOptionsSetter
多个选项结构体
当包中有多个选项结构体时,可以使用out-prefix
避免命名冲突:
// client_options.go
//go:generate options-gen -from-struct=ClientOptions -out-prefix=Client -out-filename=client_options_generated.go
type ClientOptions struct {
timeout time.Duration `option:"mandatory"`
retries int `default:"3"`
}
// server_options.go
//go:generate options-gen -from-struct=ServerOptions -out-prefix=Server -out-filename=server_options_generated.go
type ServerOptions struct {
listenAddr string `option:"mandatory" validate:"required,hostname_port"`
maxConns int `default:"100"`
}
// 生成的函数将是:
// - NewClientOptions()和ClientOption
// - NewServerOptions()和ServerOption
验证和默认值
自定义验证器
import (
"github.com/go-playground/validator/v10"
)
type Options struct {
age int `validate:"adult"`
username string `validate:"required,alphanum"`
}
func (Options) Validator() *validator.Validate {
v := validator.New()
v.RegisterValidation("adult", func(fl validator.FieldLevel) bool {
return fl.Field().Int() >= 18
})
return v
}
默认值
options-gen提供几种定义默认值的方式:
- 使用tag
//go:generate options-gen -from-struct=Options
type Options struct {
pingPeriod time.Duration `default:"3s" validate:"min=100ms,max=30s"`
name string `default:"unknown" validate:"required"`
maxAttempts int `default:"10" validate:"min=1,max=10"`
eps float32 `default:"0.0001" validate:"gt=0"`
}
- 使用变量
//go:generate options-gen -from-struct=Options -defaults-from=var
type Options struct {
httpClient *http.Client
}
var defaultOptions = Options{
httpClient: &http.Client{},
}
- 使用函数
//go:generate options-gen -from-struct=Options -defaults-from=func
type Options struct {
httpClient *http.Client
}
func getDefaultOptions() Options {
return Options{
httpClient: &http.Client{},
}
}
- 禁用默认值
//go:generate options-gen -from-struct=Options -defaults-from=none
type Options struct {
name string `default:"joe"` // 不会生效
}
检查字段是否设置
//go:generate options-gen -from-struct=Options -with-isset
type Options struct {
name string
}
// 使用:
opts := NewOptions(WithName("alice"))
if opts.IsSet(optFieldName) {
// name被明确设置为"alice"
}
变长参数设置器
//go:generate options-gen -from-struct=Options
type Options struct {
// 默认:接受[]string
tags []string
// 变长:接受多个string参数
labels []string `option:"variadic=true"`
}
// 使用:
opts := NewOptions(
WithTags([]string{"api", "v2"}),
WithLabels("prod", "us-east-1", "critical"),
)
为外部包结构生成选项
虽然不能直接使用--from-struct=github.com/user/pkg.StructName
,但可以使用类型别名:
package myapp
import (
"github.com/gorilla/websocket"
"github.com/redis/go-redis/v9"
)
// 为外部结构创建类型别名
type WebsocketOptions = websocket.Dialer
//go:generate options-gen -from-struct=WebsocketOptions
// 这将为websocket.Dialer的所有导出字段生成设置器
// 也可以别名并扩展外部结构
type RedisOptions redis.Options
//go:generate options-gen -from-struct=RedisOptions
// 为redis.Options的所有字段生成设置器
// 泛型外部类型的示例
type CacheOptions[T any] = genericpkg.Cache[T]
//go:generate options-gen -from-struct=CacheOptions
贡献
开发过程非常简单:
- 在GitHub上fork仓库
- 克隆你的fork
- 为你的目标创建新分支
- 安装Task(类似Make但更简单)
- 运行
task check
检查工作副本是否准备就绪 - 实现你的目标!
- 再次运行
task check
确认一切正常 - 创建Pull Request
options-gen提供了强大的功能来简化Golang中的函数式选项模式实现,通过代码生成减少了模板代码的编写,同时保持了类型安全和良好的API设计。
更多关于golang实现函数式选项模式API设计的插件库options-gen的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang实现函数式选项模式API设计的插件库options-gen的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
使用options-gen实现Golang函数式选项模式
函数式选项模式(Functional Options Pattern)是Go中一种优雅的API设计方式,它允许创建可扩展的配置选项,同时保持代码的清晰和易用性。options-gen
是一个自动生成选项模式代码的工具,可以大大简化这一过程。
安装options-gen
首先安装options-gen
工具:
go install github.com/kazhuravlev/options-gen/cmd/options-gen@latest
基本使用示例
1. 定义配置结构
创建一个server.go
文件,定义你的配置结构:
//go:generate options-gen -out-filename=server_options.gen.go
package server
// Config 服务器配置
// @options-gen
type Config struct {
Addr string `default:":8080" validate:"required"`
Timeout time.Duration `default:"30s"`
MaxConns int `default:"100"`
Debug bool
}
2. 生成选项代码
运行生成命令:
go generate ./...
这会生成server_options.gen.go
文件,包含所有选项模式的代码。
3. 使用生成的选项
package main
import (
"time"
"your/package/server"
)
func main() {
// 使用默认配置
srv := server.NewServer()
// 使用自定义选项
srv := server.NewServer(
server.WithAddr(":9090"),
server.WithTimeout(60*time.Second),
server.WithMaxConns(200),
server.WithDebug(true),
)
}
高级特性
1. 必填字段验证
// @options-gen
type Config struct {
// @required
Addr string
Timeout time.Duration `default:"30s"`
}
2. 自定义选项名称
// @options-gen
type Config struct {
// @option-name listen_address
Addr string `default:":8080"`
}
生成的选项函数将是WithListenAddress
。
3. 嵌套结构
// @options-gen
type Config struct {
Addr string
Timeout time.Duration
// @options-gen
TLS struct {
CertFile string
KeyFile string
}
}
使用方式:
server.NewServer(
server.WithAddr(":443"),
server.WithTLSCertFile("cert.pem"),
server.WithTLSKeyFile("key.pem"),
)
4. 接口类型选项
// @options-gen
type Config struct {
// @no-option
Logger interface {
Log(msg string)
}
}
最佳实践
- 保持选项简单:每个选项应该只做一件事
- 提供合理的默认值:通过
default
标签设置常用默认值 - 验证关键参数:使用
validate
标签或自定义验证逻辑 - 文档化选项:为每个选项添加清晰的注释
- 考虑不可变性:选项配置后应该不可变
完整示例
db/config.go
:
//go:generate options-gen -out-filename=config_options.gen.go
package db
import "time"
// Config 数据库配置
// @options-gen
type Config struct {
// @required
DSN string
// @option-name max_open_conns
MaxOpenConns int `default:"10"`
MaxIdleConns int `default:"5"`
MaxLifetime time.Duration `default:"30m"`
// @no-option
metricsCollector interface {
Collect(query string, duration time.Duration)
}
}
生成后使用:
db.NewClient(
db.WithDSN("user:pass@tcp(localhost:3306)/db"),
db.WithMaxOpenConns(20),
db.WithMaxLifetime(time.Hour),
)
options-gen
通过自动化代码生成,显著减少了实现函数式选项模式所需的样板代码,同时保持了类型安全和良好的API设计。