everything-claude-code/docs/ja-JP/examples/go-microservice-CLAUDE.md
Claude 5a5a47e710 docs: add missing Japanese translations to complete zh-CN parity (ja-JP)
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.
2026-05-17 02:31:40 -04:00

8.9 KiB
Raw Blame History

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にデプロイ