这里讨论的是不用框架只用工具库( Toolkit )的情况,否则组织形式只能按照框架的规定,也就没什么讨论空间了。
其实同样的思路还可以用来组织具体的业务代码,整合第三方服务等不少场景。 这里先讲个大概,等过年回家之后再整理篇完整的博客。
入口部分主要是声明退出方式,然后开始初始化流程。
// cmd/app/main.go
package main
func main() {
// 在第一次收到 SIG_INT ,SIG_TERM 时结束 Context ,
// 第二次收到时 os.Exit()
ctx, cancel := module.GracefulContext()
defer cancel()
// 执行业务逻辑
if err := business.Run(ctx); err != nil {
log.Fatalf("Error exit: %s", err)
}
}
组装第一层程序结构,主要是:命令行,外部依赖,端口监听。 这里以常见的 HTTP 服务器为例。
// business/app.go
package business
type App struct {
module.CLI // 命令行
module.SQLite // 数据库
module.HTTP // HTTP 服务
}
func Run(ctx context.Context) (err error) {
var app App
return module.Run(ctx, &app)
}
module.Run()
会依次初始化 App 中声明的每个 module ,然后运行所有实现了 Run()
的 module 。
初始化的大致流程为:module1.PreInit() -> module1.Init() -> module1.PostInit() -> module2.PreInit() -> ...
至于模块如何声明,就以 module.SQLite
为例:其中 Init()
和 Close()
都是 module 的可选接口。
module.HTTP
还需要实现 Run()
。
// internal/module/sqlite.go
package module
type SQLite struct {
DSN string
DB *sql.DB
}
func (m *SQLite) Init(ctx context.Context) (err error) {
if m.DB, err = sql.Open("sqlite3", m.DSN); err != nil {
return
}
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
return m.DB.PingContext(ctx)
}
func (m *SQLite) Close() error {
return m.DB.Close()
}
module.HTTP
和 module.CLI
代码比较长就不贴了,完整的 Demo 在 Github: https://github.com/GotaX/module-demo
有疑问或者想分享下自己是如何组织代码的也欢迎留言讨论。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.