gRPC
Use gRPC when strongly typed RPC contracts, streaming, or cross-service schema discipline clearly help.
Canonical guidance
- choose gRPC when typed RPC contracts or streaming are central
- keep handlers small and push business logic into ordinary Go packages
- propagate deadlines and cancellation through service calls
- avoid letting generated types leak everywhere unless the boundary is intentionally proto-shaped
Use when
- internal RPC between services
- bidirectional or server streaming
- strong schema contracts matter
Avoid
- using gRPC by default for simple public HTTP APIs
- burying domain logic in generated-service layers
- ignoring deadlines on outbound work
Preferred pattern
type UserService struct {
pb.UnimplementedUserServiceServer
Store Store
}
func (s *UserService) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
user, err := s.Store.Get(ctx, req.Id)
if err != nil {
return nil, err
}
return &pb.GetUserResponse{Id: user.ID, Name: user.Name}, nil
}
Anti-pattern
- generated handlers that also own persistence, retries, and orchestration logic
Explanation: This anti-pattern is tempting because gRPC stubs are already there, but it turns transport code into the whole application boundary.
Why
- gRPC works best when transport contracts stay clean and domain logic stays ordinary
Related pages
Sources
- Basics tutorial - gRPC Authors
- Quick start - gRPC Authors