Go 工程化标准实践

Go 工程化标准实践

本文参考 Go 微服务框架 go-kratos/kratos的项目结构及相关最佳实践

标准项目结构

/cmd

|-- cmd
    |-- demo
        |-- demo
        +-- main.go
    +-- demo1
        |-- demo1
        +-- main.go

项目的主干,每个应用程序目录名与可执行文件的名称匹配。该目录不应放置太多代码。

/internal

|-- internal
    +-- demo
        |-- biz
        |-- service
        +-- data

私有应用程序和库代码。该目录由 Go 编译器强制执行(更多细节请参阅 Go 1.4 release notes),在项目树的任何级别上都可以有多个 /internal 目录。

可在 /internal 包中添加额外结构,以分隔共享和非共享的内部代码。对于较小的项目而言不是必需,但最好有可视化线索显示预期的包的用途。

实际应用程序代码可放在 /internal/app 目录下(比如 /internal/app/myapp),应用程序共享代码可放在 /internal/pkg 目录下(比如 /internal/pkg/myprivlib)。

相关服务(比如账号服务内部有 rpc、job、admin 等)整合一起后需要区分 app。单一服务则可以去掉 /internal/myapp。

/pkg

|-- pkg
    |-- memcache
    +-- redis
|-- conf
    |-- dsn
    |-- env
    |-- flagvar
    +-- paladin
.
|-- docs
|-- example
|-- misc
|-- pkg
|-- third_party
|-- tool

外部应用程序可以使用的库代码。可以显式地表示该目录代码对于其他人而言是安全可用的。

/pkg 目录内可参考 Go 标准库的组织方式,按照功能分类。/internal/pkg 一般用于项目内的跨应用公共共享代码,但其作用域仅在单个项目工程内。

当根目录包含大量非 Go 组件和目录时,这也是一种将 Go 代码分组到一个位置的方法,使得运行各种 Go 工具更容易组织。

工具包项目结构

|-- cache
    |-- memcache
    |   +-- test
    +-- redis
        +-- test
|-- conf
    |-- dsn
    |-- env
    |-- flagvar
    +-- paladin
        +-- apollo
            +-- internal
                +-- mockserver
|-- container
    |-- group
    |-- pool
    +-- queue
        +-- apm
|-- database
    |-- hbase
    |-- sql
    +-- tidb
|-- ecode
    +-- types
|-- log
    +-- internal
        |-- core
        +-- filewriter

应当为不同的微服务建立统一的 kit 工具包项目(基础库/框架)和 app 项目。

基础库 kit 为独立项目,公司级建议只有一个。由于按照功能目录来拆分会带来不少的管理工作,建议合并整合。

其具备以下特点:

  • 统一
  • 标准库方式布局
  • 高度抽象
  • 支持插件

服务应用项目结构

.
|-- README.md
|-- api
|-- cmd
|-- configs
|-- go.mod
|-- go.sum
|-- internal
+-- test

/api

API 协议定义目录,比如 protobuf 文件和生成的 go 文件。

通常把 API 文档直接在 proto 文件中描述。

/configs

配置文件模板或默认配置。

/test

外部测试应用程序和测试数据。可随时根据需求构造 /test 目录。

对于较大的项目数据子目录是很有意义的。比如可使用 /test/data 或 /test/testdata(如果需要忽略目录中的内容)。

Go 会忽略以“.”或“_”开头的目录或文件,因此在命名测试数据目录方面有更大灵活性。

GitLab Project

|-- app
    |-- replay
    |--..
    +-- member
|-- pkg
    |-- database
    |-- ..
    +-- log
+-- ...

一个 GitLab project 中可以放置多个微服务 app(类似 monorepo),也可以按照 GitLab 的 group 里建立多个 project,每个 project 对应一个 app。

微服务结构

|-- cmd                     负责程序的:启动、关闭、配置初始化等。
    |-- myapp1-admin        面向运营侧的服务,通常数据权限更高,隔离实现更好的代码级别安全。
    |-- myapp1-interface    对外的 BFF 服务,接受来自用户的请求(HTTP、gRPC)。
    |-- myapp1-job          流式任务服务,上游一般依赖 message broker。
    |-- myapp1-service      对内的微服务,仅接受来自内部其他服务或网关的请求(gRPC)。
    +-- myapp1-task         定时任务服务,类似 cronjob,部署到 task 托管平台中。

以下这种目录结构风格:

|-- service
    |-- api             API 定义(protobuf 等)以及对应生成的 client 代码,基于 pb 生成的 swagger.json。
    |-- cmd
    |-- configs         服务配置文件,比如 database.yaml、redis.yaml、application.yaml。
    |-- internal        避免有同业务下被跨目录引用了内部的 model、dao 等内部 struct。
        |-- model       对应“存储层”的结构体,是对存储的一一映射。
        |-- dao         数据读写层,统一处理数据库和缓存(cache miss 等问题)。
        |-- service     组合各种数据访问来构建业务逻辑,包括 api 中生成的接口实现。
        |-- server      依赖 proto 定义的服务作为入参,提供快捷的启动服务全局方法。
|-- ...

app 目录下有 api、cmd、configs、internal 目录。一般还会放置 README、CHANGELOG、OWNERS。

项目的依赖路径为:model -> dao -> service -> api,model struct 串联各个层,直到 api 做 DTO 对象转换。

另一种结构风格是将 DDD 设计思想和工程结构做了简化,映射到 api、service、biz、data 各层。

.
|-- CHANGELOG
|-- OWNERS
|-- README
|-- api
|-- cmd
    |-- myapp1-admin
    |-- myapp1-interface
    |-- myapp1-job
    |-- myapp1-service
    +-- myapp1-task
|-- configs
|-- go.mod
|-- internal        避免有同业务下被跨目录引用了内部的 model、dao 等内部 struct。
    |-- biz         业务逻辑组装层,类似 DDD domain(repo 接口再次定义,依赖倒置)。
    |-- data        业务数据访问,包含 cache、db 等封装,实现 biz 的 repo 接口。
    |-- pkg
    +-- service     实现了 api 定义的服务层,类似 DDD application
    处理 DTO 到 biz 领域实体的转换(DTO->DO),同时协同各类 biz 交互,不处理复杂逻辑。

在这里插入图片描述

在这里插入图片描述

架构与数据模型

松散分层架构(Relaxed Layered System):层间关系不太严格,每层都可能使用它下面所有层的服务(而不仅是下一层)。每层都可能是半透明的,意味着有些服务只对上一层可见,而有些服务对上面的所有层都可见。

[       api         ]
    |     |       |
    | [ service ] |
    |    |        |
  [     biz     ] |
         |        |
      [    data     ]

继承分层架构(Layering Through Inheritance):高层继承并实现低层接口。需要调整各层顺序,将基础设施层移动到最高层。这依然是单向依赖,意味着领域层、应用层、表现层将不能依赖基础设施层,而基础设施层可以依赖它们。

[       data        ]
    |    |        |
    | [ api ]     |
    |    |        |
  [   service   ] |
         |        |
      [     biz     ]

数据模型:

  • 失血模型:仅包含数据定义和 getter/setter 方法,业务逻辑和应用逻辑都放到服务层中。在 Java 中称为 POJO,在 .NET 中称为 POCO。
  • 贫血模型:包含一些业务逻辑,但不包含依赖持久层的业务逻辑(会放在服务层中),领域对象不依赖于持久层。
  • 充血模型:包含所有业务逻辑,领域层依赖于持久层,简单表示就是:UI 层 -> 服务层 -> 领域层 <-> 持久层。
  • 胀血模型:和业务逻辑不想关的其他应用逻辑(如授权、事务等)放到领域模型中(反而是另外一种失血模型,服务层缺失、由领域层代劳)

生命周期

考虑服务应用对象初始化和生命周期管理,所有 HTTP/gRPC 依赖的前置资源初始化(包括 data、biz、service),之后再启动监听服务。

资源初始化和关闭步骤繁琐,比较容易出错。可利用依赖注入的思路,使用 google/wire 管理资源依赖注入,方便测试和实现单次初始化与复用。

svr := http.NewServer()
app := kratos.New()
app.Append(kratos.Hook{
    OnStart: func(ctx context.Context) error {
        return svr.Start()
    },
    OnStop: func(ctx context.Context) error {
        return svr.Shutdown(ctx)
    },
})
if err := app.Run(); err != nil {
    log.Printf("app failed: %v\n", err)
    return
}

另外还支持静态生成代码,便于诊断(而不是在运行时通过 reflection 实现)。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值