项目架构
应用层、源代码目录和生成代码的概览。
项目架构
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/。