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

gRPCやHTTPハンドラを用いたAPI設計では、リクエスト処理の境界でロジックが集中する傾向がある。
このようなコードをレビューする際、ハンドラの中で完結してよい処理と、分離すべき処理の判断基準が重要になる。

1. ハンドラ肥大化問題の実態

現場ではよく以下のようなコードが見られる:

handlerにロジック集中
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
}
Comment
@Reviewer: リクエスト検証・ハッシュ処理・DB保存など複数の責務が1関数に詰め込まれている。  
ハンドラは"エントリーポイント"に徹し、業務ロジックは別の層に委譲する設計が望ましい。

2. PlantUMLで見る理想構造

UML Diagram
Comment
@Reviewer: このような分離構造により、各層の責務が明確化し、テスト容易性と再利用性が向上する。  
特に、UseCase層は**Handlerとデータ層の中間**として、変換・検証・ルール処理を担う。

3. ハンドラに詰め込みがちな処理リスト

責務カテゴリ 本来の位置 説明
リクエストバリデーション Handler gRPC/HTTP仕様に密接
ドメイン制約チェック UseCase層 業務ルールに基づくチェック(例:年齢制限)
データ変換(DTO → Entity) UseCase層 モデルとの整合性確保
ビジネスロジック UseCase層 責務単位でテスト可能に
DBアクセス Repository 分離し、インフラ依存を限定化
Comment
@Reviewer: バリデーションとドメイン制約は混同されやすい。  
APIの形式的チェックと、業務的制約の判断ポイントを明確にする。

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
}
UseCase層での業務処理
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)
}
Comment
@Reviewer: Handler → UseCase → Repositoryの流れが明確で、各層が責務に集中できる構造に。  
この構造なら、UseCase単体テスト・Mock注入が容易で、テスト範囲も明瞭。

5. レビュー観点リスト:Handlerの構造健全性

観点 確認ポイント
コントローラの粒度 複数の責務を1関数に詰め込んでいないか?
層の責務 バリデーション・ドメインチェック・保存処理が正しい層にあるか?
テスト可能性 UseCase単体でモックテストできる構造か?
構造の再利用性 Handlerから切り出した関数は他でも使えるか?
非同期処理 contextのキャンセル・タイムアウト考慮されているか?

6. その他設計上の注意点

  • contextの扱いはUseCaseに委譲しない。キャンセルはHandlerで把握すべき。
  • レスポンスの構築はHandlerでまとめる(UseCaseは成功/失敗だけ返す構造)。
  • ログ出力も原則Handlerで制御。UseCaseは業務に集中させる。
Comment
@Reviewer: 層の境界が崩れると、テスト困難・変更困難・責任不明瞭という三重苦になる。  
ハンドラ層は「受け取って、整えて、渡す」が主な役割。

7. まとめ:責務を正しく見抜くレビュー視点を持つ

gRPC/HTTPのハンドラは、システム外部との接点でありながら、つい実装者の手癖で“全部やってしまう”危険地帯でもある。
レビューアーはこの構造的境界に注目し、以下を問いかけるべきである:

  • ハンドラが責務を持ちすぎていないか?
  • UseCaseに任せるべき業務ルールが埋もれていないか?
  • テストや再利用を阻害する設計になっていないか?

責務の正しい分離は設計の健全性と保守性を支えるレビューの柱であり、コードを読んで終わるのではなく、構造の背後にある意図と癖まで見抜くことが求められる。