Go工程化

CKeengolanggolang工程化约 2018 字大约 7 分钟

作者:程序员CKeen
博客:http://ckeen.cnopen in new window

长期坚持做有价值的事!积累沉淀,持续成长,升维思考!希望把编码作为长期兴趣爱好😄


平时我们学习go或者是写一些小的demo的时候,可能直接在工程下建一个main.go的文件就直接开始。但是如果想做一个长期维护或者正式应用的项目时,我们还是要按一定的规范来设计项目结构,不然随着项目越来越复杂,代码越来越多,依赖也越来复杂,到时候项目可能都没法进行维护了。这时就需要引入合理的项目工程结构了。

Go工程结构目录

go官方并没有定义go项目工程化的项目结构,但是网上有一个提供参考的Standard Go Project Layout的结构。

参考链接:​ https://github.com/golang-standards/project-layout/blob/master/README_zh.mdopen in new window

虽然不是官方标准,但是基本上已经被大家接受了。首先我们大概分析一下该项目的结构

/cmd

本项目的主干。 cmd目录下的每个应用程序的目录名应该与你想要的可执行文件的名称相匹配,比如:

/cmd/job: 定时任务或从消息队列订阅的异步任务的命令入口

/cmd/service:微服务启动的入口

说明: 该目录相当于应用启动入口,通常有一个main的目录调用,通常不会在这个目录中放置太多代码。

通常我们将cobra的启动的命令放在该目录,用来做项目启动,不同的子命令可以对应不同的启动项目。

而实际执行的业务部分代码从 /internal 和 /pkg 目录导入和调用代码。

如果你认为代码可以导入并在其他项目中使用,那么它应该位于 /pkg 目录中

如果代码不是可重用的,或者你不希望其他人重用它,请将该代码放到 /internal目录中

/internal

私有应用程序和库代码。这是你不希望其他人在其应用程序或库中导入代码。

这个布局模式是由 Go 编译器本身执行的。

参考链接:​ https://golang.org/doc/go1.4#internalpackages。open in new window

因此要注意,internal不局限于顶级 internal 目录。在项目树的任何级别上都可以有多个内部目录。

你可以选择向 internal 包中添加一些额外的结构,以分隔共享和非共享的内部代码。比如内部共享的应用包

/internal/app: 你的实际应用程序代码可以放在该目录下,比如job项目结构/internal/app/job,我们可以将具体的job业务代码放到该目录下

/internal/pkg: 你可以将内部共享包放到该目录下,比如我们将内部配置的配置包(internal/pkg/config、数据库配置(internal/pkg/database)、自己封装的日志包(internal/pkg/log)放到该目录下。

/pkg

外部应用可以使用的库代码(比如 /pkg/netutil)。其它项目想要导入他们,然后正常的工作,所以将内容放到这里请三思。

注意 /internal 是更好的方式来确保你的私有代码不被别人使用,因为它是 Go 强制执行的。

/pkg 是明确传达给别人这是公开的代码可被安全使用的好方法。

Travis Jeffery 写的I'll take pkg over internal open in new window文章很好的说明了 /internal和 /pkg 以及何时使用它们。

/api

OpenAPI/Swagger 规范,协议定义文件,JSON/Proto模式文件。

/web

特定于 Web 应用程序的组件: 静态 Web资源、服务器端模板和 SPAs。

/configs

配置文件模板或默认配置。将你的 confd或 consul-template模板文件放在这里。

/scripts

执行各种构建、安装、分析等操作的脚本。

这些脚本保持了根级别的 Makefile 变得小而简单(例如, https://github.com/hashicorp/terraform/blob/master/Makefileopen in new window)

/build

打包和持续集成。

将你的云( AMI )、容器( Docker )、操作系统( deb、rpm、pkg )包配置和脚本放在 /build/package目录下。

将你的 CI (travis、circle、drone)配置和脚本放在 /build/ci 目录中。请注意,有些 CI 工具(例如 Travis CI)对配置文件的位置非常挑剔。尝试将配置文件放在 /build/ci目录中,将它们链接到 CI 工具期望它们的位置(如果可能的话)。

/deployments

IaaS、PaaS、系统和容器编排部署配置和模板(docker-compose、kubernetes/helm、mesos、terraform、bosh)。注意,在一些存储库中(特别是使用 kubernetes 部署的应用程序),这个目录被称为 /deploy。

/test

额外的外部测试应用程序和测试数据。你可以随时根据需求构造 /test 目录。对于较大的项目,有一个数据子目录是有意义的

/docs

设计和用户文档(除了 godoc 生成的文档之外)。

Go项目结构示例解析

针对以上的Standard Go Project Layout的结构,网上有一个实际对应的使用示例工程项目,具体示例项目参考下面链接:

参考链接:GitHub - sdgmf/go-project-sample: Introduce the best practice experience of Go project with a complete project example.通过一个完整的项目示例介绍Go语言项目的最佳实践经验.open in new window

如果想要按照该项目结构来创建应用,可以直接将代码拉取下来,然后更改相应部分业务即可。

项目整体结构

我们看下示例项目的实际目录构成:

├── LICENSE
├── Makefile											// makefile打包文件
├── README.md
├── api														// 接口协议定义,proto文件
│   └── proto
├── build													// 项目构建目录
│   ├── details
│   ├── products
│   ├── ratings
│   └── reviews
├── cmd														// 项目启动目录入口,这里有4个微服务
│   ├── details
│   ├── products
│   ├── ratings
│   └── reviews
├── configs												// 配置文件目录
│   ├── details.yml
│   ├── grafana
│   ├── products.yml
│   ├── prometheus
│   ├── ratings.yml
│   └── reviews.yml
├── deployments										// 部署文件目录
│   └── docker-compose.yml
├── doc														// 文档目录
│   └── images
├── go.mod
├── go.sum
├── internal										 // 内部文件
│   ├── app
│   └── pkg
├── mocks
│   ├── DetailsClient.go
│   ├── DetailsRepository.go
│   ├── DetailsServer.go
│   ├── DetailsService.go
│   ├── RatingsClient.go
│   ├── RatingsRepository.go
│   ├── RatingsServer.go
│   ├── RatingsService.go
│   ├── ReviewsClient.go
│   ├── ReviewsRepository.go
│   ├── ReviewsServer.go
│   └── ReviewsService.go
└── scripts											// 脚本文件目录
    ├── grafana
    ├── products.sql
    ├── prometheus
    ├── wait-for
    └── wait-for-it.sh

这里功能实现部分主要关注的是cmd目录下的和internal目录下的。

/cmd 项目启动目录结构

cmd目录下按服务划分为details,products,ratings和reviews四个服务。 目录结构如下:

    ├── details
    │   ├── main.go
    │   ├── wire.go
    │   └── wire_gen.go
    ├── products
    │   ├── main.go
    │   ├── wire.go
    │   └── wire_gen.go
    ├── ratings
    │   ├── main.go
    │   ├── wire.go
    │   └── wire_gen.go
    └── reviews
        ├── main.go
        ├── wire.go
        └── wire_gen.go
    

该项目中是依赖注入的wire来管理每个模块之间的依赖注入,在cmd部分将项目配置,以及业务逻辑部分组装成应用,使用cobra可以用来管理相应的子命令来启动相应的服务。

wire的具体使用可以参考:go依赖注入--google开源库wire_CK持续成长的博客-CSDN博客open in new window

/internal私有目录结构

与cmd相对对应的四个服务逻辑实现部分对应internal/app目录下的四个服务业务逻辑实现,internal/app的目录结构结构如下:

    app
    │ 
    ├── details
    │   ├── app.go
    │   ├── controllers
    │   │   ├── controllers.go
    │   │   ├── details.go
    │   │   ├── details_test.go
    │   │   ├── wire.go
    │   │   └── wire_gen.go
    │   ├── grpcservers
    │   │   ├── details.go
    │   │   ├── details_test.go
    │   │   ├── servers.go
    │   │   ├── wire.go
    │   │   └── wire_gen.go
    │   ├── repositories
    │   │   ├── cover.out
    │   │   ├── details.go
    │   │   ├── details_test.go
    │   │   ├── repositories.go
    │   │   ├── wire.go
    │   │   └── wire_gen.go
    │   └── services
    │       ├── details.go
    │       ├── details_test.go
    │       ├── services.go
    │       ├── wire.go
    │       └── wire_gen.go
    ├── products
    │   ├── app.go
    │   ├── controllers
    │   │   ├── controllers.go
    │   │   └── products.go
    │   ├── grpcclients
    │   │   ├── clients.go
    │   │   ├── details.go
    │   │   ├── ratings.go
    │   │   └── reviews.go
    │   └── services
    │       ├── products.go
    │       ├── products_test.go
    │       ├── services.go
    │       ├── wire.go
    │       └── wire_gen.go
    ├── ratings
    │   ├── app.go
    │   ├── controllers
    │   │   ├── controllers.go
    │   │   ├── ratings.go
    │   │   ├── ratings_test.go
    │   │   ├── wire.go
    │   │   └── wire_gen.go
    │   ├── grpcservers
    │   │   ├── rating.go
    │   │   ├── rating_test.go
    │   │   ├── servers.go
    │   │   ├── wire.go
    │   │   └── wire_gen.go
    │   ├── repositories
    │   │   ├── ratings.go
    │   │   ├── ratings_test.go
    │   │   ├── repositories.go
    │   │   ├── wire.go
    │   │   └── wire_gen.go
    │   └── services
    │       ├── ratings.go
    │       ├── ratings_test.go
    │       ├── services.go
    │       ├── wire.go
    │       └── wire_gen.go
    └── reviews
        ├── app.go
        ├── controllers
        │   ├── controllers.go
        │   ├── reviews.go
        │   ├── reviews_test.go
        │   ├── wire.go
        │   └── wire_gen.go
        ├── grpcservers
        │   ├── reviews.go
        │   ├── reviews_test.go
        │   ├── servers.go
        │   ├── wire.go
        │   └── wire_gen.go
        ├── repositories
        │   ├── repositories.go
        │   ├── reviews.go
        │   ├── reviews_test.go
        │   ├── wire.go
        │   └── wire_gen.go
        └── services
            ├── reviews.go
            ├── reviews_test.go
            ├── services.go
            ├── wire.go
            └── wire_gen.go

每个服务分层为controllers,services和repositories。 controllers来管理http的请求,services实现相应部分的业务逻辑,repositories提供数据仓库,该部分也是用wire用来管理主要注入的依赖。

而internal/pkg为内部的共享的包,该结构里面包括config中配置对象一些封装和初始化,比如database,consul等,以及自己二次封装的一些内部库http, grpc, netutil等,具体的internal/pkg目录如下:

    ── pkg
    │       ├── app
    │       │   └── app.go
    │       ├── config
    │       │   └── config.go
    │       ├── consul
    │       │   └── consul.go
    │       ├── database
    │       │   └── database.go
    │       ├── jaeger
    │       │   └── jaeger.go
    │       ├── log
    │       │   └── log.go
    │       ├── models
    │       │   ├── detail.go
    │       │   ├── product.go
    │       │   ├── rating.go
    │       │   └── review.go
    │       ├── transports
    │       │   ├── grpc
    │       │   └── http
    │       └── utils
    │           └── netutil