@ai-content-space/loopx 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +343 -56
- package/README.zh-CN.md +392 -0
- package/package.json +4 -1
- package/plugins/loopx/.codex-plugin/plugin.json +1 -1
- package/plugins/loopx/scripts/plugin-install.test.mjs +1 -0
- package/plugins/loopx/skills/archive/SKILL.md +39 -0
- package/plugins/loopx/skills/build/SKILL.md +111 -9
- package/plugins/loopx/skills/clarify/SKILL.md +121 -1
- package/plugins/loopx/skills/debug/SKILL.md +296 -0
- package/plugins/loopx/skills/debug/condition-based-waiting.md +115 -0
- package/plugins/loopx/skills/debug/defense-in-depth.md +122 -0
- package/plugins/loopx/skills/debug/find-polluter.sh +63 -0
- package/plugins/loopx/skills/debug/root-cause-tracing.md +169 -0
- package/plugins/loopx/skills/go-style/SKILL.md +71 -0
- package/plugins/loopx/skills/kratos/SKILL.md +74 -0
- package/plugins/loopx/skills/kratos/references/advanced-features.md +314 -0
- package/plugins/loopx/skills/kratos/references/architecture.md +488 -0
- package/plugins/loopx/skills/kratos/references/configuration.md +399 -0
- package/plugins/loopx/skills/kratos/references/http-customization.md +512 -0
- package/plugins/loopx/skills/kratos/references/middleware-logging.md +400 -0
- package/plugins/loopx/skills/kratos/references/proto-api-design.md +432 -0
- package/plugins/loopx/skills/kratos/references/security-auth.md +411 -0
- package/plugins/loopx/skills/kratos/references/troubleshooting.md +385 -0
- package/plugins/loopx/skills/plan/SKILL.md +22 -2
- package/plugins/loopx/skills/review/SKILL.md +98 -1
- package/plugins/loopx/skills/tdd/SKILL.md +371 -0
- package/plugins/loopx/skills/tdd/testing-anti-patterns.md +299 -0
- package/plugins/loopx/skills/verify/SKILL.md +139 -0
- package/scripts/codex-stop-hook.mjs +71 -0
- package/scripts/codex-workflow-hook.mjs +153 -0
- package/skills/archive/SKILL.md +39 -0
- package/skills/build/SKILL.md +111 -9
- package/skills/clarify/SKILL.md +121 -1
- package/skills/debug/SKILL.md +296 -0
- package/skills/debug/condition-based-waiting.md +115 -0
- package/skills/debug/defense-in-depth.md +122 -0
- package/skills/debug/find-polluter.sh +63 -0
- package/skills/debug/root-cause-tracing.md +169 -0
- package/skills/go-style/SKILL.md +71 -0
- package/skills/kratos/SKILL.md +74 -0
- package/skills/kratos/references/advanced-features.md +314 -0
- package/skills/kratos/references/architecture.md +488 -0
- package/skills/kratos/references/configuration.md +399 -0
- package/skills/kratos/references/http-customization.md +512 -0
- package/skills/kratos/references/middleware-logging.md +400 -0
- package/skills/kratos/references/proto-api-design.md +432 -0
- package/skills/kratos/references/security-auth.md +411 -0
- package/skills/kratos/references/troubleshooting.md +385 -0
- package/skills/plan/SKILL.md +22 -2
- package/skills/review/SKILL.md +98 -1
- package/skills/tdd/SKILL.md +371 -0
- package/skills/tdd/testing-anti-patterns.md +299 -0
- package/skills/verify/SKILL.md +139 -0
- package/src/build-runtime.mjs +303 -26
- package/src/build-stop-gate.mjs +94 -0
- package/src/cli.mjs +51 -8
- package/src/codex-exec-runtime.mjs +105 -5
- package/src/context-manifest.mjs +172 -0
- package/src/install-discovery.mjs +352 -5
- package/src/next-skill.mjs +85 -0
- package/src/plan-runtime.mjs +100 -122
- package/src/review-runtime.mjs +378 -0
- package/src/runtime-maintenance.mjs +428 -14
- package/src/template-governance.mjs +223 -0
- package/src/workflow.mjs +1947 -118
- package/src/workspace-context.mjs +166 -0
- package/src/workspace-memory.mjs +69 -0
- package/templates/plan.md +6 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
Guide for Kratos layered architecture and dependency injection.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
- Structuring a new Kratos project
|
|
8
|
+
- Understanding layer responsibilities
|
|
9
|
+
- Setting up fx dependency injection
|
|
10
|
+
- Defining repository interfaces
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Layer Responsibilities
|
|
15
|
+
|
|
16
|
+
Kratos follows clean architecture with three primary layers:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
┌─────────────────────────────────────────────┐
|
|
20
|
+
│ Service │
|
|
21
|
+
│ (Protocol conversion, simple validation) │
|
|
22
|
+
├─────────────────────────────────────────────┤
|
|
23
|
+
│ Biz │
|
|
24
|
+
│ (Business logic, domain models, use cases) │
|
|
25
|
+
├─────────────────────────────────────────────┤
|
|
26
|
+
│ Data │
|
|
27
|
+
│ (Data access, repositories, external APIs) │
|
|
28
|
+
└─────────────────────────────────────────────┘
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Service Layer
|
|
32
|
+
|
|
33
|
+
**Responsibility:** Protocol conversion and basic validation.
|
|
34
|
+
|
|
35
|
+
- Convert HTTP/gRPC requests to internal types
|
|
36
|
+
- Call biz layer with appropriate parameters
|
|
37
|
+
- Convert biz responses back to proto types
|
|
38
|
+
- Handle proto-level validation (protovalidate)
|
|
39
|
+
|
|
40
|
+
**Does NOT contain:** Business logic, data access, complex calculations
|
|
41
|
+
|
|
42
|
+
```go
|
|
43
|
+
// Service layer - protocol conversion only
|
|
44
|
+
func (s *UserService) GetUser(ctx context.Context, req *v1.GetUserRequest) (*v1.GetUserResponse, error) {
|
|
45
|
+
// 1. Validate request (proto-level)
|
|
46
|
+
if err := s.validator.Validate(req); err != nil {
|
|
47
|
+
return nil, err
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 2. Call biz layer
|
|
51
|
+
user, err := s.uc.GetUser(ctx, req.UserId)
|
|
52
|
+
if err != nil {
|
|
53
|
+
return nil, err
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 3. Convert to proto response
|
|
57
|
+
return &v1.GetUserResponse{
|
|
58
|
+
Name: user.Name,
|
|
59
|
+
Email: user.Email,
|
|
60
|
+
}, nil
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Biz Layer
|
|
65
|
+
|
|
66
|
+
**Responsibility:** Business logic and domain rules.
|
|
67
|
+
|
|
68
|
+
- Implement business rules and workflows
|
|
69
|
+
- Define domain entities (not proto types)
|
|
70
|
+
- Coordinate between repositories
|
|
71
|
+
- Handle business-level errors
|
|
72
|
+
|
|
73
|
+
**Does NOT contain:** HTTP/gRPC details, database queries, proto types
|
|
74
|
+
|
|
75
|
+
```go
|
|
76
|
+
// Biz layer - business logic only
|
|
77
|
+
type User struct {
|
|
78
|
+
ID int64
|
|
79
|
+
Name string
|
|
80
|
+
Email string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type UserUseCase struct {
|
|
84
|
+
repo UserRepo
|
|
85
|
+
log *log.Helper
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func (uc *UserUseCase) GetUser(ctx context.Context, id string) (*User, error) {
|
|
89
|
+
uc.log.WithContext(ctx).Infof("Getting user: %s", id)
|
|
90
|
+
|
|
91
|
+
// Business logic: check permissions, apply rules
|
|
92
|
+
user, err := uc.repo.FindByID(ctx, id)
|
|
93
|
+
if err != nil {
|
|
94
|
+
return nil, v1.ErrorUserNotFound("user %s not found", id)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Business rule: inactive users not accessible
|
|
98
|
+
if user.Status == "inactive" {
|
|
99
|
+
return nil, errors.New(403, "FORBIDDEN", "user is inactive")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return user, nil
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Data Layer
|
|
107
|
+
|
|
108
|
+
**Responsibility:** Data access and external service integration.
|
|
109
|
+
|
|
110
|
+
- Implement repository interfaces
|
|
111
|
+
- Database operations (SQL, NoSQL)
|
|
112
|
+
- External API calls
|
|
113
|
+
- Cache management
|
|
114
|
+
|
|
115
|
+
**Does NOT contain:** Business logic, protocol handling
|
|
116
|
+
|
|
117
|
+
```go
|
|
118
|
+
// Data layer - data access only
|
|
119
|
+
type userRepo struct {
|
|
120
|
+
db *gorm.DB
|
|
121
|
+
log *log.Helper
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func (r *userRepo) FindByID(ctx context.Context, id string) (*biz.User, error) {
|
|
125
|
+
var model UserModel
|
|
126
|
+
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&model).Error; err != nil {
|
|
127
|
+
return nil, err
|
|
128
|
+
}
|
|
129
|
+
return &biz.User{
|
|
130
|
+
ID: model.ID,
|
|
131
|
+
Name: model.Name,
|
|
132
|
+
Email: model.Email,
|
|
133
|
+
}, nil
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Dependency Injection
|
|
140
|
+
|
|
141
|
+
Kratos supports two DI approaches: **wire** (official Kratos layout) and **fx** (alternative).
|
|
142
|
+
|
|
143
|
+
### Wire (Official Kratos Layout)
|
|
144
|
+
|
|
145
|
+
Wire is Google's compile-time dependency injection tool, used in official Kratos project templates.
|
|
146
|
+
|
|
147
|
+
**Install:**
|
|
148
|
+
```bash
|
|
149
|
+
go install github.com/google/wire/cmd/wire@latest
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Wire.go definition:**
|
|
153
|
+
```go
|
|
154
|
+
// go build will ignore this file, but wire uses it
|
|
155
|
+
|
|
156
|
+
//+build wireinject
|
|
157
|
+
|
|
158
|
+
package main
|
|
159
|
+
|
|
160
|
+
import (
|
|
161
|
+
"github.com/google/wire"
|
|
162
|
+
"github.com/go-kratos/kratos/v2"
|
|
163
|
+
"github.com/go-kratos/kratos/v2/log"
|
|
164
|
+
"github.com/myorg/myproject/internal/biz"
|
|
165
|
+
"github.com/myorg/myproject/internal/data"
|
|
166
|
+
"github.com/myorg/myproject/internal/server"
|
|
167
|
+
"github.com/myorg/myproject/internal/service"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
func wireApp(*conf.Server, *conf.Data, log.Logger) (*kratos.App, func(), error) {
|
|
171
|
+
panic(wire.Build(
|
|
172
|
+
data.ProviderSet,
|
|
173
|
+
biz.ProviderSet,
|
|
174
|
+
service.ProviderSet,
|
|
175
|
+
server.ProviderSet,
|
|
176
|
+
newApp,
|
|
177
|
+
))
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**ProviderSet in each layer:**
|
|
182
|
+
```go
|
|
183
|
+
// internal/data/data.go
|
|
184
|
+
var ProviderSet = wire.NewSet(NewData, NewUserRepo)
|
|
185
|
+
|
|
186
|
+
// internal/biz/biz.go
|
|
187
|
+
var ProviderSet = wire.NewSet(NewUserUseCase)
|
|
188
|
+
|
|
189
|
+
// internal/service/service.go
|
|
190
|
+
var ProviderSet = wire.NewSet(NewUserService)
|
|
191
|
+
|
|
192
|
+
// internal/server/server.go
|
|
193
|
+
var ProviderSet = wire.NewSet(NewHTTPServer, NewGRPCServer)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Generate wire_gen.go:**
|
|
197
|
+
```bash
|
|
198
|
+
wire ./cmd/server
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Advantages:**
|
|
202
|
+
- Compile-time verification (errors caught before runtime)
|
|
203
|
+
- No runtime reflection overhead
|
|
204
|
+
- Clear dependency graph visible in generated code
|
|
205
|
+
- Official Kratos template uses this approach
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
### fx (Alternative - Uber's DI)
|
|
210
|
+
|
|
211
|
+
fx is Uber's runtime dependency injection framework.
|
|
212
|
+
|
|
213
|
+
## fx Dependency Injection
|
|
214
|
+
|
|
215
|
+
Kratos also supports Uber fx for dependency injection:
|
|
216
|
+
|
|
217
|
+
### Module Pattern
|
|
218
|
+
|
|
219
|
+
Organize dependencies into modules:
|
|
220
|
+
|
|
221
|
+
```go
|
|
222
|
+
// internal/service/fx.go
|
|
223
|
+
package service
|
|
224
|
+
|
|
225
|
+
import "go.uber.org/fx"
|
|
226
|
+
|
|
227
|
+
var Module = fx.Options(
|
|
228
|
+
fx.Provide(NewUserService),
|
|
229
|
+
fx.Provide(NewOrderService),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
// internal/biz/fx.go
|
|
233
|
+
package biz
|
|
234
|
+
|
|
235
|
+
var Module = fx.Options(
|
|
236
|
+
fx.Provide(NewUserUseCase),
|
|
237
|
+
fx.Provide(NewOrderUseCase),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
// internal/data/fx.go
|
|
241
|
+
package data
|
|
242
|
+
|
|
243
|
+
var Module = fx.Options(
|
|
244
|
+
fx.Provide(NewUserRepo),
|
|
245
|
+
fx.Provide(NewData), // DB connection
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
// internal/server/fx.go
|
|
249
|
+
package server
|
|
250
|
+
|
|
251
|
+
var Module = fx.Options(
|
|
252
|
+
fx.Provide(NewHTTPServer),
|
|
253
|
+
fx.Provide(NewGRPCServer),
|
|
254
|
+
)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Main Application
|
|
258
|
+
|
|
259
|
+
```go
|
|
260
|
+
// cmd/server/main.go
|
|
261
|
+
package main
|
|
262
|
+
|
|
263
|
+
import (
|
|
264
|
+
"github.com/myorg/myproject/internal/biz"
|
|
265
|
+
"github.com/myorg/myproject/internal/data"
|
|
266
|
+
"github.com/myorg/myproject/internal/server"
|
|
267
|
+
"github.com/myorg/myproject/internal/service"
|
|
268
|
+
"go.uber.org/fx"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
func main() {
|
|
272
|
+
app := fx.New(
|
|
273
|
+
// Provide configs
|
|
274
|
+
fx.Provide(provideConfigs),
|
|
275
|
+
fx.Provide(provideLogger),
|
|
276
|
+
fx.Provide(provideValidator),
|
|
277
|
+
|
|
278
|
+
// Include modules
|
|
279
|
+
server.Module,
|
|
280
|
+
data.Module,
|
|
281
|
+
biz.Module,
|
|
282
|
+
service.Module,
|
|
283
|
+
|
|
284
|
+
// Provide Kratos app
|
|
285
|
+
appModule,
|
|
286
|
+
)
|
|
287
|
+
app.Run()
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Dependency Flow
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
fx.New() resolves dependencies:
|
|
295
|
+
|
|
296
|
+
provideConfigs → Bootstrap
|
|
297
|
+
provideLogger → log.Logger
|
|
298
|
+
provideValidator → protovalidate.Validator
|
|
299
|
+
|
|
300
|
+
data.Module:
|
|
301
|
+
NewData(Bootstrap) → *gorm.DB
|
|
302
|
+
NewUserRepo(*gorm.DB, log.Logger) → biz.UserRepo
|
|
303
|
+
|
|
304
|
+
biz.Module:
|
|
305
|
+
NewUserUseCase(biz.UserRepo, log.Logger) → *UserUseCase
|
|
306
|
+
|
|
307
|
+
service.Module:
|
|
308
|
+
NewUserService(*UserUseCase, log.Logger, protovalidate.Validator) → *UserService
|
|
309
|
+
|
|
310
|
+
server.Module:
|
|
311
|
+
NewHTTPServer(Bootstrap, *UserService, log.Logger) → *http.Server
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Repository Interface Pattern
|
|
317
|
+
|
|
318
|
+
Define repository interfaces in biz layer, implement in data layer:
|
|
319
|
+
|
|
320
|
+
### Interface Definition (Biz)
|
|
321
|
+
|
|
322
|
+
```go
|
|
323
|
+
// internal/biz/user.go
|
|
324
|
+
package biz
|
|
325
|
+
|
|
326
|
+
import "context"
|
|
327
|
+
|
|
328
|
+
// UserRepo interface - defined in biz, implemented in data
|
|
329
|
+
type UserRepo interface {
|
|
330
|
+
Save(ctx context.Context, user *User) (*User, error)
|
|
331
|
+
FindByID(ctx context.Context, id int64) (*User, error)
|
|
332
|
+
FindByEmail(ctx context.Context, email string) (*User, error)
|
|
333
|
+
Update(ctx context.Context, user *User) (*User, error)
|
|
334
|
+
Delete(ctx context.Context, id int64) error
|
|
335
|
+
ListAll(ctx context.Context) ([]*User, error)
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Implementation (Data)
|
|
340
|
+
|
|
341
|
+
```go
|
|
342
|
+
// internal/data/user.go
|
|
343
|
+
package data
|
|
344
|
+
|
|
345
|
+
import (
|
|
346
|
+
"context"
|
|
347
|
+
"github.com/myorg/myproject/internal/biz"
|
|
348
|
+
"github.com/go-kratos/kratos/v2/log"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
type userRepo struct {
|
|
352
|
+
data *Data
|
|
353
|
+
log *log.Helper
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// NewUserRepo implements biz.UserRepo interface
|
|
357
|
+
func NewUserRepo(data *Data, logger log.Logger) biz.UserRepo {
|
|
358
|
+
return &userRepo{
|
|
359
|
+
data: data,
|
|
360
|
+
log: log.NewHelper(logger),
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
func (r *userRepo) Save(ctx context.Context, user *biz.User) (*biz.User, error) {
|
|
365
|
+
// Database implementation
|
|
366
|
+
return user, nil
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Why this matters:** Biz layer doesn't know about database details. You can swap databases without changing business logic.
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Custom Route Middleware Inheritance
|
|
375
|
+
|
|
376
|
+
For non-proto routes (file upload, WebSocket), inherit server middleware:
|
|
377
|
+
|
|
378
|
+
```go
|
|
379
|
+
// internal/server/http.go
|
|
380
|
+
func NewHTTPServer(c *conf.Server, greeter *service.GreeterService, logger log.Logger) *http.Server {
|
|
381
|
+
opts := []http.ServerOption{
|
|
382
|
+
http.Middleware(
|
|
383
|
+
recovery.Recovery(),
|
|
384
|
+
auth.AuthToken(),
|
|
385
|
+
),
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
srv := http.NewServer(opts...)
|
|
389
|
+
|
|
390
|
+
// Proto-generated routes (automatic middleware)
|
|
391
|
+
v1.RegisterGreeterServiceHTTPServer(srv, greeter)
|
|
392
|
+
|
|
393
|
+
// Custom route (manual middleware)
|
|
394
|
+
route := srv.Route("/")
|
|
395
|
+
route.POST("/v1/upload", greeter.UploadFile)
|
|
396
|
+
|
|
397
|
+
return srv
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// service/upload.go
|
|
401
|
+
func (s *GreeterService) UploadFile(ctx http.Context) error {
|
|
402
|
+
// Set operation for middleware
|
|
403
|
+
http.SetOperation(ctx, "/upload.v1.UploadService/Upload")
|
|
404
|
+
|
|
405
|
+
// Bind request
|
|
406
|
+
var req UploadRequest
|
|
407
|
+
if err := ctx.BindQuery(&req); err != nil {
|
|
408
|
+
return err
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Use middleware chain
|
|
412
|
+
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
413
|
+
return s.uc.UploadFile(ctx, req.(*UploadRequest))
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
resp, err := h(ctx, &req)
|
|
417
|
+
if err != nil {
|
|
418
|
+
return err
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return ctx.JSON(200, resp)
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## pb → Struct Type Conversion
|
|
428
|
+
|
|
429
|
+
Use copier with copieroptpb for protobuf to Go struct conversion:
|
|
430
|
+
|
|
431
|
+
### Install
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
go get github.com/jinzhu/copier
|
|
435
|
+
go get github.com/tiny-lib/copieroptpb
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Usage
|
|
439
|
+
|
|
440
|
+
```go
|
|
441
|
+
import (
|
|
442
|
+
"github.com/jinzhu/copier"
|
|
443
|
+
"github.com/tiny-lib/copieroptpb"
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
// biz layer struct
|
|
447
|
+
type User struct {
|
|
448
|
+
Name string
|
|
449
|
+
Email string
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Convert pb message to biz struct
|
|
453
|
+
func toBizUser(pbUser *v1.User) (*User, error) {
|
|
454
|
+
user := &User{}
|
|
455
|
+
if err := copier.CopyWithOption(pbUser, user, copieroptpb.Option()); err != nil {
|
|
456
|
+
return nil, err
|
|
457
|
+
}
|
|
458
|
+
return user, nil
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Convert biz struct to pb message
|
|
462
|
+
func toProtoUser(user *User) (*v1.User, error) {
|
|
463
|
+
pbUser := &v1.User{}
|
|
464
|
+
if err := copier.CopyWithOption(user, pbUser, copieroptpb.Option()); err != nil {
|
|
465
|
+
return nil, err
|
|
466
|
+
}
|
|
467
|
+
return pbUser, nil
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Why copieroptpb:** Handles protobuf wrapper types (StringValue, Int32Value, etc.) that copier doesn't understand natively.
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## Dependency Boundaries
|
|
476
|
+
|
|
477
|
+
Keep layers isolated:
|
|
478
|
+
|
|
479
|
+
| Layer | Allowed Dependencies |
|
|
480
|
+
|-------|---------------------|
|
|
481
|
+
| Service | Biz, proto types, kratos transport |
|
|
482
|
+
| Biz | Data (interfaces), domain types, errors |
|
|
483
|
+
| Data | Biz (interfaces), database drivers, external APIs |
|
|
484
|
+
|
|
485
|
+
**Forbidden:**
|
|
486
|
+
- Service → Data (skip biz layer)
|
|
487
|
+
- Data → Service (reverse dependency)
|
|
488
|
+
- Any layer → proto types in wrong context
|