Унарный вызов - один запрос и один ответ. Это основа для всех остальных режимов.
Сервер
type orderServer struct {
ordersv1.UnimplementedOrderServiceServer
store Store
}
func (s *orderServer) GetOrder(ctx context.Context, req *ordersv1.GetOrderRequest) (*ordersv1.Order, error) {
order, err := s.store.Get(ctx, req.GetId())
if err != nil {
return nil, err
}
return &ordersv1.Order{Id: order.ID, Status: order.Status}, nil
}
Сервер регистрируется на отдельном net.Listener через grpc.NewServer(). Метод принимает контекст и возвращает сообщение или ошибку.
Клиент
conn, err := grpc.NewClient(target, grpc.WithTransportCredentials(insecure.NewCredentials())
if err != nil {
return err
}
defer conn.Close()
ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
defer cancel()
order, err := client.GetOrder(ctx, &ordersv1.GetOrderRequest{Id: id})
NewClient не означает успешный вызов: соединение создаётся при обращении. Дедлайн всегда ограничен, а контекст пробрасывается до репозитория и внешних вызовов.
Для production используют TLS вместо insecure.
Ошибки
Ошибку хранилища преобразуют в код gRPC, а не возвращают как обычный fmt.Errorf без смысла. Клиент читает её через status.Code(err) и status.Convert(err).Details().
Регистрация и путь вызова
Серверная реализация сама по себе ничего не слушает. Её нужно зарегистрировать в gRPC-сервере и только потом запускать listener:
lis, err := net.Listen("tcp", ":9091")
if err != nil {
return err
}
server := grpc.NewServer(grpc.UnaryInterceptor(authInterceptor))
ordersv1.RegisterOrderServiceServer(server, &orderServer{store: store})
return server.Serve(lis)
При старте проверяют и регистрацию, и реальный вызов через порт: забытый Register...Server иначе обнаруживается только как Unimplemented у клиента.
Коротко
- Сервер реализует метод интерфейса, клиент вызывает его как обычный метод.
- Дедлайн обязателен, контекст не теряется.
- TLS настраивается отдельно, insecure только для локального стенда.
- Ошибки преобразуются в gRPC-коды явно.