Files
ocdp-go/docs/development/specification.md
mangomqy c5e51ed069 ocdp v1
2025-11-13 02:54:06 +00:00

8.5 KiB
Raw Blame History

📋 OCDP 开发规范

本文档定义了 OCDP 项目的开发规范和架构要求。

🎯 整体架构 (Full Stack)

1. OpenAPI 驱动开发

采用 OpenAPI 规范来驱动前后端开发,确保 API 契约的一致性。

优势

  • API 设计优先,前后端并行开发
  • 自动生成类型安全的代码
  • 文档和代码永远同步
  • 减少沟通成本

实践

# 1. 设计 API (编辑 backend/docs/openapi.yaml)
# 2. 验证规范
make openapi-validate

# 3. 生成代码
make openapi-gen

# 4. 实现功能

2. Docker Compose 部署

使用 Docker Compose 进行整个应用的部署。新版的 Docker 已经将 Compose 集成到 Docker 里面了,所以使用 docker compose(带空格)而非旧版的 docker-compose(带连字符)。

部署服务

  • PostgreSQL - 数据持久化
  • Redis - 缓存和会话
  • Backend - Go 后端服务
  • Frontend - React 前端应用
  • Nginx - 反向代理(生产环境)

🎨 前端规范 (Frontend)

1. 纯函数渲染

要求:使用纯函数进行组件渲染,避免不必要的副作用。

原则

  • 组件应该是可预测的(相同输入→相同输出)
  • 避免在渲染过程中修改外部状态
  • 使用 useEffect 等 Hook 处理副作用
  • 保持组件的可测试性

示例

// ✅ 好的实践 - 纯函数组件
interface Props {
  name: string;
  count: number;
}

const UserCard = ({ name, count }: Props) => {
  // 纯函数:只依赖 props不修改外部状态
  return (
    <div>
      <h3>{name}</h3>
      <p>Count: {count}</p>
    </div>
  );
};

// ✅ 副作用在 useEffect 中处理
const UserList = () => {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    // 副作用API 调用)在这里处理
    fetchUsers().then(setUsers);
  }, []);
  
  return users.map(user => <UserCard {...user} />);
};

// ❌ 不好的实践 - 在渲染中产生副作用
const BadComponent = () => {
  // 不要在这里调用 API 或修改外部状态
  globalState.count++; // ❌ 副作用
  fetchData(); // ❌ 副作用
  
  return <div>Bad</div>;
};

2. 技术栈

  • 框架: React 18+ (使用 Hooks)
  • 语言: TypeScript 5+
  • 构建工具: Vite
  • 样式: Tailwind CSS
  • 路由: React Router 6+
  • 状态管理: React Context + Hooks
  • API 客户端: 从 OpenAPI 自动生成

🔧 后端规范 (Backend)

1. 六边形架构 (Hexagonal Architecture)

后端采用六边形架构(也称为端口和适配器架构),将业务逻辑与技术实现解耦。

核心目录结构

backend/internal/
├── domain/              # 领域层 - 业务逻辑核心
│   ├── entity/         # 领域实体
│   ├── service/        # 领域服务
│   └── repository/     # 仓库接口(端口)
├── application/        # 应用层 - 用例编排
│   └── usecase/        # 用例实现
└── adapter/            # 适配器层 - 技术实现
    ├── input/          # 输入适配器
    │   └── http/       # HTTP REST API
    └── output/         # 输出适配器
        ├── persistence/
        │   ├── mock/   # Mock 实现
        │   └── postgres/ # PostgreSQL 实现
        ├── oci/        # OCI Registry 客户端
        └── helm/       # Helm SDK 封装

职责划分

  • Domain 层:纯业务逻辑,不依赖任何框架或外部库
  • Application 层:编排 Domain 层的服务,实现具体的用例
  • Adapter 层处理所有技术细节HTTP、数据库、第三方 API

2. Mock Adapter 实现

要求:除了实现 ports 的 adapters 外,还要做 mock。Mock 的是 adapter 的行为反应而非假数据。

Mock 原则

  • 模拟真实 adapter 的行为
  • 可以注入真实数据
  • 可以通过调用接口自行加入数据
  • 使用内存来模拟 adapter 的交互
  • 不是返回固定的假数据

示例

// Mock Repository - 模拟真实的数据库行为
type RegistryRepositoryMock struct {
    registries map[string]*entity.Registry  // 内存存储
    mu         sync.RWMutex
}

func (r *RegistryRepositoryMock) Create(ctx context.Context, registry *entity.Registry) error {
    r.mu.Lock()
    defer r.mu.Unlock()
    
    // 模拟真实行为:检查重复、生成 ID、加密等
    if _, exists := r.registries[registry.ID]; exists {
        return errors.New("registry already exists")
    }
    
    r.registries[registry.ID] = registry
    return nil
}

func (r *RegistryRepositoryMock) GetByID(ctx context.Context, id string) (*entity.Registry, error) {
    r.mu.RLock()
    defer r.mu.RUnlock()
    
    registry, exists := r.registries[id]
    if !exists {
        return nil, errors.New("registry not found")
    }
    
    return registry, nil
}

3. Makefile 支持

要求:采用 Makefile 来支持 mock 启动以及 real 启动。

命令规范

# 开发模式Mock Adapter
run-mock:
	@echo "Starting backend with Mock adapters..."
	MODE=mock go run cmd/api/main.go

# 生产模式Real Adapter
run-real:
	@echo "Starting backend with Real adapters..."
	MODE=real go run cmd/api/main.go

# 运行测试
test:
	go test -v ./...

# 生成代码
generate:
	go generate ./...

使用方式

# 开发模式(无需数据库)
make run-mock

# 生产模式(需要 PostgreSQL
make run-real

4. 技术栈

  • 语言: Go 1.21+
  • Web 框架: Gin (轻量、高性能)
  • ORM: GORM (可选,用于 PostgreSQL adapter)
  • OCI 客户端: ORAS Go SDK v2
  • Helm 客户端: Helm SDK v3
  • K8s 客户端: client-go

📐 架构原则

1. 依赖方向

Adapter → Application → Domain
  (技术)      (编排)      (业务)
  • Domain 层不依赖任何外部库(除了标准库)
  • Application 层依赖 Domain 层
  • Adapter 层依赖 Application 和 Domain 层

2. 端口和适配器

端口Port:接口定义,在 Domain 层

// domain/repository/registry_repository.go
type RegistryRepository interface {
    Create(ctx context.Context, registry *entity.Registry) error
    GetByID(ctx context.Context, id string) (*entity.Registry, error)
    List(ctx context.Context) ([]*entity.Registry, error)
}

适配器Adapter:接口实现,在 Adapter 层

// adapter/output/persistence/mock/registry_repository_mock.go
type RegistryRepositoryMock struct {
    // Mock 实现
}

// adapter/output/persistence/postgres/registry_repository_postgres.go
type RegistryRepositoryPostgres struct {
    // PostgreSQL 实现
}

3. 依赖注入

使用构造函数注入依赖:

// 创建 Mock 模式的应用
func NewMockApp() *App {
    // 创建 Mock Repository
    registryRepo := mock.NewRegistryRepositoryMock()
    
    // 创建 Service注入 Repository
    registryService := service.NewRegistryService(registryRepo)
    
    // 创建 Handler注入 Service
    registryHandler := handler.NewRegistryHandler(registryService)
    
    return &App{
        RegistryHandler: registryHandler,
    }
}

// 创建 Production 模式的应用
func NewProductionApp(db *gorm.DB) *App {
    // 创建 PostgreSQL Repository
    registryRepo := postgres.NewRegistryRepositoryPostgres(db)
    
    // ... 其他相同
}

🔄 开发工作流

1. 功能开发流程

# 1. 设计 API
vim backend/docs/openapi.yaml

# 2. 生成代码
make openapi-gen

# 3. 实现 Domain 层
vim backend/internal/domain/service/xxx_service.go

# 4. 实现 Mock Adapter
vim backend/internal/adapter/output/persistence/mock/xxx_mock.go

# 5. 实现 Handler
vim backend/internal/adapter/input/http/handler/xxx_handler.go

# 6. 启动测试
make run-mock

# 7. 实现前端
vim frontend/src/features/xxx/pages/XxxPage.tsx

# 8. 集成测试
make dev

# 9. 实现 Production Adapter
vim backend/internal/adapter/output/persistence/postgres/xxx_postgres.go

# 10. 部署测试
docker compose up

2. 测试策略

  • 单元测试Domain 层和 Service 层(使用 Mock Repository
  • 集成测试:使用 Mock Adapter 测试完整流程
  • E2E 测试:使用真实 Adapter 测试生产环境

📚 参考文档


版本: 1.0
最后更新: 2025-11-07