Wails2 系列 02:应用入口:让 main.go 只负责装配
cbowen

适合读者

适合已经在 Wails2 中绑定过 Go 方法,但不确定入口文件应该放多少逻辑的开发者。

问题场景

Wails2 的入口文件很容易变胖。因为所有关键能力看起来都从这里开始:窗口标题、尺寸、前端资源、生命周期、服务绑定、平台选项、启动参数、通知初始化、数据连接。

如果不设边界,main.go 会慢慢塞进业务判断:用户是否登录、设置怎么保存、数据怎么迁移、按钮点击后做什么、某个平台特殊处理什么。短期看很快,长期看每次改功能都要碰入口,风险也越来越高。

更合理的做法是把 main.go 当成 composition root,也就是应用装配根。

项目观察

观察当前项目结构时,可以把 main.go 理解为几个动作的集合:

  • 加载配置和内嵌前端资源。
  • 创建存储对象和各个后端服务。
  • OnStartup 中把 Wails context 交给服务。
  • Bind 中把服务暴露给前端。
  • OnShutdown 中做资源关闭。

这些动作都属于“把应用组装起来”,而不是具体业务。

拆分原则

入口文件可以知道模块列表,但不要知道模块内部细节。

入口文件适合做这些事:

  • 创建依赖,例如配置、存储、服务实例。
  • 连接依赖,例如把 storage 传给 service。
  • 配置 Wails 应用,例如窗口、资源、生命周期和绑定。
  • 做全局启动和关闭流程。

入口文件不适合做这些事:

  • 写具体业务规则。
  • 直接读写业务数据。
  • 直接处理复杂平台差异。
  • 承载前端某个页面的流程逻辑。

改造示例

一个容易失控的入口大概会这样:

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
app := NewApp()

// 创建窗口
// 读取配置
// 打开数据库
// 判断用户状态
// 处理待办逻辑
// 处理更新逻辑
// 处理托盘逻辑
// 绑定几十个方法
}

更清楚的入口应该只保留装配关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
cfg := config.Load()
store := storage.Open(cfg.DataDir)

appService := appservice.New(cfg)
todoService := todoservice.New(store)
settingsService := settingsservice.New(cfg)

runWails(AppOptions{
Services: []any{
appService,
todoService,
settingsService,
},
})
}

如果服务需要 Wails context,可以给每个服务提供统一的 Init 方法:

1
2
3
type Service interface {
Init(ctx context.Context)
}

启动时做一件事:把 context 分发下去。

1
2
3
4
5
func onStartup(ctx context.Context, services []Service) {
for _, service := range services {
service.Init(ctx)
}
}

这样入口仍然知道有哪些服务,但业务细节被留在各自服务内部。

检查清单

  • main.go 中是否还能一眼看出应用由哪些服务组成?
  • main.go 是否避免出现具体业务规则?
  • 服务依赖是否通过构造函数传入?
  • Wails context 是否只在启动后注入到需要它的对象?
  • Bind 中绑定的是业务域服务,而不是一堆零散函数?
  • 关闭流程是否集中处理资源释放?

下一篇

下一篇会继续看后端服务层:当多个能力都要暴露给前端时,应该如何按业务域拆分 Wails 绑定服务。

 评论
评论插件加载失败
正在加载评论插件