mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-18 23:03:06 +08:00
Add remaining files to match zh-CN documentation structure: - hooks/README.md — hooks architecture and customization guide - examples/ — 8 project CLAUDE.md templates (general, user, django, go, harmonyos, laravel, rust, saas-nextjs) - CHANGELOG.md — version history - the-openclaw-guide.md — OpenClaw guide (471 lines) Total: 11 files, 2362 insertions ja-JP now has full parity with zh-CN directory structure.
8.9 KiB
8.9 KiB
Go マイクロサービス — プロジェクト CLAUDE.md
PostgreSQL、gRPC、Dockerを使用したGoマイクロサービスの実世界サンプル。 これをプロジェクトのルートにコピーしてサービスに合わせてカスタマイズしてください。
プロジェクト概要
スタック: Go 1.22+, PostgreSQL, gRPC + REST (grpc-gateway), Docker, sqlc (型安全SQL), Wire (依存性注入)
アーキテクチャ: ドメイン、リポジトリ、サービス、ハンドラーレイヤーを持つクリーンアーキテクチャ。gRPCをプライマリトランスポートとし、外部クライアント向けにRESTゲートウェイを提供。
重要なルール
Go の規約
- Effective Goと Go Code Review Comments ガイドに従う
- エラーのラッピングには
errors.New/fmt.Errorfに%wを使用 — エラーに対する文字列マッチングは禁止 init()関数は使用しない —main()またはコンストラクターで明示的に初期化する- グローバルな可変状態は使用しない — コンストラクター経由で依存関係を渡す
- コンテキストは最初のパラメーターにし、すべてのレイヤーを通じて伝播させること
データベース
- すべてのクエリは
queries/にプレーンSQLとして記述 — sqlcが型安全なGoコードを生成 migrations/のマイグレーションはgolang-migrateを使用 — データベースを直接変更しない- 複数ステップの操作には
pgx.Txを使用してトランザクションを使用する - すべてのクエリはパラメータ化プレースホルダー(
$1,$2)を使用 — 文字列フォーマットは禁止
エラーハンドリング
- パニックしない、エラーを返す — パニックは本当に回復不可能な状況のみ
- コンテキストと共にエラーをラップする:
fmt.Errorf("creating user: %w", err) - ビジネスロジック用のセンチネルエラーを
domain/errors.goに定義する - ハンドラーレイヤーでドメインエラーをgRPCステータスコードにマップする
// ドメインレイヤー — センチネルエラー
var (
ErrUserNotFound = errors.New("user not found")
ErrEmailTaken = errors.New("email already registered")
)
// ハンドラーレイヤー — gRPCステータスにマップ
func toGRPCError(err error) error {
switch {
case errors.Is(err, domain.ErrUserNotFound):
return status.Error(codes.NotFound, err.Error())
case errors.Is(err, domain.ErrEmailTaken):
return status.Error(codes.AlreadyExists, err.Error())
default:
return status.Error(codes.Internal, "internal error")
}
}
コードスタイル
- コードやコメントに絵文字を使用しない
- エクスポートされた型と関数にはドキュメントコメントが必要
- 関数は50行以内に収める — ヘルパーを抽出する
- 複数のケースを持つすべてのロジックにはテーブル駆動テストを使用する
- シグナルチャンネルには
boolではなくstruct{}を優先する
ファイル構成
cmd/
server/
main.go # エントリーポイント、Wire注入、グレースフルシャットダウン
internal/
domain/ # ビジネス型とインターフェース
user.go # ユーザーエンティティとリポジトリインターフェース
errors.go # センチネルエラー
service/ # ビジネスロジック
user_service.go
user_service_test.go
repository/ # データアクセス(sqlc生成 + カスタム)
postgres/
user_repo.go
user_repo_test.go # testcontainersを使用した統合テスト
handler/ # gRPC + RESTハンドラー
grpc/
user_handler.go
rest/
user_handler.go
config/ # 設定の読み込み
config.go
proto/ # Protobuf定義
user/v1/
user.proto
queries/ # sqlc用SQLクエリ
user.sql
migrations/ # データベースマイグレーション
001_create_users.up.sql
001_create_users.down.sql
主要なパターン
リポジトリインターフェース
type UserRepository interface {
Create(ctx context.Context, user *User) error
FindByID(ctx context.Context, id uuid.UUID) (*User, error)
FindByEmail(ctx context.Context, email string) (*User, error)
Update(ctx context.Context, user *User) error
Delete(ctx context.Context, id uuid.UUID) error
}
依存性注入付きサービス
type UserService struct {
repo domain.UserRepository
hasher PasswordHasher
logger *slog.Logger
}
func NewUserService(repo domain.UserRepository, hasher PasswordHasher, logger *slog.Logger) *UserService {
return &UserService{repo: repo, hasher: hasher, logger: logger}
}
func (s *UserService) Create(ctx context.Context, req CreateUserRequest) (*domain.User, error) {
existing, err := s.repo.FindByEmail(ctx, req.Email)
if err != nil && !errors.Is(err, domain.ErrUserNotFound) {
return nil, fmt.Errorf("checking email: %w", err)
}
if existing != nil {
return nil, domain.ErrEmailTaken
}
hashed, err := s.hasher.Hash(req.Password)
if err != nil {
return nil, fmt.Errorf("hashing password: %w", err)
}
user := &domain.User{
ID: uuid.New(),
Name: req.Name,
Email: req.Email,
Password: hashed,
}
if err := s.repo.Create(ctx, user); err != nil {
return nil, fmt.Errorf("creating user: %w", err)
}
return user, nil
}
テーブル駆動テスト
func TestUserService_Create(t *testing.T) {
tests := []struct {
name string
req CreateUserRequest
setup func(*MockUserRepo)
wantErr error
}{
{
name: "valid user",
req: CreateUserRequest{Name: "Alice", Email: "alice@example.com", Password: "secure123"},
setup: func(m *MockUserRepo) {
m.On("FindByEmail", mock.Anything, "alice@example.com").Return(nil, domain.ErrUserNotFound)
m.On("Create", mock.Anything, mock.Anything).Return(nil)
},
wantErr: nil,
},
{
name: "duplicate email",
req: CreateUserRequest{Name: "Alice", Email: "taken@example.com", Password: "secure123"},
setup: func(m *MockUserRepo) {
m.On("FindByEmail", mock.Anything, "taken@example.com").Return(&domain.User{}, nil)
},
wantErr: domain.ErrEmailTaken,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := new(MockUserRepo)
tt.setup(repo)
svc := NewUserService(repo, &bcryptHasher{}, slog.Default())
_, err := svc.Create(context.Background(), tt.req)
if tt.wantErr != nil {
assert.ErrorIs(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
}
})
}
}
環境変数
# データベース
DATABASE_URL=postgres://user:pass@localhost:5432/myservice?sslmode=disable
# gRPC
GRPC_PORT=50051
REST_PORT=8080
# 認証
JWT_SECRET= # 本番環境ではvaultから読み込む
TOKEN_EXPIRY=24h
# オブザーバビリティ
LOG_LEVEL=info # debug, info, warn, error
OTEL_ENDPOINT= # OpenTelemetryコレクター
テスト戦略
/go-test # GoのTDDワークフロー
/go-review # Go固有のコードレビュー
/go-build # ビルドエラーの修正
テストコマンド
# ユニットテスト(高速、外部依存なし)
go test ./internal/... -short -count=1
# 統合テスト(testcontainers用にDockerが必要)
go test ./internal/repository/... -count=1 -timeout 120s
# カバレッジ付きすべてのテスト
go test ./... -coverprofile=coverage.out -count=1
go tool cover -func=coverage.out # サマリー
go tool cover -html=coverage.out # ブラウザ
# レースディテクター
go test ./... -race -count=1
ECCワークフロー
# 計画
/plan "Add rate limiting to user endpoints"
# 開発
/go-test # Go固有パターンでのTDD
# レビュー
/go-review # Goのイディオム、エラーハンドリング、並行処理
/security-scan # シークレットと脆弱性
# マージ前
go vet ./...
staticcheck ./...
Git ワークフロー
feat:新機能、fix:バグ修正、refactor:コード変更mainからフィーチャーブランチを切り、PRが必要- CI:
go vet,staticcheck,go test -race,golangci-lint - デプロイ: CIでDockerイメージをビルドし、Kubernetesにデプロイ