go项目标准化工程结构解析

Posted CK持续成长

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go项目标准化工程结构解析相关的知识,希望对你有一定的参考价值。

目录

1. Go标准项目结构目录

/cmd

/internal

/pkg

/API

/WEB

/configs

/scripts

/build

/deployments

/test

/docs

2. Go标准项目结构示例解析

 项目整体结构

/cmd 项目启动目录结构

internal私有目录结构


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

1. Go标准项目结构目录

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

参考地址:https://github.com/golang-standards/project-layout/blob/master/README_zh.md

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

  • /cmd

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

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

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

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

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

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

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

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

  • /internal

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

这个布局模式是由 Go 编译器本身执行的。 参考链接: https://golang.org/doc/go1.4#internalpackages。 因此要注意,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 文章很好的说明了 /internal和 /pkg 以及何时使用它们。

  • /API

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

  • /WEB

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

  • /configs

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

  • /scripts

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

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

  • /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 生成的文档之外)。

2. Go标准项目结构示例解析

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

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

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

  •  项目整体结构

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

├── 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博客​​​​​​​

  • 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

Go笔记之一:工程项目结构的注意事项

Go笔记之一:工程项目结构的注意事项


对 Go 项目目录的理解 (Windows平台为例)

刚安装完的 Go 需要设定环境变量,最关键的环境变量有三个,GOROOT、GOPATH和GOBIN。GOROOT应该设定为 GO 在当前环境下的安装根目录,通常因为 windows 平台都是由安装工具安装完成,因此 windows 平台一般不需要设置。GOPATH 理解为是开发者的“工作区”,即表示进行开发编译 Go 程序的工作区,因此 GOPATH 允许设定多值。而 GOBIN 在 GOPATH 被设定为多值时发挥作用,即用于存放由 go install 命令产生的可执行文件。

用一个项目具体举例

项目取名 calcproj,包含一个可执行程序 calc.exe和一个独立库,项目结构如下

> calcproj
> |--calc.go
> |--lib
> |--less.go

less.go 实现了自定义类型:Integer,并提供了一个方法 Less( b Integer ),该方法实现返回当前值是否小于参数值的结果

package lib

type Integer int

func (a Integer) Less(b Integer) bool {
return a < b
}

calc.go 是入口 main,调用 Less 方法并打印出结果,为了方便,main函数中的赋值被固定

package main

import(
"fmt"
"lib"
)

func main(){

var a lib.Integer = 3
fmt.Println(a, a.Less(2));
}

进入 src 目录运行 go build calc.gogo build,在src目录下获得可执行文件calc.exe,使用命令行工具运行,得到结果

C:\cinlapgo\calcproj\src>calc.exe
3 false

由书+实践+热心网友得到的经验总结

  • [x] Go的项目可以以自定义名称开头,如myhelloTxtReader等等
  • [x] 在项目目录下,按照 Go 的规则(至少)建立src目录
  • [x] 以src为根目录,main包就放在src下,如果有其它包(如C#中的dll),就建子目录。子目录名字就是包名称,其下的 go 文档开头的也要符合该格式 package 包名称(目录名)
  • [x] 如何正确的对待命令源码文件

    • [x] 当代码包有且仅有一个命令源码文件时,在所在目录中执行go build命令,即可在该目录下生成一个与目录同名的可执行文件;若使用go install命令,则可在当前工作区bin目录下生成相应的可执行文件
    • [x] 命令源码文件不能与库源码文件在同一个代码包,否则无法正确执行go buildgo install命令
    • [x] 同一个代码包中可以有多个命令源码文件,通过go run命令分别运行,但这会使go buildgo install命令无法编译和安装该代码包。

感谢

特别感谢Go语言技术交流群的 @【副总】西安-n1ce和@【总裁】重庆-im帥

参考资料

  • 参考书籍:《Go并发编程实战》
  • 参考书籍:《Go语言编程》

以上是关于go项目标准化工程结构解析的主要内容,如果未能解决你的问题,请参考以下文章

go学习Golang底层学习笔记

golang是啥意思

golang怎么实现psd

Golang工程经验(上)

Go语言自学系列 | golang标准库bytes

Go语言标准库之命令行参数的解析:flag 库详解