百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Go架构分层cmd, internal, pkg你还MVC?

bigegpt 2024-08-24 23:20 2 浏览

https://www.ardanlabs.com/blog/2017/02/package-oriented-design.html

https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ee658109(v=pandp.10)

https://github.com/golang-standards/project-layout

https://github.com/danceyoung/paper-code/tree/master/examples/groupevent


本篇内容主要讲解golang项目的基础架构分层。


百年太久,只争朝夕,不负韶华,不枉少年,来日怎方长。


概述

一个基本的go项目一般会有`cmd`, `internal`, `pkg`三个基础目录来分层,当然这不是官方`go`核心开发团队定义的标准。但这个确实是目前`go`生态系统中比较常见的布局形式,不管从之前的和还是现在开发项目的分层来看。这些基础目录同样适用更大的项目,并且还有一些小的增强功能。


如果你创建一个项目来学习go或你开发的是一个PoC或很小的项目,这种分层就没必要使用了,可能一个`main.go`文件就够了,即把数据、业务逻辑、规则、路由等等全部放在这个文件即可,也是所谓的反模式。但是随着业务不断变化而你的项目也不断扩大,就有必要考虑这种分层模式了,否则你会欠下凌乱无序、不易扩展、不可维护的技术债。当你有更多的团队成员开发同一个项目,就更需要一个合适的架构了,这个时候介绍一个常用的基本模式来管理包和库显得那么重要的原因了。如果你有一个开源的项目,并且别人会导入你项目的代码,这时你的项目中有一个私有(通常叫`internal`)的包是非常重要的,这时clone下来你的项目,就仅仅保留自己想要的,删除其他不用的包或代码,比如`internal`文件夹的内容。至于用到那些模式和目录,视你的项目情况而定,比如`vendor`就不是一定有的。


之前go项目的三方依赖包的管理最早有`vendor, go dep`等等,但都不是官方的,使用起来也不是尽善尽美,并不能像java项目maven那样的粒度管理三方依赖包。但随着go 1.14正式发布,`go modules`管理三方依赖包的工具也正式发布了。请尽量使用go modules, 除非你有一定不用他的理由。用go modules,你就不用关心GOPATH和非要把你的项目放在go workspace文件夹了。


这样的项目架构分层只是一种通用的模式,他不会给go的面向包的设计强加什么东西。面向包设计的理念让开发者在一个 go 项目中确定包的组织和必须要遵守的设计准则。它定义了一个 go 项目应该是什么样的及怎么架构和分层一个 go 项目。它最终的目的是为了提高项目的可读性、代码整洁性和可交流性,便于团队成员沟通。一个很好的大家都理解的架构本身就是一种通用的沟通语言。


项目架构分层

每个公司都会有一个工具包的项目和不同业务的应用项目。

在这里由于篇幅的问题(个人不喜欢长篇大论),不在论述MVC(本身也是很好的架构分层),大家可以看下微软的一篇文章https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ee658109(v=pandp.10),里面有很详细很详细的介绍。


工具包项目

考虑到工具包作为公司的一个标准类库,所以应该仅有一个。里面的所有包都需要设计为高可移植性。这些包可以在任何一个项目中都能使用,并且提供的都是很实用、具体的但又非常基础的功能。为了达到这样的目标,工具包项目不能有一个包依赖三方的 vendor。因为如果有包依赖三方包,那就得不断的构建编译随着那些三方包的更新。

同时也不建议把工具包项目的部分包直接复制到你的应用项目中,因为这样本身增加了你对这些包管理、更新的工作,当然你如果真这样做也没毛病。

应用项目

应用项目是包含了很多需要部署在一起的程序集,包括服务、命令行工具和后台运行的程序。每个项目都对应一个含有其所有源代码的仓库,包括所有依赖的三方包。你需要几个应用项目,视情况以你而定,当然是越少越好。

每个应用项目通常包含三个根目录,分别是 `cmd, internal, pkg, vendor`。在 internal 文件里也会包含 `pkg` 目录,但是它和 internal 里其他的包有着不同的设计约束。

一个典型的应用项目结构应该是这样的:

paper-code/examples/groupevent
├── cmd/
│   └── eventtimer/
│       └── update/
│       └── main.go
│   └── eventserver/
│       └── router/
│           └── handler/
│           └── router.go
│       └── tests/
│       └── main.go
├── internal/
│   └── eventserver/
│       └── biz/
│           └── event/
│           └── member/
│       └── data/
│           └── service/
│   └── eventpopdserver/
│       └── event/
│       └── member/
│   └── pkg/
│       └── cfg/
│       └── db/
│       └── log/
└── vendor/
│   ├── github.com/
│   │   ├── ardanlabs/
│   │   ├── golang/
│   │   ├── prometheus/
│   └── golang.org/
├── go.mod
├── go.sum

cmd/ 项目中的所有你将要编译成可执行程序的入口代码都放在`cmd/` 文件夹里,这些代码和业务没有关系。每个程序对应一个文件夹,文件夹的名称应该以程序的名称命名。一般在名称后面加上`d` 代表该程序是一个守护进程运行。

每个文件夹必须有一个`main`包的源文件,该源文件的名称也最好命名成可执行程序的名称,当然也可以保留main文件名。在此会导入和调用`internal/`和`pkg/`等其他文件夹中相关的代码。

├── cmd/
│   └── eventtimer/
│       └── update/
│       └── main.go
│   └── eventserver/
│       └── router/
│           └── handler/
│           └── router.go
│       └── tests/
│       └── main.go
  • 该项目包含线上业务服务eventserver(提供restful API)、定时器eventtimer(定时更新数据的状态)二个应用程序。`cmd`文件夹对应有2个文件夹,并且每个文件夹下面都有一个`main`包的源文件,至于名称可以直接用main,也可以对应文件夹的名称。
  • 每个文件夹下的源文件里的代码和业务逻辑基本没任何关系。比如rest ful的eventserver,里面仅包含router的配置和相关的handler。


internal/ 在go语言中,变量,函数,方法等的存取权限只有exported(全局)和unexported(包可见,局部)2种。

在项目中不被复用,也不能被其他项目导入,仅被本项目内部使用的代码包即私有的代码包都应该放在`internal`文件夹下。该文件夹下的所有包及相应文件都有一个项目保护级别,即其他项目是不能导入这些包的,仅仅是该项目内部使用。

如果你在其他项目中导入另一个项目的`internal`的代码包,保存或`go build` 编译时会报错`use of internal package ... not allowed`,该特性是在go 1.4版本开始支持的,编译时强行校验。

package main
2
3 import (
4  "paper-code/examples/groupevent/cmd/eventserver/router/handler"
5  "paper-code/examples/groupevent/cmd/internal"
6  "paper-code/examples/groupevent/internal/eventpopdserver/event"
7  "paper-code/examples/groupevent/pkg/middleware"
8 )
9
10 func main() {
11   middleware.HandlerConv(nil)
12
13  event.EventsBy("")
14
15  eh := new(handler.EventHandler)
16  eh.Events(nil, nil)
17
18  internal.CmdInternalFunc()
19 }
  • 此代码片段为另一个项目导入paper-code/example/groupevent的代码
  • 第6行的导入就会提示`use of internal package paper-code/examples/groupevent/internal/eventpopdserver/event not allowed`
  • 第5行的导入也会提示同样的错误
  • 第7行导入就可以的,因为导入的pkg代码包

当然你也不要局限根目录下的`internal`目录,你也可以在任何一个目录中创建`internal`,规则都适用。比如上面的例子`第5行的导入也会提示同样的错误:use of internal package paper-code/examples/groupevent/cmd/internal not allowed`

`internal`包的导入规则是:`.../a/b/c/internal/d/e/f` 仅仅可以被`.../a/b/c`下的目录导入,`.../a/b/g`则不允许。这样你就可以根据此规则在同一个go项目中不同的路径添加`internal`文件夹来设置可分享、不可分享的代码。

internal/pkg/ 在同一个项目中不同程序需要访问,但又不能让其他项目访问的代码包,需要放在这里。这些包是比较基础但又提供了很特殊的功能,比如数据库、日志、用户验证等功能。

pkg/ 如果你把代码包放在根目录的`pkg`下,其他项目是可以直接导入`pkg`下的代码包的,即这里的代码包是开放的,当然你的项目本身也可以直接访问的。但是如果你要把代码放在`pkg`下,还想需要三思而后行吧,有没必要这样做,毕竟`internal`目录是最好的方式保护你的代码并且被go编译器强制校验`internal`的代码包不可分享的。如果你的项目是一个开源的并且让其他人使用你封装的一些函数等,这样做是合适的,如果你自己或公司的某一个项目,个人的经验,基本上用不上`pkg`

vendor/ vendor文件夹包含了所有依赖的三方的源代码,它是go项目最早的依赖包的管理方式。目前大都用的go mod的依赖包管理,相对vendor,能指定版本,并且你不用特意手动下载更新依赖包,通过正常的go build, go run命令会自动处理。这样会减少项目本身的容量大小。

你可以用命令 `go mod vendor`来创建你项目的vendor目录。如果你项目中既要用到之前的vendor,又要用到go mod,你可以使用 `-mod=vendor`参数进行编译,但是在go1.14就不用了,当你用go build时,会自动检查项目根目录下有无vendor,并进行编译。

结论

在实际go项目开发中,一定要灵活运用,当然也可以完全不按照这样架构分层,一切以项目的大小、业务的复杂度、个人专业技能及认知的广度和深度、时间的紧迫度为准。

最后以软件大师 [Kent Beck](https://baike.baidu.com/item/Kent%20Beck/13006051?fr=aladdin) 在《重构Refactoring》一书中描述的结尾。

  • 先让代码工作起来-如果代码不能工作,就不能产生价值
  • 然后再试图将它变好-通过对代码进行重构,让我们自己和其他人更好地理解代码,并能按照需求不断地修改代码。
  • 最后再试着让它运行得更快-按照性能提升的需求来重构代码。


谢谢

最后推荐一个工具,几秒内快速创建一个符合本文论述的一个go项目.

https://github.com/danceyoung/goslayer


下篇将基于cmd, internal, pkg的架构分层,来验证包的设计。


篇篇更精彩,章章有深度


感谢大家转发、关注和一起讨论学习

相关推荐

AI「自我复制」能力曝光,RepliBench警示:大模型正在学会伪造身份

科幻中AI自我复制失控场景,正成为现实世界严肃的研究课题。英国AISI推出RepliBench基准,分解并评估AI自主复制所需的四大核心能力。测试显示,当前AI尚不具备完全自主复制能力,但在获取资源...

【Python第三方库安装】介绍8种情况,这里最全看这里就够了!

**本图文作品主要解决CMD或pycharm终端下载安装第三方库可能出错的问题**本作品介绍了8种安装方法,这里最全的python第三方库安装教程,简单易上手,满满干货!希望大家能愉快地写代码,而不要...

pyvips,一个神奇的 Python 库!(pythonvip视频)

大家好,今天为大家分享一个神奇的Python库-pyvips。在图像处理领域,高效和快速的图像处理工具对于开发者来说至关重要。pyvips是一个强大的Python库,基于libvips...

mac 安装tesseract、pytesseract以及简单使用

一.tesseract-OCR的介绍1.tesseract-OCR是一个开源的OCR引擎,能识别100多种语言,专门用于对图片文字进行识别,并获取文本。但是它的缺点是对手写的识别能力比较差。2.用te...

实测o3/o4-mini:3分钟解决欧拉问题,OpenAI最强模型名副其实!

号称“OpenAI迄今为止最强模型”,o3/o4-mini真实能力究竟如何?就在发布后的几小时内,网友们的第一波实测已新鲜出炉。最强推理模型o3,即使遇上首位全职提示词工程师RileyGoodsid...

使用Python将图片转换为字符画并保存到文件

字符画(ASCIIArt)是将图片转换为由字符组成的艺术作品。利用Python,我们可以轻松实现图片转字符画的功能。本教程将带你一步步实现这个功能,并详细解释每一步的代码和实现原理。环境准备首先,你...

5分钟-python包管理器pip安装(python pip安装包)

pip是一个现代的,通用、普遍的Python包管理工具。提供了对Python包的查找、下载、安装、卸载的功能,是Python开发的基础。第一步:PC端打开网址:选择gz后缀的文件下载第二步:...

网络问题快速排查,你也能当好自己家的网络攻城狮

前面写了一篇关于网络基础和常见故障排查的,只列举了工具。没具体排查方式。这篇重点把几个常用工具的组合讲解一下。先有请今天的主角:nslookup及dig,traceroute,httping,teln...

终于把TCP/IP 协议讲的明明白白了,再也不怕被问三次握手了

文:涤生_Woo下周就开始和大家成体系的讲hadoop了,里面的每一个模块的技术细节我都会涉及到,希望大家会喜欢。当然了你也可以评论或者留言自己喜欢的技术,还是那句话,希望咱们一起进步。今天周五,讲讲...

记一次工控触摸屏故障的处理(工控触摸屏维修)

先说明一下,虽然我是自动化专业毕业,但已经很多年不从事现场一线的工控工作了。但自己在单位做的工作也牵涉到信息化与自动化的整合,所以平时也略有关注。上一周一个朋友接到一个活,一家光伏企业用于启动机组的触...

19、90秒快速“读懂”路由、交换命令行基础

命令行视图VRP分层的命令结构定义了很多命令行视图,每条命令只能在特定的视图中执行。本例介绍了常见的命令行视图。每个命令都注册在一个或多个命令视图下,用户只有先进入这个命令所在的视图,才能运行相应的命...

摄像头没图像的几个检查方法(摄像头没图像怎么修复)

背景描述:安防监控项目上,用户的摄像头运行了一段时间有部分摄像头不能进行预览,需要针对不能预览的摄像头进行排查,下面列出几个常见的排查方法。问题解决:一般情况为网络、供电、设备配置等情况。一,网络检查...

小谈:必需脂肪酸(必需脂肪酸主要包括)

必需脂肪酸是指机体生命活动必不可少,但机体自身又不能合成,必需由食物供给的多不饱和脂肪酸(PUFA)。必需脂肪酸主要包括两种,一种是ω-3系列的α-亚麻酸(18:3),一种是ω-6系列的亚油酸(18:...

期刊推荐:15本sci四区易发表的机械类期刊

  虽然,Sci四区期刊相比收录在sci一区、二区、三区的期刊来说要求不是那么高,投稿起来也相对容易一些。但,sci四区所收录的期刊中每本期刊的投稿难易程度也是不一样的。为方便大家投稿,本文给大家推荐...

be sick of 用法考察(be in lack of的用法)

besick表示病了,做谓语.本身是形容词,有多种意思.最通常的是:生病,恶心,呕吐,不适,晕,厌烦,无法忍受asickchild生病的孩子Hermother'sverysi...