编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

基于cmd,internal,pkg验证包的合理性in Go

wxchong 2024-08-31 04:06:48 开源技术 19 ℃ 0 评论

上篇介绍了虽然不是官方的架构分层,但却是go生态中大家喜欢用的分层模式,如果你经常看源码的话,你会在各大项目的源码中时不时看到cmd, internal, pkg的目录。

https://www.toutiao.com/i6913074488875811340/?group_id=6913074488875811340

该篇要基于cmd, internal, pkg架构分层来验证你的包设计


面向包的设计和验证

面向包设计的准则可以验证项目中包设计的是否合理,下面这些步骤可以帮你发现包设计的问题。

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


包的位置

  • `kit`

被不同应用项目导入的基础包

  • `cmd`

支持编译不同二进制程序的包,比如Restful路由程序,需要相关router, handler包和main入口包。

  • `internal`

项目内部使用的包,包括crud, service(facade)和业务逻辑的包。

  • `internal/pkg`

为本项目内部使用的基础包,包括数据库、认证和序列化等操作的包。

  • `pkg`

其他项目可以访问pkg的代码包


依赖包导入

  • 不同包导入类型或同一级别互相导入

如果有在一个包中导入另一个包中的类型或同一个目录级别下包互相导入的情况,请检查你对领域知识的理解、领域模型设计和包的设计是否合理。 如果情非得已,那么将被导入的包移动到你的包里面。

- go源码里面的网络方面的`Request, Response, Header`等都在`http`包下面 - go的设计本身不建议建一个model包,里面全是一个个结构体。因为这样设计,让其他人看代码,可能不知道这些结构体在哪被使用,修改了结构体,也不知道影响面有多大。 - go更多是按照功能职责进行包的设计,所以同一目录级别下的包是不应该互相导入的。除非你采用了在其他语言的架构分层是可以导入的,但也仅限上层可以导入下层的代码包,比如服务层、展现层、业务逻辑层、数据持久化层。

- eventserver下的biz, data就是按照传统的业务逻辑层、数据层这样的架构分层进行的设计。这样biz里面的代码包就可以导入同一目录级别下的data下的代码包,反之不然。

- eventpopdserver下的event, member是按照功能职责进行的设计,2者不能互相导入。架构大致上分2种,一个就是通用分层(presenter, service, business, data ...)的架构分层,另一种就是按照功能职责进行分层,go倾向于后者。

  • `cmd/`可以导入其他目录中的代码包。
  • `internal/`中的包不能导入`cmd/`中的包。
  • `internal/pkg/`中的包不能导入`cmd/`, `internal/`中的包。
  • `pkg/`中的包不能导入`cmd/`, `internal/`中的包。


应用级别的策略

比如日志打印在`Kit`, `internal/pkg/, pkg/`中是不允许写这些策略的,因为这些都是某种意义上共用通用的代码包。在这里数据库的配置、日志文件的配置应该和运行时环境的改变是松散耦合的,可以通过环境变量来修改配置。

数据的发送和接收

  • 在语意上要确定好一个发送和接收的类型,即值类型还是引用类型。

比如golang的`http`包中的`Request`结构体,在http中是以引用类型使用的。在传递过程中始终指向的是同一个Request

  • 如果你用一个接口类型的变量接收一个返回值,则更多的目的应该是调用接口的方法即行为,而不是值本身。如果不是这样,请直接用具体的类型。

错误处理

错误处理包括错误信息的日志输出,分析和解决错误,并且保证程序能恢复如果发生了错误。

  • `Kit`

不允许使用`panic`终止程序或抛出错误。

不允许再次包装错误信息,原本原样的把系统错误或框架的错误返回即可。

  • `cmd/`

允许使用`panic`终止程序或抛出错误。

如果有错误发生且不处理,可以根据此时的业务或逻辑上下文包装一下错误,让更上层的处理错误的函数能知道是哪里抛出的错误。

当然大多数的错误都应该在这里处理。

  • `internal/`

不允许使用`panic`终止程序或抛出错误。

如果有错误发生且不处理,可以根据此时的业务或逻辑上下文包装一下错误,让更上层的处理错误的函数能知道是哪里抛出的错误。

当然大多数的错误都应该在这里处理。

  • `internal/pkg/`

不允许使用`panic`终止程序或抛出错误。

不允许再次包装错误信息,原本原样的把系统错误或框架的错误返回即可。

  • `pkg/`

不允许使用`panic`终止程序或抛出错误。

不允许再次包装错误信息,原本原样的把系统错误或框架的错误返回即可。

测试

  • `cmd/`

允许使用第三方的测试包。

可以独立创建一个test包来管理单元测试的文件。

这里更多是集成测试而不是单元测试。

  • `kit/`, `internal/`, `internal/pkg/,pkg/`

强烈推荐使用golang的testing包。

test文件可以直接创建在对应包下面。

这里更多是单元测试而不是集成测试。


不建议的目录

  • `src/`

src目录在java开发语言的项目中是一个常用的模式,但是在go开发项目中,尽量不要使用src目录。

  • `model/`

在其他语言开发中一个非常通用的模块叫model,把所有类型都放在model里。但是在go里不建议的,因为go的包设计是根据功能职责划分的。比如一个User 模型,应该声明在他被用的功能模块里。

  • `xxs/`

带复数的目录或包。虽然go源码中有strings包,但更多都是用单数形式。


结论

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

比如错误的处理,也不一定非要按照规范来实现,在internal的业务包里可以panic,但是也需要包装错误信息,然后在cmd的某处统一recover识别并处理,这样就不用到处判断err是否nil,然后进步的处理。

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

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

谢谢

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

https://github.com/danceyoung/goslayer



篇篇更精彩,章章有深度


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

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表