Skip to content

第一个示例 ​

通过完整的示例学习 Wire 的使用流程。

项目结构 ​

目录结构 ​

myapp/
├── cmd/
│   └── server/
│       ├── main.go
│       └── wire.go          # Injector 定义
│       └── wire_gen.go      # 生成代码
├── internal/
│   ├── config/
│   │   └── config.go
│   ├── data/
│   │   └── database.go
│   │   └ repository.go
│   ├── biz/
│   │   └ service.go
│   └── server/
│       └── handler.go
│       └ server.go
├── go.mod
└── go.sum

定义组件 ​

Config ​

go
package config

type Config struct {
    Database DatabaseConfig
    Server   ServerConfig
}

type DatabaseConfig struct {
    Host     string
    Port     int
    User     string
    Password string
    Database string
}

type ServerConfig struct {
    Port int
}

func NewConfig() *Config {
    return &Config{
        Database: DatabaseConfig{
            Host:     "localhost",
            Port:     5432,
            User:     "user",
            Password: "password",
            Database: "mydb",
        },
        Server: ServerConfig{
            Port: 8080,
        },
    }
}

Database ​

go
package data

import (
    "database/sql"
    "fmt"
    "myapp/internal/config"
)

type Database struct {
    DB *sql.DB
}

func NewDatabase(cfg *config.Config) (*Database, func(), error) {
    dsn := fmt.Sprintf(
        "host=%s port=%d user=%s password=%s dbname=%s",
        cfg.Database.Host,
        cfg.Database.Port,
        cfg.Database.User,
        cfg.Database.Password,
        cfg.Database.Database,
    )
    
    db, err := sql.Open("postgres", dsn)
    if err != nil {
        return nil, nil, err
    }
    
    if err := db.Ping(); err != nil {
        return nil, nil, err
    }
    
    cleanup := func() {
        db.Close()
    }
    
    return &Database{DB: db}, cleanup, nil
}

Repository ​

go
package data

type UserRepository struct {
    db *Database
}

func NewUserRepository(db *Database) *UserRepository {
    return &UserRepository{db: db}
}

func (r *UserRepository) FindByID(id int64) (*User, error) {
    query := "SELECT id, name, email FROM users WHERE id = $1"
    row := r.db.DB.QueryRow(query, id)
    
    user := &User{}
    err := row.Scan(&user.ID, &user.Name, &user.Email)
    if err != nil {
        return nil, err
    }
    
    return user, nil
}

type User struct {
    ID    int64
    Name  string
    Email string
}

Service ​

go
package biz

import (
    "context"
    "errors"
    "myapp/internal/data"
)

var ErrUserNotFound = errors.New("用户不存在")

type UserService struct {
    repo *data.UserRepository
}

func NewUserService(repo *data.UserRepository) *UserService {
    return &UserService{repo: repo}
}

func (s *UserService) GetUser(ctx context.Context, id int64) (*data.User, error) {
    user, err := s.repo.FindByID(id)
    if err != nil {
        return nil, ErrUserNotFound
    }
    return user, nil
}

Handler ​

go
package server

import (
    "encoding/json"
    "net/http"
    "strconv"
    
    "myapp/internal/biz"
    "myapp/internal/config"
)

type UserHandler struct {
    service *biz.UserService
    cfg     *config.Config
}

func NewUserHandler(service *biz.UserService, cfg *config.Config) *UserHandler {
    return &UserHandler{
        service: service,
        cfg:     cfg,
    }
}

func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
    idStr := r.URL.Query().Get("id")
    id, err := strconv.ParseInt(idStr, 10, 64)
    if err != nil {
        http.Error(w, "invalid id", http.StatusBadRequest)
        return
    }
    
    user, err := h.service.GetUser(r.Context(), id)
    if err != nil {
        http.Error(w, err.Error(), http.StatusNotFound)
        return
    }
    
    json.NewEncoder(w).Encode(user)
}

Server ​

go
package server

import (
    "net/http"
    "myapp/internal/config"
)

type Server struct {
    httpServer *http.Server
    handler    *UserHandler
}

func NewServer(cfg *config.Config, handler *UserHandler) *Server {
    mux := http.NewServeMux()
    mux.HandleFunc("/user", handler.GetUser)
    
    httpServer := &http.Server{
        Addr:    ":" + strconv.Itoa(cfg.Server.Port),
        Handler: mux,
    }
    
    return &Server{
        httpServer: httpServer,
        handler:    handler,
    }
}

func (s *Server) Start() error {
    return s.httpServer.ListenAndServe()
}

func (s *Server) Stop() error {
    return s.httpServer.Shutdown(context.Background())
}

定义 Provider Set ​

按层组织 ​

go
package data

import "github.com/google/wire"

var DataSet = wire.NewSet(
    NewDatabase,
    NewUserRepository,
)
go
package biz

import "github.com/google/wire"

var BizSet = wire.NewSet(
    NewUserService,
)
go
package server

import "github.com/google/wire"

var ServerSet = wire.NewSet(
    NewUserHandler,
    NewServer,
)

定义 Injector ​

wire.go ​

go
//+build wireinject

package main

import (
    "github.com/google/wire"
    "myapp/internal/biz"
    "myapp/internal/config"
    "myapp/internal/data"
    "myapp/internal/server"
)

func InitializeServer() (*server.Server, func(), error) {
    wire.Build(
        config.NewConfig,
        data.DataSet,
        biz.BizSet,
        server.ServerSet,
    )
    return nil, nil, nil
}

生成代码 ​

运行 wire ​

bash
cd cmd/server
wire

生成的 wire_gen.go ​

go
// Code generated by Wire. DO NOT EDIT.

//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
//+build !wireinject

package main

import (
    "myapp/internal/biz"
    "myapp/internal/config"
    "myapp/internal/data"
    "myapp/internal/server"
)

func InitializeServer() (*server.Server, func(), error) {
    config := config.NewConfig()
    database, cleanup, err := data.NewDatabase(config)
    if err != nil {
        return nil, nil, err
    }
    userRepository := data.NewUserRepository(database)
    userService := biz.NewUserService(userRepository)
    userHandler := server.NewUserHandler(userService, config)
    server := server.NewServer(config, userHandler)
    return server, func() {
        cleanup()
    }, nil
}

使用生成的代码 ​

main.go ​

go
package main

import (
    "log"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    server, cleanup, err := InitializeServer()
    if err != nil {
        log.Fatal(err)
    }
    defer cleanup()
    
    // 启动服务器
    go func() {
        if err := server.Start(); err != nil {
            log.Printf("server error: %v", err)
        }
    }()
    
    // 等待中断信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    
    // 停止服务器
    if err := server.Stop(); err != nil {
        log.Printf("server shutdown error: %v", err)
    }
}

运行项目 ​

编译运行 ​

bash
go build -o server ./cmd/server
./server

测试 ​

bash
curl http://localhost:8080/user?id=1

依赖图可视化 ​

使用 wire check ​

bash
wire check ./cmd/server

输出:

config.Config
data.Database
data.UserRepository
biz.UserService
server.UserHandler
server.Server

添加新组件 ​

添加新服务 ​

go
package biz

type OrderService struct {
    repo *data.OrderRepository
}

func NewOrderService(repo *data.OrderRepository) *OrderService {
    return &OrderService{repo: repo}
}

var BizSet = wire.NewSet(
    NewUserService,
    NewOrderService,  // 新增
)

更新 Injector ​

go
func InitializeServer() (*server.Server, func(), error) {
    wire.Build(
        config.NewConfig,
        data.DataSet,
        biz.BizSet,
        server.ServerSet,
    )
    return nil, nil, nil
}

重新生成 ​

bash
wire ./cmd/server

错误处理示例 ​

Provider 返回错误 ​

go
func NewDatabase(cfg *config.Config) (*Database, func(), error) {
    if cfg.Database.Host == "" {
        return nil, nil, errors.New("database host is required")
    }
    // ...
}

Injector 处理错误 ​

go
func main() {
    server, cleanup, err := InitializeServer()
    if err != nil {
        log.Fatalf("初始化失败: %v", err)
    }
    defer cleanup()
    // ...
}

完整流程总结 ​

  1. 定义组件:编写业务组件和 Provider 函数
  2. 组织 Provider Set:按层分组组织 Provider
  3. 定义 Injector:使用 wire.Build 声明依赖
  4. 生成代码:运行 wire 生成 wire_gen.go
  5. 使用生成代码:在 main.go 中调用 Injector
  6. 测试运行:编译并运行应用

参考:https://github.com/google/wire/tree/main/_tutorial