[teamai] Push 87 resource(s) from XingfenD
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
# Recipes — google/wire
|
||||
|
||||
End-to-end examples. Each recipe is self-contained.
|
||||
|
||||
## HTTP Server with Postgres and Redis
|
||||
|
||||
A typical service: parsed config → DB (with cleanup) → Redis (with cleanup) → repo → service → HTTP server.
|
||||
|
||||
```
|
||||
myapp/
|
||||
├── config/
|
||||
│ ├── config.go
|
||||
│ └── wire.go
|
||||
├── infra/
|
||||
│ ├── db.go
|
||||
│ ├── cache.go
|
||||
│ └── wire.go
|
||||
├── repo/
|
||||
│ ├── user.go
|
||||
│ └── wire.go
|
||||
├── service/
|
||||
│ ├── user.go
|
||||
│ └── wire.go
|
||||
├── transport/
|
||||
│ ├── handler.go
|
||||
│ └── wire.go
|
||||
├── wire.go // injector — //go:build wireinject
|
||||
├── wire_gen.go // generated — commit this
|
||||
└── main.go
|
||||
```
|
||||
|
||||
```go
|
||||
// config/config.go
|
||||
type Config struct {
|
||||
Addr string
|
||||
DSN string
|
||||
CacheAddr string
|
||||
}
|
||||
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
Addr: env("ADDR", ":8080"),
|
||||
DSN: mustEnv("DATABASE_URL"),
|
||||
CacheAddr: env("REDIS_ADDR", "localhost:6379"),
|
||||
}
|
||||
}
|
||||
|
||||
// config/wire.go
|
||||
var ConfigSet = wire.NewSet(NewConfig)
|
||||
```
|
||||
|
||||
```go
|
||||
// infra/db.go
|
||||
func NewDB(cfg *config.Config) (*sql.DB, func(), error) {
|
||||
db, err := sql.Open("postgres", cfg.DSN)
|
||||
if err != nil { return nil, nil, err }
|
||||
if err := db.Ping(); err != nil { db.Close(); return nil, nil, err }
|
||||
return db, func() { db.Close() }, nil
|
||||
}
|
||||
|
||||
// infra/cache.go
|
||||
func NewRedis(cfg *config.Config) (*redis.Client, func(), error) {
|
||||
c := redis.NewClient(&redis.Options{Addr: cfg.CacheAddr})
|
||||
if err := c.Ping(context.Background()).Err(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return c, func() { c.Close() }, nil
|
||||
}
|
||||
|
||||
// infra/wire.go
|
||||
var InfraSet = wire.NewSet(NewDB, NewRedis)
|
||||
```
|
||||
|
||||
```go
|
||||
// repo/user.go
|
||||
type UserStore interface {
|
||||
GetUser(ctx context.Context, id int64) (*User, error)
|
||||
}
|
||||
|
||||
type PostgresUserRepo struct{ db *sql.DB }
|
||||
|
||||
func NewUserRepo(db *sql.DB) *PostgresUserRepo { return &PostgresUserRepo{db: db} }
|
||||
|
||||
// repo/wire.go
|
||||
var RepoSet = wire.NewSet(
|
||||
NewUserRepo,
|
||||
wire.Bind(new(UserStore), new(*PostgresUserRepo)),
|
||||
)
|
||||
```
|
||||
|
||||
```go
|
||||
// service/user.go
|
||||
type UserService struct {
|
||||
store repo.UserStore
|
||||
cache *redis.Client
|
||||
}
|
||||
|
||||
func NewUserService(store repo.UserStore, cache *redis.Client) *UserService {
|
||||
return &UserService{store: store, cache: cache}
|
||||
}
|
||||
|
||||
// service/wire.go
|
||||
var ServiceSet = wire.NewSet(NewUserService)
|
||||
```
|
||||
|
||||
```go
|
||||
// wire.go
|
||||
//go:build wireinject
|
||||
|
||||
package main
|
||||
|
||||
func InitApp() (*transport.Handler, func(), error) {
|
||||
wire.Build(
|
||||
config.ConfigSet,
|
||||
infra.InfraSet,
|
||||
repo.RepoSet,
|
||||
service.ServiceSet,
|
||||
transport.NewHandler,
|
||||
)
|
||||
return nil, nil, nil
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// main.go
|
||||
func main() {
|
||||
handler, cleanup, err := InitApp()
|
||||
if err != nil { log.Fatal(err) }
|
||||
defer cleanup()
|
||||
|
||||
srv := &http.Server{Addr: ":8080", Handler: handler}
|
||||
log.Fatal(srv.ListenAndServe())
|
||||
}
|
||||
```
|
||||
|
||||
## Multiple Build Variants (Prod vs Dev)
|
||||
|
||||
Use separate injector files with `//go:build` constraints to select different provider sets at build time.
|
||||
|
||||
```go
|
||||
// wire_prod.go
|
||||
//go:build wireinject && !dev
|
||||
|
||||
package main
|
||||
|
||||
func InitApp() (*App, func(), error) {
|
||||
wire.Build(ProdSet, NewApp)
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// wire_dev.go
|
||||
//go:build wireinject && dev
|
||||
|
||||
package main
|
||||
|
||||
func InitApp() (*App, func(), error) {
|
||||
wire.Build(DevSet, NewApp) // DevSet swaps real DB for in-memory SQLite
|
||||
return nil, nil, nil
|
||||
}
|
||||
```
|
||||
|
||||
Use `-output_file_prefix` to write separate output files — both commands would otherwise overwrite the same `wire_gen.go`:
|
||||
|
||||
```bash
|
||||
wire -tags dev -output_file_prefix=wire_gen_dev gen .
|
||||
wire -output_file_prefix=wire_gen_prod gen .
|
||||
```
|
||||
|
||||
Add build constraints to the generated files so only one compiles per build:
|
||||
|
||||
```go
|
||||
// wire_gen_prod.go — add at the top (after wire writes it)
|
||||
//go:build !dev
|
||||
|
||||
// wire_gen_dev.go — add at the top
|
||||
//go:build dev
|
||||
```
|
||||
|
||||
Commit both generated files. At build time, only the matching file is compiled.
|
||||
|
||||
## Cleanup-Heavy Graph
|
||||
|
||||
When several providers need shutdown coordination, wire's reverse-order cleanup is essential.
|
||||
|
||||
```go
|
||||
// Providers return (T, func(), error)
|
||||
func NewDBPool(cfg *Config) (*pgxpool.Pool, func(), error) {
|
||||
pool, err := pgxpool.New(context.Background(), cfg.DSN)
|
||||
if err != nil { return nil, nil, err }
|
||||
return pool, func() { pool.Close() }, nil
|
||||
}
|
||||
|
||||
func NewOTelExporter(cfg *Config) (*otlptrace.Exporter, func(), error) {
|
||||
exp, err := otlptracegrpc.New(context.Background(), ...)
|
||||
if err != nil { return nil, nil, err }
|
||||
return exp, func() { exp.Shutdown(context.Background()) }, nil
|
||||
}
|
||||
|
||||
func NewTracerProvider(exp *otlptrace.Exporter) (*trace.TracerProvider, func(), error) {
|
||||
tp := trace.NewTracerProvider(trace.WithBatcher(exp))
|
||||
return tp, func() { tp.Shutdown(context.Background()) }, nil
|
||||
}
|
||||
```
|
||||
|
||||
Wire generates shutdown in reverse: `TracerProvider` → `OTelExporter` → `DBPool`. Each cleanup runs before its dependencies shut down — guaranteeing in-flight spans are flushed before the exporter closes.
|
||||
|
||||
## Embedding Wire in a CLI
|
||||
|
||||
Wire produces a struct, not an app framework. You control the lifecycle:
|
||||
|
||||
```go
|
||||
// wire.go
|
||||
//go:build wireinject
|
||||
|
||||
package cmd
|
||||
|
||||
func InitServer(cfg *Config) (*http.Server, func(), error) {
|
||||
wire.Build(InfraSet, ServiceSet, NewHTTPServer)
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// cmd/serve.go
|
||||
func runServe(cfg *Config) error {
|
||||
srv, cleanup, err := InitServer(cfg)
|
||||
if err != nil { return err }
|
||||
defer cleanup()
|
||||
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
<-quit
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
return srv.Shutdown(ctx)
|
||||
}
|
||||
```
|
||||
|
||||
Unlike `fx.Run()`, wire does not manage the lifecycle loop. Implement signal handling and graceful shutdown explicitly. This is a feature for CLI tools that spin up short-lived services and need precise control over the shutdown sequence.
|
||||
|
||||
## Passing External Values to Wire
|
||||
|
||||
Values built before wire runs (parsed config, test doubles) become injector parameters:
|
||||
|
||||
```go
|
||||
//go:build wireinject
|
||||
|
||||
// cfg is resolved outside the graph — treated as a provided *Config
|
||||
func InitApp(cfg *Config) (*App, func(), error) {
|
||||
wire.Build(InfraSet, ServiceSet, NewApp)
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// main.go
|
||||
cfg, err := config.Load()
|
||||
if err != nil { log.Fatal(err) }
|
||||
app, cleanup, err := InitApp(cfg)
|
||||
```
|
||||
|
||||
The parameter `cfg *Config` satisfies any downstream provider that requests `*Config` — no `wire.Value` or extra set entry needed.
|
||||
Reference in New Issue
Block a user