Skip to content

项目结构

一个良好、一致的项目结构是软件工程的最佳实践之一。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 接口的地方,它会组织和编排 DAOProvider 提供的能力来完成一个完整的业务流程。
  • /dao: 数据访问对象 (DAO) 层。封装了对数据库的底层操作,直接与数据库交互。
  • /provider: 第三方服务封装层。当您的业务需要调用外部 API(例如,其他微服务、SaaS 服务如短信邮件等)时,建议在此目录下进行封装。
  • /model: 数据模型层。存放项目所有的数据结构定义。
    • /entity: 数据实体 (Entity)。存放与数据库表结构一一对应的 struct
    • /do: (可选) 数据对象 (Data Object)。手动创建,用于承载复杂的数据库查询结果。

关于 Provider 层的最佳实践

Provider 层是解耦内部业务逻辑与外部服务依赖的关键。一个设计良好的 Provider 层可以显著提高项目的可测试性和可维护性。

  • 职责: Provider 的核心职责是将所有对外部服务的调用(例如,调用其他微服务、请求第三方 SaaS 平台 API)封装起来,对 Logic 层屏蔽其实现细节。
  • 实现:
    • 建议为每一个外部服务创建一个单独的 provider 包,例如 internal/provider/user_service
    • 在包内,可以定义一个 struct,并为其实现调用外部服务的具体方法。
    • 内部应优先使用框架提供的 mclient 来执行 HTTP/gRPC 请求。
  • 使用:
    • Logic 层通过依赖注入的方式直接使用具体的 Provider 实现,而无需在 Service 层为其定义接口。
    • 这种做法可以避免为不属于核心业务领域的外部调用创建过多的 interface,保持 Service 层的纯粹性,同时也简化了依赖关系。

关于 doentity 的思考

为了保持框架的轻量级和灵活性,maltose 在数据模型层提供了 entitydo 两种结构,但它们的定位与传统重型框架有所不同。

  • entity (实体) - 基础数据模型

    • 职责: entity 的结构与数据库表结构 严格 1:1 对应maltose gen model 命令会为您自动生成并维护它。
    • 定位: 它是所有标准、简单 CRUD 场景下的 默认且推荐 的数据模型。gen dao 命令生成的默认方法也直接使用 entity
  • do (数据对象) - 可选的高级扩展

    • 职责: do 目录默认不被强制使用,它是一个可选的、需要您手动创建的目录,用于解决 entity 无法优雅处理的复杂场景。
    • 定位: 它不是一个被强制推行的规范,而是一个解决特定问题的"工具箱"。如果您的项目业务逻辑简单,完全可以忽略或删除 do 目录

什么时候应该手动创建和使用 do

当您的业务发展到一定复杂度,遇到以下场景时,do 层将是您的得力助手:

  1. 承载复杂数据库查询结果: 当您需要执行多表 JOIN 或其他复杂 SQL 查询时,其返回结果的结构往往与任何单一的 entity 都不匹配。此时,在 internal/model/do/ 目录下手动创建一个 struct 来定义这个专用的结果集,是最佳实践。

  2. 聚合内部业务数据: 当 Service 层需要将来自不同数据源(例如,MySQL 中的 entity、Redis 缓存、第三方 API 返回的数据)的信息组合成一个统一的对象,以便在业务逻辑内部流转时,do 提供了一个理想的"聚合模型"存放地。

简而言之,maltose 的设计哲学是:entity 为本,do 为用。我们默认提供最轻量、最直接的 entity 方案满足 80% 的常规需求,同时保留了 do 这一扩展能力,让开发者在面对 20% 的复杂问题时,有路可循,有章法可依。

/utility

存放项目范围内的通用工具函数或模块,例如统一的日志记录器、错误码定义等。

通过遵循这套项目结构,您可以更轻松地管理代码,并与团队成员高效协作。

Released under the MIT License.