前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go 进阶训练营 – Go 工程化实践一:工程项目结构

Go 进阶训练营 – Go 工程化实践一:工程项目结构

作者头像
Yuyy
发布2022-09-13 09:09:12
1.5K0
发布2022-09-13 09:09:12
举报

工程项目结构

序言

go工程的项目结构多种多样,不同的框架对应不同的结构,即使是同一个框架,也有可能出现不同的结构。我个人感觉目前go很年轻,处于百花齐放的时代。而不像java,早已形成业界标准,主流框架spring全家桶,你想要的它都有。基本不用纠结项目结构,只需照着来就行。这对刚从java转到go的同学造成很大的困扰,不知道怎么写符合业界标注,找开源项目参考,github一搜,发现大家各不相同,难啊!

最终,我们团队内部形成共识,在B站毛剑老师推荐的项目布局的基础上进行修改,使其更适合我们当前的业务。最近正好要给团队分享B站微服务框架kratos的使用,借此机会增加go项目的共识。

Standard Go Project Layout

首先出场的是 github 高星项目:golang-standards/project-layout ,了解go项目里的目录含义,go sdk也是符合其定义的。

/cmd

/internal

  • 私有代码,不希望被依赖者引入。与之对应的是pkg目录。
  • 项目内应用之间公共部分放在/internal/pkg
  • 编译器支持,编译时会禁止此目录下的文件被外部依赖。并且不局限于顶级目录,在任何目录当中都是生效的。

/pkg

  • 显式的告诉外部应用程序可以使用的库代码。
  • pkg 目录下的包一般会按照功能进行区分,例如 /pkg/cache/pkg/conf
  • 可以放工具类,例如string的工具类,就放到/pkg/stringx/string.go,stringx作为包名是为了和sdk里的string包区分。注意:不应该将util、common作为一个包名,意义不明确,容易造成大杂烩。
  • 当根目录包含大量非 Go 组件和目录时,可以挪到pkg里(目前没遇到这类文件)。

Service Application Project Layout

服务应用布局在标准布局的基础上,增加下列目录。

/api

API 协议定义目录,xxapi.proto protobuf 文件,以及生成的 go 文件。我们使用了API大仓的方案,没有用这个目录。具体请移步Gitlab CI/CD 实践六:统一管理 protocol buffer,API 大仓设计与实现

/configs

配置文件模板或默认配置。这里使用的复数,原因是参考其他开开源项目,但Uber-go不建议目录名使用复数。

/test

额外的测试程序和测试数据,这里使用子目录可以达到编译忽略的效果,也就是目录以"."或"_"开头。

不应该包含:/src

有些 Go 项目确实有一个 src 文件夹,但这通常发生在开发人员有 Java 背景,在那里它是一种常见的模式。一般而言,在 Go 项目当中不应该出现 src 目录,Go 和 Java 不同,在 Go 中每一个目录都是一个包,每一个包都是一等公民,我们不需要将项目代码放到 src 当中,不要用写其他语言的方式来写 Go。

kit project

  • 一个公司应该只有一个kit基础库。
  • kit 项目必须具备的特点:
代码语言:txt
复制
- 统一
- 标准库方式布局
- 高度抽象
- 支持插件
- 持续维护尽量少的依赖于三方包,只保留起项目使用的gRPC之类的包。使用接口达到解耦的目的。例如A项目引入kit库,需要使用日志,而kit库里定义了日志接口,打日志的地方都是调用接口。此时需要引入另个一内部logrus基础库,该库实现了kit库定义的接口,底层调用真正的logrus三方包。这样带来的好处:1、避免kit库太臃肿;2、可自行实现日志接口,不使用内部logrus基础库,实现自定义功能,还可以更改依赖的logrus版本。
  • 类似于springboot starter和springboot data starter的关系,一个kit基础库,搭配多个内部实现库。但我们团队内部没有这样做,我们的基础库里放的是各种基础代码,例如创建日志,创建数据库连接等,基本是把业务无关的基础代码封装到kit库里。要说原因的话,我想大概是业务简单,我们经常使用到的基础库代码,大多数和三方包耦合的,可以说就是为了更方便的使用三方包。这和B站的kit库理念不同,他们高度抽象,支持插件,不依赖具体实现,封装的是复杂的,非业务的逻辑。
  • 持续维护这点是mohuishou大佬加的,结合他的经验,这点确实重要。 减少依赖和持续维护是我后面补充的,这一点其实很遗憾,我们部门刚进来的时候方向是对的也建立了一套基础库,然后大家都使用这同一套库,但是很遗憾,我们这一套库一是没人维护,二是没有一套机制来进行迭代,到现在很多团队和项目已经各搞各的了。 这样其实会导致做很多重复工作以及后续的一些改动很难推进,前车之鉴,如果有类似的情况一定要在小火苗出来的时候先摁住,从大的角度来讲统一有时候比好用重要,不好用应该参与贡献而不是另起炉灶。

服务类型

微服务中的 app 服务类型分为5类:interface、service、job、task、admin。

interface

对外的 BFF 服务,接受来自用户的请求,比如暴露了 HTTP/gRPC 接口。

我们的服务业务简单,不需要BFF来聚合,所以没有使用interface。

service

对内的微服务,仅接受来自内部其他服务或者网关的请求,比如暴露了gRPC 接口只对内服务。

admin

区别于 service,更多是面向运营测的服务,通常数据权限更高,隔离带来更好的代码级别安全。

job

流式任务处理的服务,上游一般依赖 message broker。

task

定时任务,类似 cronjob,部署到 task 托管平台中。

项目结构

团队内部应尽量保持项目结构统一,可以使用项目模板生成。

毛剑老师推荐的目录结构,这样做的编译产物名字是比较好的,一看就知道是什么程序。但是我们项目内没有这么干,为避免和项目名冗余,cmd下的目录去掉了myapp1,api定义也是这样的,项目组/项目名/版本/admin.proto。如果此项目只包含一种服务类型,那就去掉服务类型这级目录,包括internal也是。

项目命名

appid,b站采用的是三段式,业务.服务.子服务,例如:account.service.vip

数据库初始化

mohuishou大佬还使用了一个服务类型,myapp-migration: 数据库迁移任务,用于初始化数据库。这个正好是我最近遇到的多环境数据库初始化问题,目前是计划单独做一个项目,通过cicd触发,在各个环境执行数据库变更任务。想在想来,也可以做成一个基础库,项目启动时初始化数据库。但是听龙哥说grom的数据库迁移有问题,很多字段类型对不上,这块后面验证下,看能不能解决。这两种方案,我更倾向于前者,支持跨语言,我们公司内部就存在多种编程语言的服务。

B站项目布局演进

项目布局 v1

项目的依赖路径为: model -> dao -> service -> api,model struct 串联各个层。整体和java的结构类似,controller-》service-》dao。

  • model: 放对应“存储层”的结构体,是对存储的一一隐射。
  • dao: 数据读写层,数据库和缓存全部在这层统一处理,包括 cache miss 处理。
  • service: 组合各种数据访问来构建业务逻辑。
  • server: 依赖 proto 定义的服务作为入参,提供快捷的启动服务全局方法。
  • api: 定义了 API proto 文件,和生成的 stub 代码,它生成的 interface,其实现者在 service 中。
  • service 的方法签名因为实现了 API 的 接口定义,DTO 直接在业务逻辑层直接使用了,更有 dao 直接使用,最简化代码。
  • DO(Domain Object): 领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。缺乏 DTO 和 DO 的对象转换。
v1 存在的问题
  • 没有 DTO 对象,model 中的对象贯穿全局,所有层都有
    • model 的字段,和前端显示的字段是有差异的,例如password。
  • server 层的代码可以通过基础库精简下,公共逻辑封装到基础库,提供统一服务暴露方式。

项目布局 v2

  • app 目录下有 api、cmd、configs、internal 目录,目录里一般还会放置 README、CHANGELOG、OWNERS。
  • internal:是为了避免有同业务下有人跨目录引用了内部的 biz、data、service 等内部 struct。
    • 如果存在一个仓库多个应用,那么可以在 internal 里面进行分层,例如 /internal/admin , /internal/job
    • biz: 业务逻辑的组装层,类似 DDD 的 domain 层,data 类似 DDD 的 repo,repo 接口在这里定义,使用依赖倒置的原则。
    • data: 业务数据访问,包含 cache、db 等封装,实现了 biz 的 repo 接口。我们可能会把 data 与 dao 混淆在一起,data 偏重业务的含义,它所要做的是将领域对象重新拿出来,去掉了 DDD 的 infra 层。
    • 实现接口需要编译校验。go里的接口与实现类是隐式的关联关系,没有依赖关系,由于关系不明确,可使用类型断言:_,interface=(impl)(nil),在编译期间确定实现关系。
    • data层相对于dao层偏业务,不是以表为单位,而是以业务对象为单位,也就是说data层可能包含业务对象组装。另外可将缓存逻辑也放在data层,对上层而言,仅仅是获取对象,不管data层是从db、缓存、http还是grpc里获取的。
    • service: 实现了 api 定义的服务层,类似 DDD 的 application 层,处理 DTO 到 biz 领域实体的转换(DTO -> DO),同时协同各类 biz 交互,但是不应处理复杂逻辑。
  • PO(Persistent Object): 持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么数据表中的每个字段(或若干个)就对应 PO 的一个(或若干个)属性。

示例可以参考 kratos v2 的 example

参考

Go工程化(二) 项目目录结构

Post Views: 5

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-9-12 1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 工程项目结构
    • 序言
      • Standard Go Project Layout
        • /cmd
        • /internal
        • /pkg
      • Service Application Project Layout
        • /api
        • /configs
        • /test
        • 不应该包含:/src
      • kit project
        • 服务类型
          • interface
          • service
          • admin
          • job
          • task
        • 项目结构
          • 项目命名
            • 数据库初始化
              • B站项目布局演进
                • 项目布局 v1
                • 项目布局 v2
              • 参考
              相关产品与服务
              数据库
              云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档