Implementing Unary RPCs

1. Creating Server Handler

Example: Unary handler

func (s *server) GetUser(ctx context.Context, r *userv1.GetUserRequest) (*userv1.User, error) {
    if r.GetId() == "" {
        return nil, status.Error(codes.InvalidArgument, "id is required")
    }
    u, err := s.repo.Find(ctx, r.GetId())
    if errors.Is(err, repo.ErrNotFound) {
        return nil, status.Error(codes.NotFound, "user not found")
    }
    if err != nil { return nil, status.Errorf(codes.Internal, "db: %v", err) }
    return u, nil
}
RuleDetail
Signature(ctx, *Req) (*Res, error)
Always return status errorsUse status.Error / Errorf
Honor ctxPass to downstream calls

2. Receiving Request Context

From ctxAPI
Deadlinectx.Deadline()
Cancellationctx.Done()
Metadatametadata.FromIncomingContext(ctx)
Peer infopeer.FromContext(ctx)

3. Accessing Request Fields

APIDetail
r.GetX()Nil-safe getter — preferred
r.XDirect access — panics on nil request
Presence (optional)r.X != nil for messages / wrappers

4. Returning Response

PatternDetail
Successreturn &Res{...}, nil
Failurereturn nil, status.Error(code, msg)
Partial responseAvoid — return either or use streaming

5. Returning Errors

Example: Rich error details

st := status.New(codes.InvalidArgument, "bad email")
st, _ = st.WithDetails(&errdetails.BadRequest_FieldViolation{
    Field: "email", Description: "not RFC 5322",
})
return nil, st.Err()
Common CodeUse
InvalidArgumentBad input
NotFoundMissing resource
AlreadyExistsDuplicate creation
PermissionDeniedAuthZ failure
UnauthenticatedNo/invalid creds
InternalUnexpected server error

6. Validating Request Data

ApproachDetail
Inline checksSimple, explicit; clutters handler
protovalidateCEL rules in .proto
Validation interceptorCentralized — calls Validate() automatically

7. Creating Client Call

Example: Client unary call

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
u, err := client.GetUser(ctx, &userv1.GetUserRequest{Id: "u1"})
if err != nil {
    st, _ := status.FromError(err)
    log.Printf("%s: %s", st.Code(), st.Message())
    return
}

8. Setting Call Options

OptionUse
grpc.WaitForReady(true)Queue until conn ready
grpc.MaxCallRecvMsgSize(n)Per-call recv limit
grpc.UseCompressor("gzip")Enable per-call compression
grpc.Header(&md)Capture response headers
grpc.Trailer(&md)Capture trailers

9. Handling Client Response

PatternDetail
Always check errDon't read response on error
Use status.FromErrorExtract code + message + details
Idempotent retriesOnly on Unavailable/DeadlineExceeded for safe ops

10. Using Context Cancellation

Example: Cancel mid-flight

ctx, cancel := context.WithCancel(context.Background())
go func() { time.Sleep(100 * time.Millisecond); cancel() }()
_, err := client.SlowOp(ctx, req) // err has codes.Canceled
BehaviorDetail
Server seesctx.Done() closes, ctx.Err() == context.Canceled
Client receivescodes.Canceled