项目架构

Goat 将生成的 foundation 与应用扩展结合起来。在 goatcms.com 中,事实来源是定义和模板 herd/;goatapp/ 由生成器生成,而应用实现属于 child/。请勿手动编辑 goatapp/,也不要将其中的代码复制到 child。

结构与依赖关系

  • herd/_model.goat:实体、角色、模块和约束。
  • herd/_modules.goat:已精确锁定的 goatcore 和 goatmodule 版本;本地 checkout 位于 .goat/modules/。
  • herd/gen/templates/:项目模板,包括共享服务容器。
  • child/app/:布局、bootstrap、路由、命令、产品行为和 SQL 迁移。
  • child/static/raw/:由生成的 child/static/main.go 嵌入的项目资源。
  • child/web/:位于 /child/ 下的独立 Angular 应用;foundation 提供 /app/ 和 elements。

服务组合

入口点创建 services.GenInstances,并将布局、邮件器和存储传递给 appcore.RunApp(instances, bootstrap.Body)。单个容器对所有服务执行 Init,随后执行其 AfterInit。容器槽位由生成器生成;实现保留在 child 中。产品行为属于特定存储实例,不使用全局状态。

Bootstrap 注册路由、命令和 serve 操作。只有该操作会启动 worker,且每个实例只启动一次。应用关闭时会取消 worker,并在关闭数据库之前等待其结束。CLI 命令和迁移不会启动后台工作。

页面和迁移

页面通过 layoutcontract.Document 传递内容,并通过 PageMeta 传递元数据;renderer 提供统一 shell 和 SEO。文档嵌入 BaseDocument,它提供可选槽位。Child 布局组合公共 GOAT 主题和应用样式,但不复制 foundation 实现。

Foundation 和 child 是通过 go.mod 中的 replace 连接的独立 Go 模块。请分别测试两个模块。迁移名称及其相对路径不可变:模型迁移属于 0000_goatmigrations/,应用迁移属于 0001_childigrations/。每个 child 文件都在事务中执行。

应用模型

文件:

herd/_model.goat

描述应用领域结构。

其中可以包括:

  • 实体,
  • 字段,
  • 数据类型,
  • 关系,
  • 角色,
  • 共享属性,
  • 供后端和前端使用的信息。

模型应主要描述应用的意图和结构,而不是每个文件的实现细节。

基于该模型,Goat 可以生成系统多个层的一致组件,例如:

  • 模型,
  • DAO 和仓储,
  • DTO,
  • API 端点,
  • CRUD 命令,
  • SQL 迁移,
  • 表单,
  • 表格,
  • 管理视图,
  • 用户界面元素。

最大的收益不只是创建文件,而是能够保持同一实体多种表示形式之间的一致性。

模型变更可以同时映射到后端、数据库和前端。

代码生成器

生成器组合多个来源:

应用模型
      +
模板
      +
源代码
      ↓
应用代码

结果是位于项目根目录中的完整应用。

不过,生成的代码并不被视为只读代码。

你可以:

  • 编辑,
  • 扩展,
  • 重构,
  • 适配特定需求,
  • 补充自定义逻辑,
  • 与项目的其余更改一起提交。

Goat 应在生成能够带来实际收益的地方实现自动化。但它并不要求后续的每项修改都必须只能由模型或模板完成。

编辑生成的代码

应用程序更改属于 child/,生成器模板属于 herd/gen/templates/。foundation 更改应通过生成器引入,不要手动编辑 goatapp/。

重新生成应用程序

更改模型后,你可以运行:

goat re

生成器会分析当前的应用程序定义,并引入由此产生的更改。

典型工作流可能如下:

修改模型
      ↓
goat re
      ↓
更新代码
      ↓
手动完善
      ↓
git diff
      ↓
测试

重新生成后,始终建议检查:

git diff

这样你可以准确看到生成器修改了项目的哪些部分,并决定哪些更改应包含在提交中。

Git 仍然是使用 Goat 时的重要组成部分:生成器会提出具体的代码更改,但最终范围由开发者控制。

模型、模板还是直接编辑?

Goat 不强制规定唯一的修改方式。

你可以根据任务的性质,在不同层级上进行工作。

修改模型

如果更改涉及领域结构,请从以下内容开始:

herd/_model.goat

示例:

  • 添加实体,
  • 添加字段,
  • 修改数据类型,
  • 修改关系,
  • 添加角色,
  • 修改共享属性。

在这种情况下,模型是最佳位置,因为它使生成器能够保持应用程序不同层之间的一致性。

修改模板

如果你想改变整类相似元素的生成方式,合适的位置可能是:

herd/gen/templates/

示例:

  • 修改所有端点的结构,
  • 添加通用注解,
  • 修改表单的生成方式,
  • 修改默认 CRUD 组件,
  • 在生成的代码中引入新的约定。

此时,模型决定应生成什么,而模板定义默认实现应当如何呈现。

直接编辑代码

应用程序更改属于 child/,生成器模板属于 herd/gen/templates/。foundation 更改应通过生成器引入,不要手动编辑 goatapp/。

应用程序层

child/cmd/goatcms/main.go 启动应用程序核心。

核心负责的内容包括:

  • 加载配置,
  • 初始化依赖项,
  • 注册服务,
  • 准备应用程序基础设施,
  • 处理命令行终端。

因此,不同操作可以使用相同的应用程序环境。

示例命令:

serve
db:migrate
crud:doc:persist

无需实现为彼此独立的工具。

它们可以共享:

  • 配置,
  • 数据库访问,
  • 服务,
  • 日志记录,
  • 授权机制,
  • 项目的其余基础设施。

这既有助于应用程序开发,也有助于创建管理工具和自动化流程。

领域模型与生成的代码

定义在以下位置的实体:

herd/_model.goat

可用于生成应用程序不同层的元素。

示例流程:

实体
  ↓
后端模型
  ↓
DAO / 仓库
  ↓
DTO
  ↓
API
  ↓
表单
  ↓
列表视图

这可以减少对同一数据结构进行多次描述的需求。

无需手动同步应用程序的多个层级,基础信息可以来自共享定义。

但这并不意味着所有层级都必须保持完全一致,或完全由生成工具生成。每个生成的元素都可以进一步根据应用程序需求进行调整。

.goat 脚本

herd/ 目录还充当项目的自动化层。

.goat 脚本可以描述完整的开发流程,包括:

  • 启动环境,
  • 生成应用程序,
  • 迁移,
  • 初始化数据库,
  • 加载 fixture 数据,
  • 构建前端,
  • 同步翻译,
  • 运行测试。

例如:

bash child/dev.sh

可以启动开始项目开发所需的完整工作流。

因此,构建、测试和运行应用程序的方式仍是仓库的一部分。

无需为每个操作系统或开发工作站维护单独的手动说明集。

脚本执行隔离

Goat 的一个重要设计目标,是限制所执行脚本对宿主系统的影响。

构建和测试期间使用的工具可以在基于 Docker 的隔离环境中运行。

这包括:

  • Go,
  • Node.js,
  • npm,
  • PostgreSQL,
  • Playwright,
  • 构建工具,
  • 项目所需的额外依赖项。

这可以减少对开发人员计算机本地配置的依赖,并在团队成员与 CI 之间统一环境。

隔离还限制了可供执行脚本使用的资源范围。

在典型情况下,它们可以处理当前项目,而无需自由访问宿主机上的其他数据。

这在执行来自外部依赖项的代码时尤为重要,例如由 npm install 运行的安装脚本。

架构与 AI 协作

Goat 架构的设计也旨在与 AI 工具良好协作。

在传统项目中,即使是很小的需求变更,也可能意味着需要分析多个文件:

模型
DTO
DAO
API
迁移
表单
视图
前端

在基于 Goat 的项目中,部分此类变更可以在更高层级进行描述:

需求变更
      ↓
模型变更
      ↓
goat re
      ↓
更新多个层级

此时,AI 可以在更小的上下文中工作,并专注于变更意图,而不必分析完整的生成实现。

这可以:

  • 减少传递给模型的代码量,
  • 降低分析成本,
  • 缩短理解变更所需的时间,
  • 减少被修改的文件数量,
  • 降低层级之间不一致的风险。

同时,Goat 不强制只能在高层级上工作。

如果变更涉及某一处具体代码片段,AI 或开发人员可以直接修改它。

这提供了两个互补的工作层级:

高层级 —— 修改模型、配置或模板,然后重新生成,

低层级 —— 直接编辑应用程序代码。

选择哪个层级取决于哪种方式更简单、更清晰且维护成本更低。

典型工作流

对模型进行修改时,流程可能如下:

# 修改应用模型
vim herd/_model.goat

# 生成由此产生的变更
goat re

# 检查结果
git diff

# 如有需要,手动完善代码

# 运行测试
goat run:script --path=herd/test.goat

# 检查最终变更范围
git status

如果是纯业务或实现层面的变更,可能不需要重新生成。你可以直接修改相应的代码片段并运行测试。

最重要的原则

应用层变更属于 child/,生成器模板属于 herd/gen/templates/。foundation 层变更应通过生成器完成,不要手动编辑 goatapp/。