Skip to content

项目结构

一个良好、一致的项目结构是软件工程的最佳实践之一。Maltose 通过 maltose new 命令为用户提供了一套经过精心设计的、推荐的项目结构,旨在实现关注点分离,提高项目的可维护性和可扩展性。

命名规范

在 Maltose 中,我们推荐遵循一套能最大化利用代码生成优势的命名规范。这能极大地提升项目的可读性和可维护性。

快速概览:一个完整的例子

我们以一个名为 user-center 的用户中心模块为例,看它在 Maltose 的工作流中如何演变:

阶段示例规范 (Case)说明
1. API 设计api/user-center/v1/Kebab-case目录名:使用连字符,对 URL 和 DevOps 生态友好。
2. Service 生成internal/service/user_center.goSnake-case文件名:自动转换为下划线,符合 Go 社区习惯。
3. Logic 生成internal/logic/usercenter/lowerCase逻辑层包/目录:自动转换为小写单词,符合 Go 包规范。
4. Go 代码内部type sUserCenter struct {}CamelCase类型名:自动转换为驼峰式,符合 Go 语法要求。

遵循这套规范,您可以在享受目录命名便利性的同时,产出完全符合 Go 社区规范的内部代码。


核心原则详解

  1. 目录名 (Modules): 使用连字符 (kebab-case)

    • 这是 Web 和云原生生态的事实标准。Maltose 的生成工具会为您处理好后续的转换。
    • 示例: api/user-center/, api/order-management/
  2. Go 文件名 (Filenames): 使用下划线 (snake_case)

    • 这是 Go 社区的编码风格共识,可读性好。
    • 示例: user_center.go, order_management.go
  3. Go 包名 (Packages): 使用小写单个单词

    • 这是 Go 语言的官方约定。对于需要复合词的复杂包名,最佳实践是直接将小写单词连接起来(如 package httpclient),并绝对避免使用下划线或驼峰式作为包名。
    • 示例: package service, package usercenter
  4. Go 类型名 (Types): 使用驼峰式 (CamelCase)

    • 这是 Go 语言的强制语法要求,用于区分导出和非导出成员。
    • 示例: type sUserCenter struct {}, type cOrderManagement struct {}

项目结构

当您执行 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,是项目的对外契约。

支持的路径格式

maltose gen service 命令能够智能地解析 api 目录下的文件路径,以确定生成的 Controller 和 Service 的模块名和版本。支持以下格式:

  • 模块化版本 (推荐): api/<module>/<version>/<file>.go (例如: api/user/v1/user.go)
  • 纯版本: api/<version>/<file>.go (例如: api/v1/user.go)
  • 模块化: api/<module>/<file>.go (例如: api/user/user.go) - 此时版本将默认为 v1

/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.