项目初始化
1. 创建项目结构
推荐的标准项目结构:
myproject/
├── cmd/ # 主应用程序目录
│ └── appname/ # 可执行程序
│ └── main.go
├── internal/ # 私有应用程序代码
│ ├── config/ # 配置相关
│ ├── controller/ # 控制器
│ ├── service/ # 业务逻辑
│ └── dao/ # 数据访问
├── pkg/ # 可被外部导入的库代码
├── api/ # API定义文件(Swagger, protobuf等)
├── configs/ # 配置文件模板或默认配置
├── scripts/ # 脚本
├── build/ # 打包和持续集成
├── deployments/ # 部署相关
├── test/ # 测试文件
├── go.mod # Go模块定义
└── go.sum # 依赖校验
2. 初始化Go模块
go mod init github.com/yourname/projectname
组件编排
1. 使用依赖注入
推荐使用wire或dig等依赖注入框架:
// 使用wire示例
//+build wireinject
package main
import (
"github.com/google/wire"
"myproject/internal/config"
"myproject/internal/server"
"myproject/internal/service"
)
func InitApp() (*App, error) {
wire.Build(
config.ProviderSet,
service.ProviderSet,
server.ProviderSet,
NewApp,
)
return &App{}, nil
}
2. 组件生命周期管理
type Component interface {
Start() error
Stop() error
Healthy() bool
}
type App struct {
components []Component
}
func (a *App) Register(c Component) {
a.components = append(a.components, c)
}
func (a *App) Start() error {
for _, c := range a.components {
if err := c.Start(); err != nil {
return fmt.Errorf("component start failed: %w", err)
}
}
return nil
}
优雅退出
1. 信号处理
func (a *App) Run() error {
// 启动所有组件
if err := a.Start(); err != nil {
return err
}
// 创建信号通道
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// 等待信号
sig := <-sigChan
log.Printf("Received signal: %v, shutting down...", sig)
// 优雅关闭
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return a.Shutdown(ctx)
}
func (a *App) Shutdown(ctx context.Context) error {
var errs []error
// 逆序关闭组件
for i := len(a.components) - 1; i >= 0; i-- {
c := a.components[i]
if err := c.Stop(); err != nil {
errs = append(errs, fmt.Errorf("component stop failed: %w", err))
}
}
if len(errs) > 0 {
return fmt.Errorf("shutdown errors: %v", errs)
}
return nil
}
2. HTTP服务器优雅退出
func (s *HTTPServer) Start() error {
go func() {
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("HTTP server error: %v", err)
}
}()
return nil
}
func (s *HTTPServer) Stop() error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
return s.server.Shutdown(ctx)
}
完整示例
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
type Component interface {
Start() error
Stop() error
}
type HTTPServer struct {
server *http.Server
}
func NewHTTPServer() *HTTPServer {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, World!")
})
return &HTTPServer{
server: &http.Server{
Addr: ":8080",
Handler: mux,
},
}
}
func (s *HTTPServer) Start() error {
go func() {
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("HTTP server error: %v", err)
}
}()
log.Println("HTTP server started")
return nil
}
func (s *HTTPServer) Stop() error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
log.Println("Shutting down HTTP server...")
return s.server.Shutdown(ctx)
}
type App struct {
components []Component
}
func NewApp() *App {
return &App{}
}
func (a *App) Register(c Component) {
a.components = append(a.components, c)
}
func (a *App) Start() error {
for _, c := range a.components {
if err := c.Start(); err != nil {
return fmt.Errorf("component start failed: %w", err)
}
}
return nil
}
func (a *App) Shutdown(ctx context.Context) error {
var errs []error
for i := len(a.components) - 1; i >= 0; i-- {
c := a.components[i]
if err := c.Stop(); err != nil {
errs = append(errs, fmt.Errorf("component stop failed: %w", err))
}
}
if len(errs) > 0 {
return fmt.Errorf("shutdown errors: %v", errs)
}
return nil
}
func (a *App) Run() error {
if err := a.Start(); err != nil {
return err
}
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigChan
log.Printf("Received signal: %v, shutting down...", sig)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return a.Shutdown(ctx)
}
func main() {
app := NewApp()
app.Register(NewHTTPServer())
if err := app.Run(); err != nil {
log.Fatalf("Application error: %v", err)
}
log.Println("Application stopped gracefully")
}
最佳实践
-
组件设计:
- 每个组件应实现简单的Start/Stop接口
- 组件之间尽量减少直接依赖
-
错误处理:
- 启动时遇到错误应立即退出
- 关闭时尽量处理所有组件,记录错误但不立即退出
-
超时控制:
- 为关闭操作设置合理的超时时间
- 超时后强制退出
-
日志记录:
- 记录组件启动/关闭的关键事件
- 记录信号接收和超时事件
-
测试:
- 测试正常启动和关闭流程
- 测试信号处理
- 测试超时场景
通过这种方式,你可以构建一个结构清晰、易于维护且能够优雅处理退出的Go应用程序。