项目结构
一个良好、一致的项目结构是软件工程的最佳实践之一。Maltose 通过 maltose new
命令为用户提供了一套经过精心设计的、推荐的项目结构,旨在实现关注点分离,提高项目的可维护性和可扩展性。
当您执行 maltose new <project-name>
后,会得到如下的目录结构:
.
├── api/ # API 定义和请求/响应结构
│ └── v1/ # API 版本
├── cmd/ # 应用程序入口
│ ├── server.go # 服务启动
│ └── config.go # (可选) 配置加载及钩子(Hook)函数
├── config/ # 配置文件
├── internal/ # 内部代码,不对外暴露
│ ├── controller/ # 控制器
│ ├── dao/ # 数据访问对象
│ ├── logic/ # 业务逻辑实现
│ ├── model/ # 数据模型
│ │ ├── entity/ # 数据库实体
│ │ └── do/ # (可选) 业务数据对象
│ ├── provider/ # 第三方服务封装
│ └── service/ # 服务接口定义
├── utility/ # 通用工具
├── go.mod
└── main.go # 项目主入口
以下是各主要目录和文件的职责说明:
/api
存放 API 的定义文件(.go
格式)。这里的"定义"主要指请求和响应的 struct
,是项目的对外契约。
/cmd
项目的启动入口层。负责应用的启动、依赖注入、路由注册等核心初始化工作。
/config
存放应用的配置文件,例如 config.yaml
。
/internal
存放项目内部的业务逻辑代码,这是项目的主要工作区。Go 语言的机制确保了 internal
包下的代码只能被项目内部引用,无法被外部项目导入。
- /controller: 控制器层。负责接收和解析来自路由的请求,进行参数校验,然后调用
Logic
层处理业务,最后向客户端返回响应。 - /service: 服务接口层。定义了业务逻辑所需的接口 (
interface
)。 - /logic: 业务逻辑层。这是实现
service
接口的地方,它会组织和编排DAO
和Provider
提供的能力来完成一个完整的业务流程。 - /dao: 数据访问对象 (DAO) 层。封装了对数据库的底层操作,直接与数据库交互。
- /provider: 第三方服务封装层。当您的业务需要调用外部 API(例如,其他微服务、SaaS 服务如短信邮件等)时,建议在此目录下进行封装。
- /model: 数据模型层。存放项目所有的数据结构定义。
- /entity: 数据实体 (Entity)。存放与数据库表结构一一对应的
struct
。 - /do: (可选) 数据对象 (Data Object)。手动创建,用于承载复杂的数据库查询结果。
- /entity: 数据实体 (Entity)。存放与数据库表结构一一对应的
关于 Provider 层的最佳实践
Provider
层是解耦内部业务逻辑与外部服务依赖的关键。一个设计良好的 Provider
层可以显著提高项目的可测试性和可维护性。
- 职责:
Provider
的核心职责是将所有对外部服务的调用(例如,调用其他微服务、请求第三方 SaaS 平台 API)封装起来,对Logic
层屏蔽其实现细节。 - 实现:
- 建议为每一个外部服务创建一个单独的
provider
包,例如internal/provider/user_service
。 - 在包内,可以定义一个
struct
,并为其实现调用外部服务的具体方法。 - 内部应优先使用框架提供的
mclient
来执行 HTTP/gRPC 请求。
- 建议为每一个外部服务创建一个单独的
- 使用:
Logic
层通过依赖注入的方式直接使用具体的Provider
实现,而无需在Service
层为其定义接口。- 这种做法可以避免为不属于核心业务领域的外部调用创建过多的
interface
,保持Service
层的纯粹性,同时也简化了依赖关系。
关于 do
和 entity
的思考
为了保持框架的轻量级和灵活性,maltose
在数据模型层提供了 entity
和 do
两种结构,但它们的定位与传统重型框架有所不同。
entity
(实体) - 基础数据模型- 职责:
entity
的结构与数据库表结构 严格 1:1 对应。maltose gen model
命令会为您自动生成并维护它。 - 定位: 它是所有标准、简单 CRUD 场景下的 默认且推荐 的数据模型。
gen dao
命令生成的默认方法也直接使用entity
。
- 职责:
do
(数据对象) - 可选的高级扩展- 职责:
do
目录默认不被强制使用,它是一个可选的、需要您手动创建的目录,用于解决entity
无法优雅处理的复杂场景。 - 定位: 它不是一个被强制推行的规范,而是一个解决特定问题的"工具箱"。如果您的项目业务逻辑简单,完全可以忽略或删除
do
目录。
- 职责:
什么时候应该手动创建和使用 do
?
当您的业务发展到一定复杂度,遇到以下场景时,do
层将是您的得力助手:
承载复杂数据库查询结果: 当您需要执行多表
JOIN
或其他复杂 SQL 查询时,其返回结果的结构往往与任何单一的entity
都不匹配。此时,在internal/model/do/
目录下手动创建一个struct
来定义这个专用的结果集,是最佳实践。聚合内部业务数据: 当
Service
层需要将来自不同数据源(例如,MySQL 中的entity
、Redis 缓存、第三方 API 返回的数据)的信息组合成一个统一的对象,以便在业务逻辑内部流转时,do
提供了一个理想的"聚合模型"存放地。
简而言之,maltose
的设计哲学是:entity
为本,do
为用。我们默认提供最轻量、最直接的 entity
方案满足 80% 的常规需求,同时保留了 do
这一扩展能力,让开发者在面对 20% 的复杂问题时,有路可循,有章法可依。
/utility
存放项目范围内的通用工具函数或模块,例如统一的日志记录器、错误码定义等。
通过遵循这套项目结构,您可以更轻松地管理代码,并与团队成员高效协作。