ocdp v1
This commit is contained in:
458
docs/development/go-vs-typescript.md
Normal file
458
docs/development/go-vs-typescript.md
Normal file
@ -0,0 +1,458 @@
|
||||
# Go vs TypeScript + class-transformer 对比
|
||||
|
||||
## 命名约定和自动转换实现
|
||||
|
||||
本文档展示 Go 和 TypeScript 如何实现类似的自动类型转换机制。
|
||||
|
||||
---
|
||||
|
||||
## 📋 命名约定总结
|
||||
|
||||
### OpenAPI 规范
|
||||
|
||||
| 元素 | 命名约定 | 示例 |
|
||||
|------|---------|------|
|
||||
| Fixed Fields | `camelCase` | `operationId`, `requestBody` |
|
||||
| Schema 名称 | `PascalCase` | `ClusterResponse`, `CreateClusterRequest` |
|
||||
| Schema 属性 | `snake_case` | `has_ca_data`, `created_at`, `cluster_id` |
|
||||
|
||||
### Backend Go
|
||||
|
||||
| 元素 | 命名约定 | 示例 |
|
||||
|------|---------|------|
|
||||
| 导出变量/字段 | `PascalCase` | `HasCAData`, `CreatedAt` |
|
||||
| 非导出变量 | `camelCase` | `hasCAData`, `createdAt` |
|
||||
| 类型名 | `PascalCase` | `ClusterResponse`, `CreateClusterRequest` |
|
||||
| JSON 标签 | `snake_case` | `json:"has_ca_data"`, `json:"created_at"` |
|
||||
|
||||
### Frontend TypeScript
|
||||
|
||||
| 元素 | 命名约定 | 示例 |
|
||||
|------|---------|------|
|
||||
| 变量 | `camelCase` | `hasCAData`, `createdAt` |
|
||||
| 类型名 | `PascalCase` | `Cluster`, `CreateClusterRequest` |
|
||||
| JSON | `snake_case` | `has_ca_data`, `created_at` |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 自动转换对比
|
||||
|
||||
### Go 的实现
|
||||
|
||||
```go
|
||||
// backend/internal/adapter/input/http/dto/cluster_dto.go
|
||||
|
||||
package dto
|
||||
|
||||
// 类型定义:PascalCase
|
||||
type ClusterResponse struct {
|
||||
// 导出字段:PascalCase
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Host string `json:"host"`
|
||||
|
||||
// 字段名:PascalCase, JSON:snake_case
|
||||
HasCAData bool `json:"has_ca_data"` // ← struct tag
|
||||
HasCertData bool `json:"has_cert_data"` // ← struct tag
|
||||
HasKeyData bool `json:"has_key_data"` // ← struct tag
|
||||
|
||||
CAData string `json:"ca_data"` // ← struct tag
|
||||
CertData string `json:"cert_data"` // ← struct tag
|
||||
KeyData string `json:"key_data"` // ← struct tag
|
||||
|
||||
CreatedAt string `json:"created_at"` // ← struct tag
|
||||
UpdatedAt string `json:"updated_at"` // ← struct tag
|
||||
}
|
||||
|
||||
// 使用 - Go 自动转换
|
||||
func GetCluster() ClusterResponse {
|
||||
cluster := ClusterResponse{
|
||||
ID: "cluster-123",
|
||||
Name: "Production",
|
||||
HasCAData: true, // 内部使用 PascalCase
|
||||
CAData: "••••••••",
|
||||
CreatedAt: "2025-11-10",
|
||||
}
|
||||
|
||||
// json.Marshal 自动转换为 snake_case
|
||||
// {"id":"cluster-123","has_ca_data":true,"ca_data":"••••••••","created_at":"2025-11-10"}
|
||||
return cluster
|
||||
}
|
||||
```
|
||||
|
||||
### TypeScript + class-transformer 的实现
|
||||
|
||||
```typescript
|
||||
// frontend/src/api/models/cluster.model.ts
|
||||
|
||||
import { Expose } from 'class-transformer';
|
||||
|
||||
// 类型定义:PascalCase
|
||||
export class Cluster {
|
||||
@Expose()
|
||||
id!: string;
|
||||
|
||||
@Expose()
|
||||
name!: string;
|
||||
|
||||
@Expose()
|
||||
host!: string;
|
||||
|
||||
// 字段名:camelCase, JSON:snake_case
|
||||
@Expose({ name: 'has_ca_data' }) // ← @Expose decorator (类似 struct tag)
|
||||
hasCAData?: boolean;
|
||||
|
||||
@Expose({ name: 'has_cert_data' }) // ← @Expose decorator
|
||||
hasCertData?: boolean;
|
||||
|
||||
@Expose({ name: 'has_key_data' }) // ← @Expose decorator
|
||||
hasKeyData?: boolean;
|
||||
|
||||
@Expose({ name: 'ca_data' }) // ← @Expose decorator
|
||||
caData?: string;
|
||||
|
||||
@Expose({ name: 'cert_data' }) // ← @Expose decorator
|
||||
certData?: string;
|
||||
|
||||
@Expose({ name: 'key_data' }) // ← @Expose decorator
|
||||
keyData?: string;
|
||||
|
||||
@Expose({ name: 'created_at' }) // ← @Expose decorator
|
||||
createdAt!: string;
|
||||
|
||||
@Expose({ name: 'updated_at' }) // ← @Expose decorator
|
||||
updatedAt!: string;
|
||||
}
|
||||
|
||||
// 使用 - TypeScript + class-transformer 自动转换
|
||||
import { fromJson, toJson } from '@/api/serializer';
|
||||
|
||||
function getCluster(): Cluster {
|
||||
// JSON → 类实例 (snake_case → camelCase)
|
||||
const apiResponse = {
|
||||
id: "cluster-123",
|
||||
name: "Production",
|
||||
has_ca_data: true, // JSON: snake_case
|
||||
ca_data: "••••••••",
|
||||
created_at: "2025-11-10",
|
||||
};
|
||||
|
||||
const cluster = fromJson(Cluster, apiResponse);
|
||||
|
||||
// 内部使用 camelCase
|
||||
console.log(cluster.hasCAData); // true
|
||||
console.log(cluster.caData); // "••••••••"
|
||||
console.log(cluster.createdAt); // "2025-11-10"
|
||||
|
||||
return cluster;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 详细对比
|
||||
|
||||
### 1. 结构定义
|
||||
|
||||
#### Go
|
||||
```go
|
||||
type ClusterResponse struct {
|
||||
HasCAData bool `json:"has_ca_data"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
```
|
||||
|
||||
#### TypeScript + class-transformer
|
||||
```typescript
|
||||
class Cluster {
|
||||
@Expose({ name: 'has_ca_data' })
|
||||
hasCAData?: boolean;
|
||||
|
||||
@Expose({ name: 'created_at' })
|
||||
createdAt!: string;
|
||||
}
|
||||
```
|
||||
|
||||
**对应关系**:
|
||||
- Go 的 `struct tag` ↔ TypeScript 的 `@Expose` 装饰器
|
||||
- Go 的 `json:"field_name"` ↔ TypeScript 的 `{ name: 'field_name' }`
|
||||
|
||||
---
|
||||
|
||||
### 2. JSON 序列化(结构体/类 → JSON)
|
||||
|
||||
#### Go
|
||||
```go
|
||||
cluster := ClusterResponse{
|
||||
HasCAData: true, // PascalCase
|
||||
CreatedAt: "2025-11-10",
|
||||
}
|
||||
|
||||
jsonBytes, _ := json.Marshal(cluster)
|
||||
// 自动转换为: {"has_ca_data":true,"created_at":"2025-11-10"}
|
||||
```
|
||||
|
||||
#### TypeScript + class-transformer
|
||||
```typescript
|
||||
const cluster = new Cluster();
|
||||
cluster.hasCAData = true; // camelCase
|
||||
cluster.createdAt = "2025-11-10";
|
||||
|
||||
const json = toJson(cluster);
|
||||
// 自动转换为: {has_ca_data: true, created_at: "2025-11-10"}
|
||||
```
|
||||
|
||||
**对应关系**:
|
||||
- Go 的 `json.Marshal()` ↔ TypeScript 的 `toJson()`
|
||||
- 都实现了:内部字段名 → JSON snake_case
|
||||
|
||||
---
|
||||
|
||||
### 3. JSON 反序列化(JSON → 结构体/类)
|
||||
|
||||
#### Go
|
||||
```go
|
||||
jsonStr := `{"has_ca_data":true,"created_at":"2025-11-10"}`
|
||||
var cluster ClusterResponse
|
||||
json.Unmarshal([]byte(jsonStr), &cluster)
|
||||
|
||||
// 自动映射到 PascalCase 字段
|
||||
fmt.Println(cluster.HasCAData) // true
|
||||
fmt.Println(cluster.CreatedAt) // "2025-11-10"
|
||||
```
|
||||
|
||||
#### TypeScript + class-transformer
|
||||
```typescript
|
||||
const apiResponse = {
|
||||
has_ca_data: true,
|
||||
created_at: "2025-11-10"
|
||||
};
|
||||
|
||||
const cluster = fromJson(Cluster, apiResponse);
|
||||
|
||||
// 自动映射到 camelCase 字段
|
||||
console.log(cluster.hasCAData); // true
|
||||
console.log(cluster.createdAt); // "2025-11-10"
|
||||
```
|
||||
|
||||
**对应关系**:
|
||||
- Go 的 `json.Unmarshal()` ↔ TypeScript 的 `fromJson()`
|
||||
- 都实现了:JSON snake_case → 内部字段名
|
||||
|
||||
---
|
||||
|
||||
## 📊 完整数据流转示例
|
||||
|
||||
### Scenario: 创建集群
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 1. Frontend 组件 (TypeScript camelCase) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ const request = new CreateClusterRequest(); │
|
||||
│ request.name = "Production"; │
|
||||
│ request.caData = "LS0t..."; // camelCase │
|
||||
│ request.certData = "LS0t..."; │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓ toJson(request)
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 2. HTTP Request Body (JSON snake_case) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ { │
|
||||
│ "name": "Production", │
|
||||
│ "ca_data": "LS0t...", // snake_case │
|
||||
│ "cert_data": "LS0t..." │
|
||||
│ } │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓ HTTP POST
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 3. Backend Go (PascalCase struct) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ type CreateClusterRequest struct { │
|
||||
│ Name string `json:"name"` │
|
||||
│ CAData string `json:"ca_data"` // PascalCase │
|
||||
│ CertData string `json:"cert_data"` │
|
||||
│ } │
|
||||
│ │
|
||||
│ // json.Unmarshal 自动映射 │
|
||||
│ var req CreateClusterRequest │
|
||||
│ json.Unmarshal(body, &req) │
|
||||
│ // req.CAData = "LS0t..." // 自动转换! │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓ 处理业务逻辑
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 4. Backend Response (JSON snake_case) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ { │
|
||||
│ "id": "cluster-123", │
|
||||
│ "name": "Production", │
|
||||
│ "has_ca_data": true, // snake_case │
|
||||
│ "created_at": "2025-11-10T08:00:00Z" │
|
||||
│ } │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓ fromJson(Cluster, response)
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 5. Frontend 使用 (TypeScript camelCase) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ const cluster: Cluster = await createCluster(request); │
|
||||
│ │
|
||||
│ // 使用 camelCase │
|
||||
│ console.log(cluster.hasCAData); // true │
|
||||
│ console.log(cluster.createdAt); // "2025-11-10T08:00:00Z" │
|
||||
│ │
|
||||
│ // 在 React 组件中 │
|
||||
│ {cluster.hasCAData && <Badge>Has CA</Badge>} │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 关键相似点
|
||||
|
||||
| 特性 | Go | TypeScript + class-transformer |
|
||||
|------|----|---------------------------------|
|
||||
| **元数据标记** | `struct tags` | `@Expose` 装饰器 |
|
||||
| **内部命名** | `PascalCase` | `camelCase` |
|
||||
| **JSON 命名** | `snake_case` | `snake_case` |
|
||||
| **序列化** | `json.Marshal()` | `toJson()` |
|
||||
| **反序列化** | `json.Unmarshal()` | `fromJson()` |
|
||||
| **自动转换** | ✅ 内置支持 | ✅ 通过 class-transformer |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 实现代码对比
|
||||
|
||||
### Go - 完整示例
|
||||
|
||||
```go
|
||||
package dto
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type ClusterResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
HasCAData bool `json:"has_ca_data"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 创建实例
|
||||
cluster := ClusterResponse{
|
||||
ID: "123",
|
||||
Name: "Test",
|
||||
HasCAData: true,
|
||||
CreatedAt: "2025-11-10",
|
||||
}
|
||||
|
||||
// 序列化
|
||||
jsonBytes, _ := json.Marshal(cluster)
|
||||
// Output: {"id":"123","name":"Test","has_ca_data":true,"created_at":"2025-11-10"}
|
||||
|
||||
// 反序列化
|
||||
var newCluster ClusterResponse
|
||||
json.Unmarshal(jsonBytes, &newCluster)
|
||||
// newCluster.HasCAData = true
|
||||
}
|
||||
```
|
||||
|
||||
### TypeScript - 完整示例
|
||||
|
||||
```typescript
|
||||
import { Expose } from 'class-transformer';
|
||||
import { fromJson, toJson } from '@/api/serializer';
|
||||
|
||||
class Cluster {
|
||||
@Expose() id!: string;
|
||||
@Expose() name!: string;
|
||||
@Expose({ name: 'has_ca_data' }) hasCAData?: boolean;
|
||||
@Expose({ name: 'created_at' }) createdAt!: string;
|
||||
}
|
||||
|
||||
function main() {
|
||||
// 创建实例
|
||||
const cluster = new Cluster();
|
||||
cluster.id = "123";
|
||||
cluster.name = "Test";
|
||||
cluster.hasCAData = true;
|
||||
cluster.createdAt = "2025-11-10";
|
||||
|
||||
// 序列化
|
||||
const json = toJson(cluster);
|
||||
// Output: {id:"123",name:"Test",has_ca_data:true,created_at:"2025-11-10"}
|
||||
|
||||
// 反序列化
|
||||
const newCluster = fromJson(Cluster, json);
|
||||
// newCluster.hasCAData === true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 OpenAPI 驱动开发
|
||||
|
||||
### OpenAPI 规范 → Go
|
||||
|
||||
```yaml
|
||||
# backend/docs/openapi.yaml
|
||||
|
||||
components:
|
||||
schemas:
|
||||
ClusterResponse: # → type ClusterResponse struct
|
||||
properties:
|
||||
id: # → ID string `json:"id"`
|
||||
type: string
|
||||
has_ca_data: # → HasCAData bool `json:"has_ca_data"`
|
||||
type: boolean
|
||||
created_at: # → CreatedAt string `json:"created_at"`
|
||||
type: string
|
||||
```
|
||||
|
||||
### OpenAPI 规范 → TypeScript
|
||||
|
||||
```yaml
|
||||
# backend/docs/openapi.yaml
|
||||
|
||||
components:
|
||||
schemas:
|
||||
ClusterResponse: # → class Cluster
|
||||
properties:
|
||||
id: # → @Expose() id!: string
|
||||
type: string
|
||||
has_ca_data: # → @Expose({ name: 'has_ca_data' }) hasCAData?: boolean
|
||||
type: boolean
|
||||
created_at: # → @Expose({ name: 'created_at' }) createdAt!: string
|
||||
type: string
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✨ 总结
|
||||
|
||||
### Go 的优势
|
||||
- ✅ 内置支持,无需额外库
|
||||
- ✅ 编译时生成代码
|
||||
- ✅ 零运行时开销
|
||||
|
||||
### TypeScript + class-transformer 的优势
|
||||
- ✅ 与 Go 相似的开发体验
|
||||
- ✅ 装饰器语法清晰
|
||||
- ✅ 类型安全
|
||||
- ✅ 运行时开销极小
|
||||
|
||||
### 共同点
|
||||
- ✅ 都使用元数据标记字段映射
|
||||
- ✅ 都实现了自动类型转换
|
||||
- ✅ 都保持了代码的可读性和可维护性
|
||||
- ✅ 都支持 OpenAPI 驱动开发
|
||||
|
||||
---
|
||||
|
||||
**结论**: TypeScript + class-transformer 成功复现了 Go 的 struct tags 机制,为前端开发提供了同样优雅的类型转换体验!
|
||||
|
||||
---
|
||||
|
||||
**创建日期**: 2025-11-10
|
||||
**作者**: AI Assistant
|
||||
|
||||
|
||||
Reference in New Issue
Block a user