claudient 0.1.0
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/.claude-plugin/plugin.json +42 -0
- package/CONTEXT.md +58 -0
- package/README.md +165 -0
- package/agents/build-resolvers/de/python-resolver.md +64 -0
- package/agents/build-resolvers/de/typescript-resolver.md +65 -0
- package/agents/build-resolvers/es/python-resolver.md +64 -0
- package/agents/build-resolvers/es/typescript-resolver.md +65 -0
- package/agents/build-resolvers/fr/python-resolver.md +64 -0
- package/agents/build-resolvers/fr/typescript-resolver.md +65 -0
- package/agents/build-resolvers/nl/python-resolver.md +64 -0
- package/agents/build-resolvers/nl/typescript-resolver.md +65 -0
- package/agents/build-resolvers/python-resolver.md +62 -0
- package/agents/build-resolvers/typescript-resolver.md +63 -0
- package/agents/core/architect.md +64 -0
- package/agents/core/code-reviewer.md +78 -0
- package/agents/core/de/architect.md +66 -0
- package/agents/core/de/code-reviewer.md +80 -0
- package/agents/core/de/planner.md +63 -0
- package/agents/core/de/security-reviewer.md +93 -0
- package/agents/core/es/architect.md +66 -0
- package/agents/core/es/code-reviewer.md +80 -0
- package/agents/core/es/planner.md +63 -0
- package/agents/core/es/security-reviewer.md +93 -0
- package/agents/core/fr/architect.md +66 -0
- package/agents/core/fr/code-reviewer.md +80 -0
- package/agents/core/fr/planner.md +63 -0
- package/agents/core/fr/security-reviewer.md +93 -0
- package/agents/core/nl/architect.md +66 -0
- package/agents/core/nl/code-reviewer.md +80 -0
- package/agents/core/nl/planner.md +63 -0
- package/agents/core/nl/security-reviewer.md +93 -0
- package/agents/core/planner.md +61 -0
- package/agents/core/security-reviewer.md +91 -0
- package/guides/agent-orchestration.md +231 -0
- package/guides/de/agent-orchestration.md +174 -0
- package/guides/de/getting-started.md +164 -0
- package/guides/de/hooks-cookbook.md +160 -0
- package/guides/de/memory-management.md +153 -0
- package/guides/de/security.md +180 -0
- package/guides/de/skill-authoring.md +214 -0
- package/guides/de/token-optimization.md +156 -0
- package/guides/es/agent-orchestration.md +174 -0
- package/guides/es/getting-started.md +164 -0
- package/guides/es/hooks-cookbook.md +160 -0
- package/guides/es/memory-management.md +153 -0
- package/guides/es/security.md +180 -0
- package/guides/es/skill-authoring.md +214 -0
- package/guides/es/token-optimization.md +156 -0
- package/guides/fr/agent-orchestration.md +174 -0
- package/guides/fr/getting-started.md +164 -0
- package/guides/fr/hooks-cookbook.md +227 -0
- package/guides/fr/memory-management.md +169 -0
- package/guides/fr/security.md +180 -0
- package/guides/fr/skill-authoring.md +214 -0
- package/guides/fr/token-optimization.md +158 -0
- package/guides/getting-started.md +164 -0
- package/guides/hooks-cookbook.md +423 -0
- package/guides/memory-management.md +192 -0
- package/guides/nl/agent-orchestration.md +174 -0
- package/guides/nl/getting-started.md +164 -0
- package/guides/nl/hooks-cookbook.md +160 -0
- package/guides/nl/memory-management.md +153 -0
- package/guides/nl/security.md +180 -0
- package/guides/nl/skill-authoring.md +214 -0
- package/guides/nl/token-optimization.md +156 -0
- package/guides/security.md +229 -0
- package/guides/skill-authoring.md +226 -0
- package/guides/token-optimization.md +169 -0
- package/hooks/lifecycle/cost-tracker.md +49 -0
- package/hooks/lifecycle/cost-tracker.sh +59 -0
- package/hooks/lifecycle/pre-compact-save.md +56 -0
- package/hooks/lifecycle/pre-compact-save.sh +37 -0
- package/hooks/lifecycle/session-start.md +50 -0
- package/hooks/lifecycle/session-start.sh +47 -0
- package/hooks/post-tool-use/audit-log.md +53 -0
- package/hooks/post-tool-use/audit-log.sh +53 -0
- package/hooks/post-tool-use/prettier.md +53 -0
- package/hooks/post-tool-use/prettier.sh +49 -0
- package/hooks/pre-tool-use/block-dangerous.md +48 -0
- package/hooks/pre-tool-use/block-dangerous.sh +76 -0
- package/hooks/pre-tool-use/git-push-confirm.md +46 -0
- package/hooks/pre-tool-use/git-push-confirm.sh +36 -0
- package/mcp/configs/github.json +11 -0
- package/mcp/configs/postgres.json +11 -0
- package/mcp/de/recommended-servers.md +170 -0
- package/mcp/es/recommended-servers.md +170 -0
- package/mcp/fr/recommended-servers.md +170 -0
- package/mcp/nl/recommended-servers.md +170 -0
- package/mcp/recommended-servers.md +168 -0
- package/package.json +45 -0
- package/prompts/project-starters/de/fastapi-project.md +62 -0
- package/prompts/project-starters/de/nextjs-project.md +82 -0
- package/prompts/project-starters/es/fastapi-project.md +62 -0
- package/prompts/project-starters/es/nextjs-project.md +82 -0
- package/prompts/project-starters/fastapi-project.md +60 -0
- package/prompts/project-starters/fr/fastapi-project.md +62 -0
- package/prompts/project-starters/fr/nextjs-project.md +82 -0
- package/prompts/project-starters/nextjs-project.md +80 -0
- package/prompts/project-starters/nl/fastapi-project.md +62 -0
- package/prompts/project-starters/nl/nextjs-project.md +82 -0
- package/prompts/system-prompts/ai-product.md +80 -0
- package/prompts/system-prompts/data-pipeline.md +76 -0
- package/prompts/system-prompts/de/ai-product.md +82 -0
- package/prompts/system-prompts/de/data-pipeline.md +78 -0
- package/prompts/system-prompts/de/saas-backend.md +71 -0
- package/prompts/system-prompts/es/ai-product.md +82 -0
- package/prompts/system-prompts/es/data-pipeline.md +78 -0
- package/prompts/system-prompts/es/saas-backend.md +71 -0
- package/prompts/system-prompts/fr/ai-product.md +82 -0
- package/prompts/system-prompts/fr/data-pipeline.md +78 -0
- package/prompts/system-prompts/fr/saas-backend.md +71 -0
- package/prompts/system-prompts/nl/ai-product.md +82 -0
- package/prompts/system-prompts/nl/data-pipeline.md +78 -0
- package/prompts/system-prompts/nl/saas-backend.md +71 -0
- package/prompts/system-prompts/saas-backend.md +69 -0
- package/prompts/task-specific/changelog.md +81 -0
- package/prompts/task-specific/de/changelog.md +83 -0
- package/prompts/task-specific/de/debugging.md +78 -0
- package/prompts/task-specific/de/pr-description.md +69 -0
- package/prompts/task-specific/debugging.md +76 -0
- package/prompts/task-specific/es/changelog.md +83 -0
- package/prompts/task-specific/es/debugging.md +78 -0
- package/prompts/task-specific/es/pr-description.md +69 -0
- package/prompts/task-specific/fr/changelog.md +83 -0
- package/prompts/task-specific/fr/debugging.md +78 -0
- package/prompts/task-specific/fr/pr-description.md +69 -0
- package/prompts/task-specific/nl/changelog.md +83 -0
- package/prompts/task-specific/nl/debugging.md +78 -0
- package/prompts/task-specific/nl/pr-description.md +69 -0
- package/prompts/task-specific/pr-description.md +67 -0
- package/rules/common/coding-style.md +45 -0
- package/rules/common/de/coding-style.md +47 -0
- package/rules/common/de/git.md +48 -0
- package/rules/common/de/performance.md +40 -0
- package/rules/common/de/security.md +45 -0
- package/rules/common/de/testing.md +45 -0
- package/rules/common/es/coding-style.md +47 -0
- package/rules/common/es/git.md +48 -0
- package/rules/common/es/performance.md +40 -0
- package/rules/common/es/security.md +45 -0
- package/rules/common/es/testing.md +45 -0
- package/rules/common/fr/coding-style.md +47 -0
- package/rules/common/fr/git.md +48 -0
- package/rules/common/fr/performance.md +40 -0
- package/rules/common/fr/security.md +45 -0
- package/rules/common/fr/testing.md +45 -0
- package/rules/common/git.md +46 -0
- package/rules/common/nl/coding-style.md +47 -0
- package/rules/common/nl/git.md +48 -0
- package/rules/common/nl/performance.md +40 -0
- package/rules/common/nl/security.md +45 -0
- package/rules/common/nl/testing.md +45 -0
- package/rules/common/performance.md +38 -0
- package/rules/common/security.md +43 -0
- package/rules/common/testing.md +43 -0
- package/rules/language-specific/de/go.md +48 -0
- package/rules/language-specific/de/python.md +38 -0
- package/rules/language-specific/de/typescript.md +51 -0
- package/rules/language-specific/es/go.md +48 -0
- package/rules/language-specific/es/python.md +38 -0
- package/rules/language-specific/es/typescript.md +51 -0
- package/rules/language-specific/fr/go.md +48 -0
- package/rules/language-specific/fr/python.md +38 -0
- package/rules/language-specific/fr/typescript.md +51 -0
- package/rules/language-specific/go.md +46 -0
- package/rules/language-specific/nl/go.md +48 -0
- package/rules/language-specific/nl/python.md +38 -0
- package/rules/language-specific/nl/typescript.md +51 -0
- package/rules/language-specific/python.md +36 -0
- package/rules/language-specific/typescript.md +49 -0
- package/scripts/cli.js +161 -0
- package/scripts/link-skills.sh +35 -0
- package/scripts/list-skills.sh +34 -0
- package/skills/ai-engineering/agent-construction.md +285 -0
- package/skills/ai-engineering/claude-api.md +248 -0
- package/skills/ai-engineering/de/agent-construction.md +287 -0
- package/skills/ai-engineering/de/claude-api.md +250 -0
- package/skills/ai-engineering/es/agent-construction.md +287 -0
- package/skills/ai-engineering/es/claude-api.md +250 -0
- package/skills/ai-engineering/fr/agent-construction.md +287 -0
- package/skills/ai-engineering/fr/claude-api.md +250 -0
- package/skills/ai-engineering/nl/agent-construction.md +287 -0
- package/skills/ai-engineering/nl/claude-api.md +250 -0
- package/skills/backend/dotnet/csharp.md +304 -0
- package/skills/backend/dotnet/de/csharp.md +306 -0
- package/skills/backend/dotnet/es/csharp.md +306 -0
- package/skills/backend/dotnet/fr/csharp.md +306 -0
- package/skills/backend/dotnet/nl/csharp.md +306 -0
- package/skills/backend/go/de/go.md +307 -0
- package/skills/backend/go/es/go.md +307 -0
- package/skills/backend/go/fr/go.md +307 -0
- package/skills/backend/go/go.md +305 -0
- package/skills/backend/go/nl/go.md +307 -0
- package/skills/backend/nodejs/de/nestjs.md +274 -0
- package/skills/backend/nodejs/de/nextjs.md +222 -0
- package/skills/backend/nodejs/es/nestjs.md +274 -0
- package/skills/backend/nodejs/es/nextjs.md +222 -0
- package/skills/backend/nodejs/fr/nestjs.md +274 -0
- package/skills/backend/nodejs/fr/nextjs.md +222 -0
- package/skills/backend/nodejs/nestjs.md +272 -0
- package/skills/backend/nodejs/nextjs.md +220 -0
- package/skills/backend/nodejs/nl/nestjs.md +274 -0
- package/skills/backend/nodejs/nl/nextjs.md +222 -0
- package/skills/backend/python/de/django.md +285 -0
- package/skills/backend/python/de/fastapi.md +244 -0
- package/skills/backend/python/django.md +283 -0
- package/skills/backend/python/es/django.md +285 -0
- package/skills/backend/python/es/fastapi.md +244 -0
- package/skills/backend/python/fastapi.md +242 -0
- package/skills/backend/python/fr/django.md +285 -0
- package/skills/backend/python/fr/fastapi.md +244 -0
- package/skills/backend/python/nl/django.md +285 -0
- package/skills/backend/python/nl/fastapi.md +244 -0
- package/skills/data-ml/dbt-data-pipelines.md +155 -0
- package/skills/data-ml/de/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/de/pandas-polars.md +147 -0
- package/skills/data-ml/de/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/es/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/es/pandas-polars.md +147 -0
- package/skills/data-ml/es/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/fr/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/fr/pandas-polars.md +147 -0
- package/skills/data-ml/fr/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/nl/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/nl/pandas-polars.md +147 -0
- package/skills/data-ml/nl/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/pandas-polars.md +145 -0
- package/skills/data-ml/pytorch-tensorflow.md +169 -0
- package/skills/database/de/graphql.md +181 -0
- package/skills/database/es/graphql.md +181 -0
- package/skills/database/fr/graphql.md +181 -0
- package/skills/database/graphql.md +179 -0
- package/skills/database/nl/graphql.md +181 -0
- package/skills/devops-infra/de/docker.md +133 -0
- package/skills/devops-infra/de/github-actions.md +179 -0
- package/skills/devops-infra/de/kubernetes.md +129 -0
- package/skills/devops-infra/de/terraform.md +130 -0
- package/skills/devops-infra/docker.md +131 -0
- package/skills/devops-infra/es/docker.md +133 -0
- package/skills/devops-infra/es/github-actions.md +179 -0
- package/skills/devops-infra/es/kubernetes.md +129 -0
- package/skills/devops-infra/es/terraform.md +130 -0
- package/skills/devops-infra/fr/docker.md +133 -0
- package/skills/devops-infra/fr/github-actions.md +179 -0
- package/skills/devops-infra/fr/kubernetes.md +129 -0
- package/skills/devops-infra/fr/terraform.md +130 -0
- package/skills/devops-infra/github-actions.md +177 -0
- package/skills/devops-infra/kubernetes.md +127 -0
- package/skills/devops-infra/nl/docker.md +133 -0
- package/skills/devops-infra/nl/github-actions.md +179 -0
- package/skills/devops-infra/nl/kubernetes.md +129 -0
- package/skills/devops-infra/nl/terraform.md +130 -0
- package/skills/devops-infra/terraform.md +128 -0
- package/skills/finance-payments/de/stripe.md +187 -0
- package/skills/finance-payments/es/stripe.md +187 -0
- package/skills/finance-payments/fr/stripe.md +187 -0
- package/skills/finance-payments/nl/stripe.md +187 -0
- package/skills/finance-payments/stripe.md +185 -0
- package/workflows/code-review.md +151 -0
- package/workflows/de/code-review.md +153 -0
- package/workflows/de/debugging-session.md +146 -0
- package/workflows/de/feature-development.md +155 -0
- package/workflows/de/new-project-bootstrap.md +175 -0
- package/workflows/de/refactor-safely.md +150 -0
- package/workflows/debugging-session.md +144 -0
- package/workflows/es/code-review.md +153 -0
- package/workflows/es/debugging-session.md +146 -0
- package/workflows/es/feature-development.md +155 -0
- package/workflows/es/new-project-bootstrap.md +175 -0
- package/workflows/es/refactor-safely.md +150 -0
- package/workflows/feature-development.md +153 -0
- package/workflows/fr/code-review.md +153 -0
- package/workflows/fr/debugging-session.md +146 -0
- package/workflows/fr/feature-development.md +155 -0
- package/workflows/fr/new-project-bootstrap.md +175 -0
- package/workflows/fr/refactor-safely.md +150 -0
- package/workflows/new-project-bootstrap.md +173 -0
- package/workflows/nl/code-review.md +153 -0
- package/workflows/nl/debugging-session.md +146 -0
- package/workflows/nl/feature-development.md +155 -0
- package/workflows/nl/new-project-bootstrap.md +175 -0
- package/workflows/nl/refactor-safely.md +150 -0
- package/workflows/refactor-safely.md +148 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
> 🇩🇪 Dies ist die deutsche Übersetzung. [Englische Version](../go.md).
|
|
2
|
+
|
|
3
|
+
# Go Skill
|
|
4
|
+
|
|
5
|
+
## Wann aktivieren
|
|
6
|
+
- Einen Go-HTTP-Service oder ein CLI-Tool bauen
|
|
7
|
+
- Ein Go-Projekt mit `cmd/`, `internal/`, `pkg/` strukturieren
|
|
8
|
+
- HTTP-Handler mit `net/http`, chi oder gin schreiben
|
|
9
|
+
- Interfaces, Einbettung und Kompositionsmuster implementieren
|
|
10
|
+
- Parallelen Code mit Goroutines, Channels und `sync`-Primitiven schreiben
|
|
11
|
+
- Fehlerbehandlung, -umschließung und Sentinel-Fehler
|
|
12
|
+
- Tabellengetriebene Tests schreiben
|
|
13
|
+
|
|
14
|
+
## Wann NICHT verwenden
|
|
15
|
+
- Node.js-, Python- oder andere Sprachdienste — andere Ökosysteme
|
|
16
|
+
- Protobuf/gRPC-Dienste ohne vorherigen gRPC-Setup-Kontext
|
|
17
|
+
- Reine Skript-Aufgaben — Shell oder Python sind besser geeignet
|
|
18
|
+
|
|
19
|
+
## Anweisungen
|
|
20
|
+
|
|
21
|
+
### Projektlayout
|
|
22
|
+
```
|
|
23
|
+
myapp/
|
|
24
|
+
├── cmd/
|
|
25
|
+
│ └── server/
|
|
26
|
+
│ └── main.go # Einstiegspunkt — dünn, verbindet und startet nur
|
|
27
|
+
├── internal/ # Private Pakete — nicht extern importierbar
|
|
28
|
+
│ ├── config/
|
|
29
|
+
│ ├── handler/
|
|
30
|
+
│ ├── service/
|
|
31
|
+
│ └── store/
|
|
32
|
+
├── pkg/ # Öffentliche Pakete — von anderen Modulen importierbar
|
|
33
|
+
│ └── apierr/
|
|
34
|
+
├── go.mod
|
|
35
|
+
└── go.sum
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### main.go — nur Verkabelung
|
|
39
|
+
```go
|
|
40
|
+
// cmd/server/main.go
|
|
41
|
+
package main
|
|
42
|
+
|
|
43
|
+
import (
|
|
44
|
+
"context"
|
|
45
|
+
"log/slog"
|
|
46
|
+
"net/http"
|
|
47
|
+
"os"
|
|
48
|
+
"os/signal"
|
|
49
|
+
"syscall"
|
|
50
|
+
"time"
|
|
51
|
+
|
|
52
|
+
"myapp/internal/config"
|
|
53
|
+
"myapp/internal/handler"
|
|
54
|
+
"myapp/internal/store"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
func main() {
|
|
58
|
+
cfg := config.MustLoad()
|
|
59
|
+
db := store.MustConnect(cfg.DatabaseURL)
|
|
60
|
+
defer db.Close()
|
|
61
|
+
|
|
62
|
+
mux := handler.NewMux(db, cfg)
|
|
63
|
+
|
|
64
|
+
srv := &http.Server{
|
|
65
|
+
Addr: ":" + cfg.Port,
|
|
66
|
+
Handler: mux,
|
|
67
|
+
ReadTimeout: 10 * time.Second,
|
|
68
|
+
WriteTimeout: 30 * time.Second,
|
|
69
|
+
IdleTimeout: 60 * time.Second,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
go func() {
|
|
73
|
+
slog.Info("server starting", "addr", srv.Addr)
|
|
74
|
+
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
|
|
75
|
+
slog.Error("server error", "err", err)
|
|
76
|
+
os.Exit(1)
|
|
77
|
+
}
|
|
78
|
+
}()
|
|
79
|
+
|
|
80
|
+
quit := make(chan os.Signal, 1)
|
|
81
|
+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
82
|
+
<-quit
|
|
83
|
+
|
|
84
|
+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
85
|
+
defer cancel()
|
|
86
|
+
srv.Shutdown(ctx)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Fehlerbehandlung
|
|
91
|
+
```go
|
|
92
|
+
// Sentinel-Fehler für erwartete Bedingungen definieren
|
|
93
|
+
var (
|
|
94
|
+
ErrNotFound = errors.New("not found")
|
|
95
|
+
ErrDuplicate = errors.New("duplicate entry")
|
|
96
|
+
ErrForbidden = errors.New("forbidden")
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// Fehler mit Kontext umschließen — %w verwenden, um das Entpacken zu erhalten
|
|
100
|
+
func (s *UserStore) GetByID(ctx context.Context, id string) (*User, error) {
|
|
101
|
+
var u User
|
|
102
|
+
err := s.db.QueryRowContext(ctx, `SELECT id, email FROM users WHERE id = $1`, id).
|
|
103
|
+
Scan(&u.ID, &u.Email)
|
|
104
|
+
if errors.Is(err, sql.ErrNoRows) {
|
|
105
|
+
return nil, fmt.Errorf("user %s: %w", id, ErrNotFound)
|
|
106
|
+
}
|
|
107
|
+
if err != nil {
|
|
108
|
+
return nil, fmt.Errorf("user store get: %w", err)
|
|
109
|
+
}
|
|
110
|
+
return &u, nil
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Aufrufer prüft Verhalten, nicht Nachrichtenstrings
|
|
114
|
+
user, err := store.GetByID(ctx, id)
|
|
115
|
+
if errors.Is(err, ErrNotFound) {
|
|
116
|
+
http.Error(w, "not found", http.StatusNotFound)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### HTTP-Handler mit chi
|
|
122
|
+
```go
|
|
123
|
+
// internal/handler/users.go
|
|
124
|
+
type UserHandler struct {
|
|
125
|
+
svc UserService
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
func (h *UserHandler) Register(r chi.Router) {
|
|
129
|
+
r.Get("/users/{id}", h.GetUser)
|
|
130
|
+
r.Post("/users", h.CreateUser)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
|
|
134
|
+
id := chi.URLParam(r, "id")
|
|
135
|
+
user, err := h.svc.GetUser(r.Context(), id)
|
|
136
|
+
if err != nil {
|
|
137
|
+
renderError(w, err)
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
renderJSON(w, http.StatusOK, user)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
func renderJSON(w http.ResponseWriter, code int, v any) {
|
|
144
|
+
w.Header().Set("Content-Type", "application/json")
|
|
145
|
+
w.WriteHeader(code)
|
|
146
|
+
json.NewEncoder(w).Encode(v)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func renderError(w http.ResponseWriter, err error) {
|
|
150
|
+
switch {
|
|
151
|
+
case errors.Is(err, ErrNotFound):
|
|
152
|
+
http.Error(w, "not found", http.StatusNotFound)
|
|
153
|
+
case errors.Is(err, ErrForbidden):
|
|
154
|
+
http.Error(w, "forbidden", http.StatusForbidden)
|
|
155
|
+
default:
|
|
156
|
+
slog.Error("internal error", "err", err)
|
|
157
|
+
http.Error(w, "internal server error", http.StatusInternalServerError)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Interfaces
|
|
163
|
+
```go
|
|
164
|
+
// Interfaces dort definieren, wo sie verwendet werden (Verbraucherseite), nicht wo implementiert
|
|
165
|
+
// internal/handler/users.go
|
|
166
|
+
type UserService interface {
|
|
167
|
+
GetUser(ctx context.Context, id string) (*User, error)
|
|
168
|
+
CreateUser(ctx context.Context, req CreateUserRequest) (*User, error)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Interfaces klein halten — 1-3 Methoden bevorzugen
|
|
172
|
+
// io.Reader, io.Writer sind die kanonischen Beispiele
|
|
173
|
+
|
|
174
|
+
// Einbettung für Komposition
|
|
175
|
+
type ReadWriter interface {
|
|
176
|
+
io.Reader
|
|
177
|
+
io.Writer
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Parallelitätsmuster
|
|
182
|
+
```go
|
|
183
|
+
// Fan-out mit errgroup
|
|
184
|
+
import "golang.org/x/sync/errgroup"
|
|
185
|
+
|
|
186
|
+
func fetchAll(ctx context.Context, ids []string) ([]*User, error) {
|
|
187
|
+
g, ctx := errgroup.WithContext(ctx)
|
|
188
|
+
results := make([]*User, len(ids))
|
|
189
|
+
|
|
190
|
+
for i, id := range ids {
|
|
191
|
+
i, id := i, id // Schleifenvariablen erfassen
|
|
192
|
+
g.Go(func() error {
|
|
193
|
+
u, err := store.GetByID(ctx, id)
|
|
194
|
+
if err != nil {
|
|
195
|
+
return err
|
|
196
|
+
}
|
|
197
|
+
results[i] = u
|
|
198
|
+
return nil
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if err := g.Wait(); err != nil {
|
|
203
|
+
return nil, err
|
|
204
|
+
}
|
|
205
|
+
return results, nil
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Worker-Pool
|
|
209
|
+
func processJobs(ctx context.Context, jobs <-chan Job) {
|
|
210
|
+
const workers = 10
|
|
211
|
+
var wg sync.WaitGroup
|
|
212
|
+
for range workers {
|
|
213
|
+
wg.Add(1)
|
|
214
|
+
go func() {
|
|
215
|
+
defer wg.Done()
|
|
216
|
+
for job := range jobs {
|
|
217
|
+
if err := process(ctx, job); err != nil {
|
|
218
|
+
slog.Error("job failed", "id", job.ID, "err", err)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}()
|
|
222
|
+
}
|
|
223
|
+
wg.Wait()
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Kontext-Weitergabe
|
|
228
|
+
```go
|
|
229
|
+
// Kontext immer als erstes Argument weitergeben
|
|
230
|
+
func (s *Service) DoWork(ctx context.Context, id string) error {
|
|
231
|
+
// ctx an alle nachgelagerten Aufrufe weitergeben
|
|
232
|
+
user, err := s.store.GetByID(ctx, id)
|
|
233
|
+
if err != nil {
|
|
234
|
+
return err
|
|
235
|
+
}
|
|
236
|
+
return s.notifier.Send(ctx, user.Email)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Anfragebezogene Werte im Kontext nur für übergreifende Belange speichern
|
|
240
|
+
// (Tracing-IDs, Auth-Info) — niemals für optionale Funktionsparameter
|
|
241
|
+
type contextKey string
|
|
242
|
+
const requestIDKey contextKey = "request_id"
|
|
243
|
+
|
|
244
|
+
func WithRequestID(ctx context.Context, id string) context.Context {
|
|
245
|
+
return context.WithValue(ctx, requestIDKey, id)
|
|
246
|
+
}
|
|
247
|
+
func GetRequestID(ctx context.Context) string {
|
|
248
|
+
id, _ := ctx.Value(requestIDKey).(string)
|
|
249
|
+
return id
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Tabellengetriebene Tests
|
|
254
|
+
```go
|
|
255
|
+
func TestGetUser(t *testing.T) {
|
|
256
|
+
tests := []struct {
|
|
257
|
+
name string
|
|
258
|
+
id string
|
|
259
|
+
want *User
|
|
260
|
+
wantErr error
|
|
261
|
+
}{
|
|
262
|
+
{"found", "123", &User{ID: "123", Email: "a@b.com"}, nil},
|
|
263
|
+
{"not found", "999", nil, ErrNotFound},
|
|
264
|
+
{"empty id", "", nil, ErrNotFound},
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
for _, tt := range tests {
|
|
268
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
269
|
+
svc := newTestService(t)
|
|
270
|
+
got, err := svc.GetUser(context.Background(), tt.id)
|
|
271
|
+
if !errors.Is(err, tt.wantErr) {
|
|
272
|
+
t.Fatalf("err = %v, want %v", err, tt.wantErr)
|
|
273
|
+
}
|
|
274
|
+
if diff := cmp.Diff(tt.want, got); diff != "" {
|
|
275
|
+
t.Errorf("mismatch (-want +got):\n%s", diff)
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Logging mit slog
|
|
283
|
+
```go
|
|
284
|
+
// Strukturiertes Logging — niemals fmt.Println in Produktionscode
|
|
285
|
+
slog.Info("request completed", "method", r.Method, "path", r.URL.Path, "status", 200, "duration_ms", elapsed.Milliseconds())
|
|
286
|
+
slog.Error("db query failed", "query", "getUserByID", "err", err)
|
|
287
|
+
|
|
288
|
+
// JSON-Logger in main einrichten
|
|
289
|
+
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
|
290
|
+
Level: slog.LevelInfo,
|
|
291
|
+
}))
|
|
292
|
+
slog.SetDefault(logger)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Beispiel
|
|
296
|
+
|
|
297
|
+
**Benutzer:** Eine Go-HTTP-API für eine To-Do-Liste bauen: Aufgaben erstellen, auflisten und abschließen. PostgreSQL-Backend, chi-Router, strukturiertes Logging, graceful Shutdown.
|
|
298
|
+
|
|
299
|
+
**Erwartete Ausgabe:**
|
|
300
|
+
- `cmd/server/main.go` — verbindet Store, Handler, startet Server mit graceful Shutdown
|
|
301
|
+
- `internal/store/task.go` — `TaskStore` mit `Create`, `List`, `Complete`-Methoden, die `database/sql` verwenden
|
|
302
|
+
- `internal/handler/tasks.go` — `TaskHandler` mit `Register(r chi.Router)`, JSON-Antworten
|
|
303
|
+
- `internal/handler/tasks_test.go` — tabellengetriebene Tests mit einem Mock-Store
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
> **Mit uns arbeiten:** Claudient wird von [Uitbreiden](https://uitbreiden.com/) unterstützt — wir bauen KI-Produkte und B2B-Lösungen mit Entwickler-Communities. [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
> 🇪🇸 Esta es la traducción en español. [Versión en inglés](../go.md).
|
|
2
|
+
|
|
3
|
+
# Skill de Go
|
|
4
|
+
|
|
5
|
+
## Cuándo activar
|
|
6
|
+
- Construir un servicio HTTP o herramienta CLI en Go
|
|
7
|
+
- Estructurar un proyecto Go con `cmd/`, `internal/`, `pkg/`
|
|
8
|
+
- Escribir manejadores HTTP con `net/http`, chi o gin
|
|
9
|
+
- Implementar interfaces, embedding y patrones de composición
|
|
10
|
+
- Escribir código concurrente con goroutines, canales y primitivas `sync`
|
|
11
|
+
- Manejo de errores, wrapping y errores centinela
|
|
12
|
+
- Escribir pruebas orientadas a tablas
|
|
13
|
+
|
|
14
|
+
## Cuándo NO usar
|
|
15
|
+
- Servicios en Node.js, Python u otros lenguajes — ecosistemas diferentes
|
|
16
|
+
- Servicios Protobuf/gRPC sin contexto previo de configuración gRPC
|
|
17
|
+
- Tareas puras de scripting — shell o Python son mejores opciones
|
|
18
|
+
|
|
19
|
+
## Instrucciones
|
|
20
|
+
|
|
21
|
+
### Estructura del proyecto
|
|
22
|
+
```
|
|
23
|
+
myapp/
|
|
24
|
+
├── cmd/
|
|
25
|
+
│ └── server/
|
|
26
|
+
│ └── main.go # Punto de entrada — ligero, solo conecta e inicia
|
|
27
|
+
├── internal/ # Paquetes privados — no importables externamente
|
|
28
|
+
│ ├── config/
|
|
29
|
+
│ ├── handler/
|
|
30
|
+
│ ├── service/
|
|
31
|
+
│ └── store/
|
|
32
|
+
├── pkg/ # Paquetes públicos — importables por otros módulos
|
|
33
|
+
│ └── apierr/
|
|
34
|
+
├── go.mod
|
|
35
|
+
└── go.sum
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### main.go — solo conexión
|
|
39
|
+
```go
|
|
40
|
+
// cmd/server/main.go
|
|
41
|
+
package main
|
|
42
|
+
|
|
43
|
+
import (
|
|
44
|
+
"context"
|
|
45
|
+
"log/slog"
|
|
46
|
+
"net/http"
|
|
47
|
+
"os"
|
|
48
|
+
"os/signal"
|
|
49
|
+
"syscall"
|
|
50
|
+
"time"
|
|
51
|
+
|
|
52
|
+
"myapp/internal/config"
|
|
53
|
+
"myapp/internal/handler"
|
|
54
|
+
"myapp/internal/store"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
func main() {
|
|
58
|
+
cfg := config.MustLoad()
|
|
59
|
+
db := store.MustConnect(cfg.DatabaseURL)
|
|
60
|
+
defer db.Close()
|
|
61
|
+
|
|
62
|
+
mux := handler.NewMux(db, cfg)
|
|
63
|
+
|
|
64
|
+
srv := &http.Server{
|
|
65
|
+
Addr: ":" + cfg.Port,
|
|
66
|
+
Handler: mux,
|
|
67
|
+
ReadTimeout: 10 * time.Second,
|
|
68
|
+
WriteTimeout: 30 * time.Second,
|
|
69
|
+
IdleTimeout: 60 * time.Second,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
go func() {
|
|
73
|
+
slog.Info("server starting", "addr", srv.Addr)
|
|
74
|
+
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
|
|
75
|
+
slog.Error("server error", "err", err)
|
|
76
|
+
os.Exit(1)
|
|
77
|
+
}
|
|
78
|
+
}()
|
|
79
|
+
|
|
80
|
+
quit := make(chan os.Signal, 1)
|
|
81
|
+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
82
|
+
<-quit
|
|
83
|
+
|
|
84
|
+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
85
|
+
defer cancel()
|
|
86
|
+
srv.Shutdown(ctx)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Manejo de errores
|
|
91
|
+
```go
|
|
92
|
+
// Definir errores centinela para condiciones esperadas
|
|
93
|
+
var (
|
|
94
|
+
ErrNotFound = errors.New("not found")
|
|
95
|
+
ErrDuplicate = errors.New("duplicate entry")
|
|
96
|
+
ErrForbidden = errors.New("forbidden")
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// Envolver errores con contexto — usar %w para preservar el unwrapping
|
|
100
|
+
func (s *UserStore) GetByID(ctx context.Context, id string) (*User, error) {
|
|
101
|
+
var u User
|
|
102
|
+
err := s.db.QueryRowContext(ctx, `SELECT id, email FROM users WHERE id = $1`, id).
|
|
103
|
+
Scan(&u.ID, &u.Email)
|
|
104
|
+
if errors.Is(err, sql.ErrNoRows) {
|
|
105
|
+
return nil, fmt.Errorf("user %s: %w", id, ErrNotFound)
|
|
106
|
+
}
|
|
107
|
+
if err != nil {
|
|
108
|
+
return nil, fmt.Errorf("user store get: %w", err)
|
|
109
|
+
}
|
|
110
|
+
return &u, nil
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// El llamador verifica el comportamiento, no los strings de mensaje
|
|
114
|
+
user, err := store.GetByID(ctx, id)
|
|
115
|
+
if errors.Is(err, ErrNotFound) {
|
|
116
|
+
http.Error(w, "not found", http.StatusNotFound)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Manejadores HTTP con chi
|
|
122
|
+
```go
|
|
123
|
+
// internal/handler/users.go
|
|
124
|
+
type UserHandler struct {
|
|
125
|
+
svc UserService
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
func (h *UserHandler) Register(r chi.Router) {
|
|
129
|
+
r.Get("/users/{id}", h.GetUser)
|
|
130
|
+
r.Post("/users", h.CreateUser)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
|
|
134
|
+
id := chi.URLParam(r, "id")
|
|
135
|
+
user, err := h.svc.GetUser(r.Context(), id)
|
|
136
|
+
if err != nil {
|
|
137
|
+
renderError(w, err)
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
renderJSON(w, http.StatusOK, user)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
func renderJSON(w http.ResponseWriter, code int, v any) {
|
|
144
|
+
w.Header().Set("Content-Type", "application/json")
|
|
145
|
+
w.WriteHeader(code)
|
|
146
|
+
json.NewEncoder(w).Encode(v)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func renderError(w http.ResponseWriter, err error) {
|
|
150
|
+
switch {
|
|
151
|
+
case errors.Is(err, ErrNotFound):
|
|
152
|
+
http.Error(w, "not found", http.StatusNotFound)
|
|
153
|
+
case errors.Is(err, ErrForbidden):
|
|
154
|
+
http.Error(w, "forbidden", http.StatusForbidden)
|
|
155
|
+
default:
|
|
156
|
+
slog.Error("internal error", "err", err)
|
|
157
|
+
http.Error(w, "internal server error", http.StatusInternalServerError)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Interfaces
|
|
163
|
+
```go
|
|
164
|
+
// Definir interfaces donde se usan (lado consumidor), no donde se implementan
|
|
165
|
+
// internal/handler/users.go
|
|
166
|
+
type UserService interface {
|
|
167
|
+
GetUser(ctx context.Context, id string) (*User, error)
|
|
168
|
+
CreateUser(ctx context.Context, req CreateUserRequest) (*User, error)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Mantener interfaces pequeñas — preferiblemente 1-3 métodos
|
|
172
|
+
// io.Reader, io.Writer son los ejemplos canónicos
|
|
173
|
+
|
|
174
|
+
// Embedding para composición
|
|
175
|
+
type ReadWriter interface {
|
|
176
|
+
io.Reader
|
|
177
|
+
io.Writer
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Patrones de concurrencia
|
|
182
|
+
```go
|
|
183
|
+
// Fan-out con errgroup
|
|
184
|
+
import "golang.org/x/sync/errgroup"
|
|
185
|
+
|
|
186
|
+
func fetchAll(ctx context.Context, ids []string) ([]*User, error) {
|
|
187
|
+
g, ctx := errgroup.WithContext(ctx)
|
|
188
|
+
results := make([]*User, len(ids))
|
|
189
|
+
|
|
190
|
+
for i, id := range ids {
|
|
191
|
+
i, id := i, id // capturar variables del bucle
|
|
192
|
+
g.Go(func() error {
|
|
193
|
+
u, err := store.GetByID(ctx, id)
|
|
194
|
+
if err != nil {
|
|
195
|
+
return err
|
|
196
|
+
}
|
|
197
|
+
results[i] = u
|
|
198
|
+
return nil
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if err := g.Wait(); err != nil {
|
|
203
|
+
return nil, err
|
|
204
|
+
}
|
|
205
|
+
return results, nil
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Pool de workers
|
|
209
|
+
func processJobs(ctx context.Context, jobs <-chan Job) {
|
|
210
|
+
const workers = 10
|
|
211
|
+
var wg sync.WaitGroup
|
|
212
|
+
for range workers {
|
|
213
|
+
wg.Add(1)
|
|
214
|
+
go func() {
|
|
215
|
+
defer wg.Done()
|
|
216
|
+
for job := range jobs {
|
|
217
|
+
if err := process(ctx, job); err != nil {
|
|
218
|
+
slog.Error("job failed", "id", job.ID, "err", err)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}()
|
|
222
|
+
}
|
|
223
|
+
wg.Wait()
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Propagación de contexto
|
|
228
|
+
```go
|
|
229
|
+
// Siempre propaga el contexto como primer argumento
|
|
230
|
+
func (s *Service) DoWork(ctx context.Context, id string) error {
|
|
231
|
+
// Pasar ctx a todas las llamadas downstream
|
|
232
|
+
user, err := s.store.GetByID(ctx, id)
|
|
233
|
+
if err != nil {
|
|
234
|
+
return err
|
|
235
|
+
}
|
|
236
|
+
return s.notifier.Send(ctx, user.Email)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Almacena valores con ámbito de solicitud en el contexto solo para preocupaciones transversales
|
|
240
|
+
// (IDs de trazado, info de autenticación) — nunca para parámetros de función opcionales
|
|
241
|
+
type contextKey string
|
|
242
|
+
const requestIDKey contextKey = "request_id"
|
|
243
|
+
|
|
244
|
+
func WithRequestID(ctx context.Context, id string) context.Context {
|
|
245
|
+
return context.WithValue(ctx, requestIDKey, id)
|
|
246
|
+
}
|
|
247
|
+
func GetRequestID(ctx context.Context) string {
|
|
248
|
+
id, _ := ctx.Value(requestIDKey).(string)
|
|
249
|
+
return id
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Pruebas orientadas a tablas
|
|
254
|
+
```go
|
|
255
|
+
func TestGetUser(t *testing.T) {
|
|
256
|
+
tests := []struct {
|
|
257
|
+
name string
|
|
258
|
+
id string
|
|
259
|
+
want *User
|
|
260
|
+
wantErr error
|
|
261
|
+
}{
|
|
262
|
+
{"found", "123", &User{ID: "123", Email: "a@b.com"}, nil},
|
|
263
|
+
{"not found", "999", nil, ErrNotFound},
|
|
264
|
+
{"empty id", "", nil, ErrNotFound},
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
for _, tt := range tests {
|
|
268
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
269
|
+
svc := newTestService(t)
|
|
270
|
+
got, err := svc.GetUser(context.Background(), tt.id)
|
|
271
|
+
if !errors.Is(err, tt.wantErr) {
|
|
272
|
+
t.Fatalf("err = %v, want %v", err, tt.wantErr)
|
|
273
|
+
}
|
|
274
|
+
if diff := cmp.Diff(tt.want, got); diff != "" {
|
|
275
|
+
t.Errorf("mismatch (-want +got):\n%s", diff)
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Logging con slog
|
|
283
|
+
```go
|
|
284
|
+
// Logging estructurado — nunca fmt.Println en código de producción
|
|
285
|
+
slog.Info("request completed", "method", r.Method, "path", r.URL.Path, "status", 200, "duration_ms", elapsed.Milliseconds())
|
|
286
|
+
slog.Error("db query failed", "query", "getUserByID", "err", err)
|
|
287
|
+
|
|
288
|
+
// Configurar logger JSON en main
|
|
289
|
+
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
|
290
|
+
Level: slog.LevelInfo,
|
|
291
|
+
}))
|
|
292
|
+
slog.SetDefault(logger)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Ejemplo
|
|
296
|
+
|
|
297
|
+
**Usuario:** Construir una API HTTP en Go para una lista de tareas: crear, listar y completar tareas. Backend PostgreSQL, router chi, logging estructurado, apagado graceful.
|
|
298
|
+
|
|
299
|
+
**Salida esperada:**
|
|
300
|
+
- `cmd/server/main.go` — conecta store, handler, inicia el servidor con apagado graceful
|
|
301
|
+
- `internal/store/task.go` — `TaskStore` con métodos `Create`, `List`, `Complete` usando `database/sql`
|
|
302
|
+
- `internal/handler/tasks.go` — `TaskHandler` con `Register(r chi.Router)`, respuestas JSON
|
|
303
|
+
- `internal/handler/tasks_test.go` — pruebas orientadas a tablas con un store mock
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
> **Trabaja con nosotros:** Claudient está respaldado por [Uitbreiden](https://uitbreiden.com/) — construimos productos de IA y soluciones B2B con comunidades de desarrolladores. [uitbreiden.com](https://uitbreiden.com/)
|