coze-studio学习笔记(二)

叠甲

本文由 Cursor AI 辅助生成,正在学习 go 中,同时这也是我开始阅读的第一份开源项目的源代码,有很多设计思想值得学习,不懂的地方有很多,如有错误欢迎指正,共同努力。

前情提要

成功用 docker compose 启动了项目,了解数据库表设计,新建了 go 工程的目录。

Day2

今日目标:从 0 学习并搭建 coze 同款 go 后端框架的用户接口层,并且成功启动项目。

DDD思想的四层架构分析

graph TD A["用户接口层
(User Interface Layer)
backend/api/"] --> B["应用服务层
(Application Service Layer)
backend/application/"] B --> C["领域层
(Domain Layer)
backend/domain/"] C --> D["基础设施层
(Infrastructure Layer)
backend/infra/"] A1["API Handler"] --> A A2["Router"] --> A A3["Middleware"] --> A A4["Model/DTO"] --> A B1["Application Services"] --> B B2["Event Bus"] --> B B3["Service Orchestration"] --> B C1["Domain Services"] --> C C2["Domain Entities"] --> C C3["Repository Interfaces"] --> C C4["Domain Events"] --> C D1["Repository Implementations"] --> D D2["External Service Clients"] --> D D3["Database Access"] --> D D4["Cache/Storage"] --> D

1. 用户接口层 (User Interface Layer) - backend/api/

职责: 处理HTTP请求,数据传输对象(DTO)转换,路由管理主要组件:

  • Handler: 处理具体的HTTP请求逻辑
  • Router: 路由配置和注册
  • Middleware: 认证、日志、CORS等横切关注点
  • Model: API请求/响应的数据模型

2.应用服务层 (Application Service Layer) - backend/application/

职责: 业务流程编排,跨领域协调,事务管理主要组件:

  • Application Services: 具体的应用服务实现
  • Service Components: 服务依赖组件
  • Event Bus: 事件总线处理

分层设计:

  • Basic Services: 仅依赖基础设施的基础服务
  • Primary Services: 依赖基础服务的主要业务服务
  • Complex Services: 依赖主要服务的复杂业务服务

3.领域层 (Domain Layer) - backend/domain/

职责: 核心业务逻辑,领域实体,业务规则领域划分:

  • user/ - 用户管理领域
  • workflow/ - 工作流领域
  • conversation/ - 对话领域
  • knowledge/ - 知识库领域
  • plugin/ - 插件领域
  • agent/ - 智能体领域
  • 等多个业务领域...

每个领域的标准结构:

text 复制代码
domain/user/
├── entity/          # 领域实体
├── service/         # 领域服务
├── repository/      # 仓储接口
└── internal/        # 内部实现

4.基础设施层 (Infrastructure Layer) - backend/infra/

职责: 技术实现,外部系统集成,数据持久化主要组件:

  • Contract: 基础设施接口定义
  • Impl: 具体技术实现

技术栈:

  • 数据库: MySQL (使用GORM)
  • 缓存: Redis
  • 存储: TOS/S3/MinIO
  • 搜索: Elasticsearch
  • 消息队列: Event Bus
  • 模型管理: Model Manager

special. 跨领域协作 - backend/crossdomain/

昨天粗略了解了一下 DDD 的设计思想,看到有一点原则就是禁止跨领域调用,领域的依赖都是自上而下的,但是项目中却有跨领域的实现,问了一下 Cursor,他是这么回答的:

graph TD subgraph "业务场景:工作流执行" WF["工作流节点执行"] WF --> P["调用插件工具"] WF --> K["检索知识库"] WF --> D["查询数据库"] WF --> A["调用智能体"] end subgraph "领域边界问题" WFD["Workflow Domain
工作流领域"] PD["Plugin Domain
插件领域"] KD["Knowledge Domain
知识库领域"] DD["Database Domain
数据库领域"] AD["Agent Domain
智能体领域"] WFD -.->|"直接依赖?
违反DDD原则"| PD WFD -.->|"直接依赖?
违反DDD原则"| KD WFD -.->|"直接依赖?
违反DDD原则"| DD WFD -.->|"直接依赖?
违反DDD原则"| AD end subgraph "跨领域解决方案" CC["Cross-Domain Contract
跨领域契约"] CI["Cross-Domain Implementation
跨领域实现"] WFD --> CC CC --> CI CI --> PD CI --> KD CI --> DD CI --> AD end
🤔 为什么会有跨领域的情况?
1️⃣ 业务复杂性驱动

在Coze Studio这样的AI平台中,业务场景天然具有跨领域特性:典型场景:

  • 工作流执行:一个工作流可能需要调用插件、检索知识库、查询数据库、调用智能体
  • 智能体对话:智能体需要使用插件工具、访问知识库、记录对话历史
  • 应用构建:应用可能集成多个领域的能力
2️⃣ DDD原则约束

严格的DDD原则要求:

  • 领域独立性:每个领域应该独立,不直接依赖其他领域
  • 防腐层:避免领域间的直接耦合
  • 单一职责:每个领域只关注自己的核心业务

问题:如果工作流领域直接依赖插件领域,会导致:

go 复制代码
// ❌ 违反DDD原则的做法
import "github.com/coze-dev/coze-studio/backend/domain/plugin/service"

type WorkflowService struct {
    pluginService plugin.PluginService // 直接依赖其他领域
}
3️⃣ 跨领域解决方案的设计

项目采用了契约-实现分离的跨领域模式:

  1. 契约层 (Contract)

定义跨领域接口,不包含具体实现:

go 复制代码
// cross_plugin.go
type PluginService interface {
	MGetVersionPlugins(ctx context.Context, versionPlugins []model.VersionPlugin) (plugins []*model.PluginInfo, err error)
	ExecuteTool(ctx context.Context, req *model.ExecuteToolRequest, opts ...model.ExecuteToolOpt) (resp *model.ExecuteToolResponse, err error)
	// ... 其他方法
}

var defaultSVC PluginService // 全局单例
  1. 实现层 (Implementation)

桥接具体的领域服务:

go 复制代码
// plugin.go
type impl struct {
	DomainSVC plugin.PluginService // 包装真实的领域服务
}

func InitDomainService(c plugin.PluginService) crossplugin.PluginService {
	defaultSVC = &impl{
		DomainSVC: c,
	}
	return defaultSVC
}
4️⃣ 架构优势

这种跨领域设计带来了多个优势:

  1. 🔒 解耦合
  • 工作流领域不直接依赖插件领域
  • 通过接口契约进行交互
  • 便于单元测试和模拟
  1. 🔄 可扩展
  • 新增领域时,只需定义新的跨领域契约
  • 现有领域无需修改
  1. 🎯 职责清晰
  • 每个领域专注自己的核心业务
  • 跨领域逻辑统一管理
  1. 🧪 可测试
  • 可以轻松模拟跨领域依赖
  • 单元测试更加独立
总结

跨领域的存在是因为:

  1. 业务需求:AI平台的复杂业务场景天然需要多领域协作
  1. 架构原则:严格遵循DDD原则,避免领域间直接耦合
  1. 工程实践:通过契约-实现分离,实现了既满足业务需求又保持架构清洁的解决方案

项目框架分析

  1. web框架:cloudwego-hertz
  2. rpc框架:grpc、thrift
  3. ai 编排框架:cloudwego-eino
  4. orm 框架:gorm

搭建hertz框架

text 复制代码
cloudwego-hertz 官网:https://www.cloudwego.io/zh/docs/hertz/overview/
  1. 初始化 go 环境,go 版本选择 1.24 跟 coze-studio 保持一致
  2. 根据官网文档安装hertz依赖
  3. hertz 框架提供了代码生成的能力,coze-studio中的 idl 目录就是当前项目的 thrift 文件

由于以前没有接触过 IDL,做个笔记记录

1. 什么是 IDL

IDL(Interface Definition Language)是一种接口定义语言,用于:

  • 定义服务接口(方法签名)
  • 定义数据结构(请求/响应格式)
  • 支持跨语言的服务通信
  • 实现代码自动生成

2.Thrift IDL

这个项目使用的是 Apache Thrift IDL

thrift 复制代码
// 定义数据结构
struct Base {
    1: string LogID = "",
    2: string Caller = "",
    3: string Addr = "",
    // ...
}

// 定义响应结构
struct BaseResp {
    1: string StatusMessage = "",
    2: i32    StatusCode = 0,
    // ...
}

//服务定义
service MessageService {
    message.GetMessageListResponse GetMessageList(1: message.GetMessageListRequest request)
    message.DeleteMessageResponse DeleteMessage(1: message.DeleteMessageRequest request)
    // ...
}

3. 项目中的 IDL 组织结构

text 复制代码
idl/
├── api.thrift              # 主入口文件,聚合所有服务
├── base.thrift             # 基础数据结构
├── conversation/           # 对话相关服务
│   ├── message_service.thrift
│   ├── conversation_service.thrift
│   └── ...
├── intelligence/           # AI 智能体服务
├── plugin/                # 插件服务
├── upload/                # 文件上传服务
└── ...                    # 其他业务模块

DDD-用户接口层:使用 hz 进行代码生成

经过 Cursor 的分析,参考 hertz 的文档,以及 coze-studio 目录下的.hz 文件,可以自己写出这段生成代码

shell 复制代码
hz new --handler_dir=api/handler --model_dir=api/model --router_dir=api/router -idl idl/api.thrift -module github.com/<your-name>/coze-studio --enable_extends true

这里要注意,一定要开启 enable_extends,不然只会根据 api.thrift 中的 service 生成空壳 router 文件,因为 idl/api.thrift 文件采用了service IntelligenceService extends intelligence.IntelligenceService {}extends 的写法,在这里踩坑了好久,最后在 hz 的文档中看到了 enable_extends 的属性,开启后成功解决。

生成的文件目录结构:

text 复制代码
.
├── handler
│   └── coze
│       ├── agent_run_service.go
│       ├── bot_open_api_service.go
│       ├── conversation_service.go
│       ├── database_service.go
│       ├── developer_api_service.go
│       ├── intelligence_service.go
│       ├── knowledge_service.go
│       ├── memory_service.go
│       ├── message_service.go
│       ├── open_apiauth_service.go
│       ├── passport_service.go
│       ├── playground_service.go
│       ├── plugin_develop_service.go
│       ├── public_product_service.go
│       ├── resource_service.go
│       ├── upload_service.go
│       └── workflow_service.go
├── model
│   ├── base
│   │   └── base.go
│   ├── common
│   │   └── common.go
│   ├── conversation
│   │   ├── agentrun
│   │   │   └── agentrun_service.go
│   │   ├── common
│   │   │   └── common.go
│   │   ├── conversation
│   │   │   ├── conversation.go
│   │   │   └── conversation_service.go
│   │   ├── message
│   │   │   ├── message.go
│   │   │   └── message_service.go
│   │   └── run
│   │       └── run.go
│   ├── coze
│   │   └── api.go
│   ├── database
│   │   └── database.go
│   ├── file
│   │   └── upload
│   │       └── upload.go
│   ├── flow
│   │   ├── dataengine
│   │   │   └── dataset
│   │   │       ├── common.go
│   │   │       ├── dataset.go
│   │   │       ├── document.go
│   │   │       ├── flow_dataengine_dataset.go
│   │   │       ├── review.go
│   │   │       └── slice.go
│   │   ├── devops
│   │   │   └── debugger
│   │   │       ├── coze
│   │   │       │   └── flow.devops.debugger.coze.go
│   │   │       └── domain
│   │   │           ├── infra
│   │   │           │   └── infra.go
│   │   │           └── testcase
│   │   │               └── testcase.go
│   │   └── marketplace
│   │       ├── marketplace_common
│   │       │   └── marketplace_common.go
│   │       ├── product_common
│   │       │   └── product_common.go
│   │       └── product_public_api
│   │           └── public_api.go
│   ├── intelligence
│   │   ├── common
│   │   │   ├── common_struct.go
│   │   │   └── intelligence_common_struct.go
│   │   ├── intelligence.go
│   │   └── search.go
│   ├── knowledge
│   │   └── document
│   │       └── kdocument.go
│   ├── kvmemory
│   │   └── kvmemory.go
│   ├── ocean
│   │   └── cloud
│   │       ├── bot_common
│   │       │   └── bot_common.go
│   │       ├── bot_open_api
│   │       │   └── bot_open_api.go
│   │       ├── developer_api
│   │       │   └── developer_api.go
│   │       ├── memory
│   │       │   └── ocean_cloud_memory.go
│   │       ├── playground
│   │       │   ├── playground.go
│   │       │   ├── prompt_resource.go
│   │       │   └── shortcut_command.go
│   │       ├── plugin_develop
│   │       │   └── plugin_develop.go
│   │       └── workflow
│   │           ├── ocean_cloud_workflow.go
│   │           ├── trace.go
│   │           └── workflow.go
│   ├── passport
│   │   └── passport.go
│   ├── permission
│   │   └── openapiauth
│   │       ├── openapiauth.go
│   │       └── openapiauth_service.go
│   ├── plugin_develop_common
│   │   └── plugin_develop_common.go
│   ├── project
│   │   └── project.go
│   ├── project_memory
│   │   └── project_memory.go
│   ├── publish
│   │   └── publish.go
│   ├── resource
│   │   ├── common
│   │   │   └── resource_common.go
│   │   └── resource.go
│   ├── table
│   │   └── table.go
│   ├── task
│   │   └── task.go
│   └── task_struct
│       └── task_struct.go
└── router
    ├── coze
    │   ├── api.go
    │   └── middleware.go
    └── register.go

57 directories, 73 files

这样就很方便的帮我们生成了 DDD 四层架构的第一层,用户接口层。

到这一步我们的项目代码其实就可以运行起来了,替换一下main.go中的默认注册路由,替换成/api/router/register.go 中的GeneratedRegister

go 复制代码
// Code generated by hertz generator.

package main

import (
    "github.com/cloudwego/hertz/pkg/app/server"
    "github.com/liliagrisina/coze-studio/api/router"
)

func main() {
    h := server.Default()
    router.GeneratedRegister(h)
    h.Spin()
}

执行go run main.go成功启动我的第一个 hertz 框架项目。

相关推荐
元闰子1 分钟前
分离还是统一,这是个问题
后端·面试·架构
寻月隐君10 分钟前
Rust Scoped Threads 实战:更安全、更简洁的并发编程
后端·rust·github
爷_1 小时前
手把手教程:用腾讯云新平台搞定专属开发环境,永久免费薅羊毛!
前端·后端·架构
山间小僧1 小时前
「查漏补缺」ZGC相关内容整理
java·jvm·后端
程序视点2 小时前
FDM下载神器:免费多线程下载工具,速度90+M/S,完美替代1DM!
windows·后端
happycode7002 小时前
数据库迁移实践
后端
Livingbody2 小时前
【ERNIEKit】基于ERNIE4.5-0.3B大模型微调的心理咨询师大模型全流程
后端
长安不见3 小时前
图与循环依赖、死锁(一) 为何我的循环依赖时好时坏?
后端
codervibe3 小时前
Spring Boot 项目中泛型统一响应封装的设计与实现
后端