gRPCやHTTPハンドラにおける構造と責務分離:レビューアーの境界判断

gRPCやHTTPハンドラによるAPI設計では、処理の入口にすべての責務が集中しがちで、
バリデーションから永続化、業務ルールまでを1関数に押し込めるようなコードはよく見かけます。

レビューアーは、構造と責務を明確に分離した設計がなされているかを見抜く必要があります。
この記事では、技術的な構造から適切な責務分離の判断軸、レビューでの観点まで一貫して解説します。


1. ハンドラ肥大化の実態とリスク

func (s *UserServer) RegisterUser(ctx context.Context, req *pb.RegisterRequest) (*pb.RegisterResponse, error) {
    if req.Name == "" || !strings.Contains(req.Email, "@") {
        return nil, status.Error(codes.InvalidArgument, "invalid input")
    }

    hashed, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)

    user := model.User{Name: req.Name, Email: req.Email, PasswordHash: string(hashed)}
    if err := s.repo.Save(user); err != nil {
        return nil, status.Error(codes.Internal, "failed to save")
    }

    return &pb.RegisterResponse{Success: true}, nil
}
@Reviewer
業務ロジック(バリデーション、ハッシュ化、永続化)までがHandler内に混在しています。ハンドラは入力検証とユースケース呼び出しに留め、処理責務は層ごとに分離してください。
指摘ポイント
  • リクエスト検証、パスワードハッシュ化、ドメイン構造作成、永続化処理がすべて同居
  • ハンドラ層の責務を超え、業務ロジックやインフラ処理まで侵食している
  • テストが困難になり、責務単位の変更がしにくくなる

2. 責務分離された理想構造

UML Diagram

3. ハンドラに混入しやすい処理と適切な配置

処理種別 本来の位置 説明
リクエスト形式チェック Handler フィールドの有無や形式など、仕様的な検査
ドメイン制約(業務ルール) UseCase 業務ロジック上の条件(例:年齢、メール形式)
データ変換 UseCase DTOをドメインモデルに変換し整合性を保つ
DB操作 Repository 永続化の責務は専用層に任せ、インフラと切り離す

「形式の妥当性チェック」と「業務ルールの検証」は、責務の分岐点です。
どちらも“バリデーション”と呼ばれがちなので、レビューでは目的の違いに注意してください。


4. 改善された実装構造(責務ごとの分離)

func (s *UserServer) RegisterUser(ctx context.Context, req *pb.RegisterRequest) (*pb.RegisterResponse, error) {
    if err := validateRequest(req); err != nil {
        return nil, status.Error(codes.InvalidArgument, err.Error())
    }

    input := convertToInput(req)

    if err := s.usecase.RegisterUser(ctx, input); err != nil {
        return nil, status.Error(codes.Internal, err.Error())
    }

    return &pb.RegisterResponse{Success: true}, nil
}
@Reviewer
Handlerの責務が適切に限定されています。context制御・ログ出力もこの層で完結しているか確認してください。
ユースケースに業務ロジックを集約した良例
func (u *UserUseCase) RegisterUser(ctx context.Context, input RegisterInput) error {
    if !strings.Contains(input.Email, "@") {
        return errors.New("invalid email format")
    }

    hashed, err := hashPassword(input.Password)
    if err != nil {
        return err
    }

    user := User{Name: input.Name, Email: input.Email, PasswordHash: hashed}
    return u.repo.Save(user)
}
共通化方針の明示

同様の処理が今後複数のユースケースで発生する場合に備え、現時点で共通化の方針(ヘルパー切り出し等)をドキュメントに残しておくと、拡張時の判断がしやすくなります。


5. レビュー観点リスト:gRPC/HTTP Handlerの設計健全性

観点 確認ポイント
責務の集中度 Handlerが複数の関心ごとを抱えていないか
層の分離 UseCaseやRepositoryの責務を侵食していないか
UseCaseの独立性 単体でテスト可能な構造に分離されているか
Handlerの中立性 制御だけに徹しており、ドメインルールを含んでいないか
contextの扱い contextキャンセルなどをUseCaseで扱っていないか

6. 補足設計指針:境界ルールの徹底

  • contextキャンセル制御はHandler層で完結させる
  • ログ出力もHandler側で統一し、ユースケース層では行わない
  • UseCaseは処理成功/失敗の判定のみ責任を持ち、レスポンス構築はHandlerに任せる

Handlerが責務過多になった結果、業務ルール・永続化・応答形式まで抱える設計は、変更容易性も再利用性も著しく下がります。
責務の越境がないか、層ごとにレビューする習慣が重要です。


7. まとめ:構造の守護者としてのレビュー視点

gRPCやHTTPのハンドラ設計において、構造と責務の分離は見た目の正しさ以上に、変更耐性と運用コストを左右します。

レビューアーとして以下の観点を問い続けてください:

  • その処理は本当にこの層の責任ですか?
  • テストしやすい構造になっていますか?
  • 構造が意図を語ってくれていますか?

責務境界を守るレビューは、目に見えない構造の健全性を将来の開発者へ引き継ぐ作業でもあります。