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提供几种定义默认值的方式:

  1. 使用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"`
}
  1. 使用变量
//go:generate options-gen -from-struct=Options -defaults-from=var
type Options struct {
	httpClient *http.Client
}

var defaultOptions = Options{
	httpClient: &http.Client{},
}
  1. 使用函数
//go:generate options-gen -from-struct=Options -defaults-from=func
type Options struct {
	httpClient *http.Client
}

func getDefaultOptions() Options {
	return Options{
		httpClient: &http.Client{},
	}
}
  1. 禁用默认值
//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

贡献

开发过程非常简单:

  1. 在GitHub上fork仓库
  2. 克隆你的fork
  3. 为你的目标创建新分支
  4. 安装Task(类似Make但更简单)
  5. 运行task check检查工作副本是否准备就绪
  6. 实现你的目标!
  7. 再次运行task check确认一切正常
  8. 创建Pull Request

options-gen提供了强大的功能来简化Golang中的函数式选项模式实现,通过代码生成减少了模板代码的编写,同时保持了类型安全和良好的API设计。


更多关于golang实现函数式选项模式API设计的插件库options-gen的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于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)
    }
}

最佳实践

  1. 保持选项简单:每个选项应该只做一件事
  2. 提供合理的默认值:通过default标签设置常用默认值
  3. 验证关键参数:使用validate标签或自定义验证逻辑
  4. 文档化选项:为每个选项添加清晰的注释
  5. 考虑不可变性:选项配置后应该不可变

完整示例

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设计。

回到顶部