Go语言项目的组织方式

构建方式

最好保持传统使用make构建,避免新构建工具的构建成本

Lint

golangci-lint,并开启所有默认的linter规则

文档

doc.go存放该包的一般描述 如果一个文件放不下,那么可以准备一个docs文件夹

Readme文件

  • 项目目标
  • 快速入门部分:开始处理项目时应该做的事情,任何依赖项,基本示例,描述项目的外部链接

包组织方式

最推荐的方法:在不被迫添加新包的前提下,直接将整个代码保存在根目录下,避免循环依赖 创建新包的理由:

  1. 当你有不止一种启动应用程序的方式,例如Web API和CLI共存的时候,创建一个/cmd包并包含cliweb子包,并在运行时指定到文件go run ./cmd/cli
  2. 当你想提取更详细的实现时
  3. 当你开始为密切相关的事物添加公共前缀时
r := networkReader{}
//
r := network.Reader{}

模块化

两种组织方法

  1. 按种类组织
.
|-- handlers
|   |-- course.go
|   |-- lecture.go
|   |-- profile.go
|   |-- user.go
|-- main.go
|-- models
|   |-- course.go
|   |-- lecture.go
|   |-- user.go
|-- repositories
|   |-- course.go
|   |-- lecture.go
|   |-- user.go
|-- services
|   |-- course.go
|   |-- user.go
|-- utils
    |-- strings.go

缺点:每个类型、常量或函数都必须是公开的,才能在项目的另一部分访问;在写代码或者debug的时候,经常需要在多个包之间寻找问题。

  1. 按组件组织
.
|-- course
|   |-- httphandler.go
|   |-- model.go
|   |-- repository.go
|   |-- service.go
|-- main.go
|-- profile
    |-- httphandler.go
    |-- model.go
    |-- repository.go
    |-- service.go

缺点:容易发生相互依赖的问题 例如,想在用户的个人资料上显示最近的课程,从个人资料的角度上来看,课程是一种外部依赖,解决方法是在profile下创建一个接口。

type Courses interface {
    MostRecent(ctx context.Context, userID string, max int) ([]course.Model, error)
}

然后在course包中,公开实现该接口的服务

type Courses struct {
    // 一些不能导出的变量
}

func (c Courses) MostRecent(ctx context.Context, userID string, max int) ([]Model, error) {
    // 实现逻辑
}

最后,在main.go中从course包中创建Courses实例并传递给profile包,便可以在不引发循环依赖的情况下实现该功能。此外,写profile的测试时,可以直接mock创建一个实现,甚至可以在没有course实现包的情况下开发和测试profile的功能。

简洁架构

应用程序或模块有4层,分别是Domain、Application、Ports、Adapters。

  • Domain:业务核心,没有外部依赖,不应该知道代码是在哪个上下文执行的
  • Application:应用程序用法的粘合点,所有Domain、Ports、Adapter的逻辑串联位置
  • Ports: 用户输入,获取信息后传入给Application
  • Adapters: 与外部通信,存储和获取数据,对低层细节的抽象。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注