Введение: Пересмотр Традиций в Мире Go

В мире разработки на Go выбор фреймворка часто превращается в лояльность, сопоставимую с выбором между Apple и Android. Фреймворки вроде Gin, Echo и Beego долгие годы были верными спутниками программистов, помогая быстро развертывать проекты и достигать первых успехов. Они предлагали удобные инструменты и привычные шаблоны работы, но со временем становится очевидным, что у каждого свой «язык». Это создаёт трудности при переходе между ними или интеграции с другими системами.

Ситуация усугубляется, если проекты разрастаются, а масштабируемость и поддержка становятся критически важными. Здесь каждый несовместимый компонент или разнообразие подходов превращается в препятствие, требующее дополнительных ресурсов и времени. Как если бы вы каждый раз, переходя с одного фреймворка на другой, переезжали в новый дом, где даже привычный световой выключатель находится не там, куда рука тянется автоматически.

Для тех, кто хочет углубиться в историю и особенности каждого фреймворка, предлагаю ознакомиться с подробными обзорами и сравнениями:

  1. Beego: An Overview and Comparison with Other Frameworks

  2. Building Microservice Using Golang Echo Framework

  3. Go with Gin

В этой статье мы рассмотрим, почему пришло время отказаться от привычной многообразности в пользу стандартизации и как gRPC-Gateway может стать «универсальным переводчиком», способным объединить разрозненные диалекты Go-фреймворков в единый, понятный всем язык взаимодействия микросервисов.

Стандартизация, стандартизация и еще раз стандартизация

Когда речь заходит о создании серверов на Go, каждый фреймворк предлагает свой набор правил и инструментов. Изначально это кажется большим плюсом: вы можете выбрать именно то, что подходит под ваши конкретные нужды и стиль работы. Однако, представьте себе сложность, когда в одном проекте необходимо совместить модули, написанные с использованием разных фреймворков. Это сравнимо с попыткой собрать пазл, где каждая часть произведена разными производителями и не сочетается с остальными.

Примеры Gin, Echo и, не дай Бог, Beego часто всплывают в беседах разработчиков. Они, без сомнения, оказали огромное влияние на сообщество Go и поддерживали многие проекты на разных этапах их жизненного цикла. Особенно Gin, который уже более 10 лет является надёжным инструментом для многих. Но современные реалии разработки требуют гибкости и возможности масштабирования, а привязка к одному фреймворку может серьёзно ограничить обе эти характеристики.

Так, к примеру, если ваш проект начался с использования Echo, и вы хотите интегрировать стороннюю библиотеку, написанную для Gin, вы столкнетесь с необходимостью переписывания кода или создания «мостов» между различиями в API. Это не только увеличивает рабочий объём, но и вносит дополнительную сложность в поддержку и обновление кода.

Стандартизация — это ключ к решению этих проблем. Переход к использованию gRPC и HTTP/2, а также адаптация таких инструментов, как gRPC-Gateway, открывает новые горизонты в вопросах совместимости и универсальной коммуникации. Подумайте о gRPC-Gateway как об универсальном переводчике, который позволяет вам говорить на одном языке с разными системами, не вдаваясь в особенности каждой из них.

Знакоство с gRPC-Gateway

gRPC-Gateway — это магический мост, соединяющий старый мир RESTful JSON API и современный мир gRPC. Он позволяет вашим сервисам говорить на двух языках, обслуживая и традиционные HTTP запросы, и мощные gRPC вызовы. Это особенно удобно, когда у вас есть разнообразные клиенты: некоторые из них предпочитают общаться через простые HTTP запросы, в то время как другие — через gRPC.

Давайте рассмотрим жизненный пример: вы управляете веб-сервисом, который предоставляет информацию о погоде. Ваши данные — это не просто текст, это объёмные наборы данных с метеостанций. Здесь gRPC идеально подходит для пересылки этих данных с высокой скоростью и надежностью. Но ваши клиенты — это не только другие сервера, но и мобильные приложения и веб-браузеры, которые ожидают удобные и понятные JSON ответы.

Вот как gRPC-Gateway вступает в игру: он трансформирует HTTP запросы в gRPC вызовы и обратно, делая ваш сервис доступным для всех, без необходимости поддержки двух разных API. Всё это происходит за кулисами, и для конечного пользователя это выглядит как обычная работа с веб-сайтом или приложением.

Пример кода, который вы могли бы видеть без gRPC-Gateway, выглядит примерно так:

syntax = "proto3";

package your.service.v1;

option go_package = "github.com/yourorg/yourprotos/gen/go/your/service/v1";

message StringMessage {
  string value = 1;
}

service YourService {
  rpc Echo(StringMessage) returns (StringMessage) {}
}

А теперь добавим немного "магии" gRPC-Gateway:

syntax = "proto3";
package your.service.v1;
option go_package = "github.com/yourorg/yourprotos/gen/go/your/service/v1";
import "google/api/annotations.proto";

message StringMessage {
  string value = 1;
}

service YourService {
  rpc Echo(StringMessage) returns (StringMessage) {
      option (google.api.http) = {
      post: "/v1/example/echo"
      body: "*"
    };
  }
}

Эти несколько строк позволяют вам автоматически принимать HTTP POST запросы на адрес /v1/example/echo и обрабатывать их как вызовы gRPC. Это пример того, как gRPC-Gateway упрощает жизнь разработчикам, предоставляя единый, универсально понятный API.

Сочетание GRPC и HTTP серверов

Возможность одновременного использования gRPC и HTTP в одном приложении – это как иметь в своем распоряжении и спортивный автомобиль, и внедорожник; вы можете наслаждаться скоростью и мощью, когда это необходимо, и надежностью и универсальностью в другой раз. gRPC-Gateway позволяет вашему сервису легко переключаться между этими двумя "транспортами", предоставляя такую гибкость.

Давайте рассмотрим обычный рабочий процесс:

  1. HTTP запрос: Клиент отправляет HTTP запрос, который достигает gRPC-Gateway.

  2. Преобразование в gRPC: Gateway анализирует запрос, преобразует его в соответствующий gRPC вызов и перенаправляет его на gRPC сервер.

  3. Обработка gRPC сервером: gRPC сервер обрабатывает запрос и отправляет ответ обратно в Gateway.

  4. Преобразование в HTTP: Gateway переводит gRPC ответ обратно в формат, понятный HTTP клиенту, и отправляет его обратно.

Этот процесс можно представить в виде диаграммы:

В реальной жизни это выглядит следующим образом: когда пользователь запрашивает информацию через веб-интерфейс, его запрос идет через gRPC-Gateway, который транслирует его в gRPC вызов. Затем, получив ответ от gRPC сервиса, Gateway «переводит» его обратно в HTTP ответ и отправляет пользователю, обеспечивая плавный и незаметный процесс взаимодействия.

А на уровне кода, это может выглядеть так:

package main

import (
	"context"
	"log"
	"net"
	"net/http"

	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"golang.org/x/sync/errgroup"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	echo "path/to/your/protobuf_package" 
)

const (
	grpcServerEndpoint = "localhost:50051"
	httpServerEndpoint = "localhost:8080"
)

type EchoService struct {
	echo.UnimplementedEchoServiceServer
}

func (s *EchoService) Echo(ctx context.Context, in *echo.EchoRequest) (*echo.EchoResponse, error) {
	return &echo.EchoResponse{Message: "Echo: " + in.Message}, nil
}

func main() {
	g, ctx := errgroup.WithContext(context.Background())

	g.Go(func() error {
		return startGRPCServer(ctx)
	})

	g.Go(func() error {
		return startHTTPServer(ctx)
	})

	if err := g.Wait(); err != nil {
		log.Fatalf("Не удалось запустить серверы: %s", err)
	}
}

func startGRPCServer(ctx context.Context) (err error) {
	lis, err := net.Listen("tcp", grpcServerEndpoint)
	if err != nil {
		return err
	}

	s := grpc.NewServer()
	echo.RegisterEchoServiceServer(s, &EchoService{})

	go func() {
		<-ctx.Done()
		s.GracefulStop()
	}()

	log.Printf("Запуск gRPC сервера на %s", grpcServerEndpoint)
	return s.Serve(lis)
}

func startHTTPServer(ctx context.Context) (err error) {
	mux := runtime.NewServeMux()
	opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
	err = echo.RegisterEchoServiceHandlerFromEndpoint(ctx, mux, grpcServerEndpoint, opts)
	if err != nil {
		return err
	}

	srv := &http.Server{
		Addr:    httpServerEndpoint,
		Handler: mux,
	}

	go func() {
		<-ctx.Done()
		srv.Shutdown(ctx)
	}()

	log.Printf("Запуск HTTP сервера на %s", httpServerEndpoint)
	return srv.ListenAndServe()
}

Этот простой пример показывает, как в рамках одного приложения можно обеспечить работу и gRPC, и HTTP серверов, давая пользователям свободу выбора и обеспечивая разработчикам удобные инструменты для управления API.

Преимущества использования gRPC-Gateway

gRPC-Gateway не просто облегчает разработку — он раскрывает новые возможности для систем, где требуется совместимость и гибкость. Это особенно важно в современных микросервисных архитектурах, где системы должны взаимодействовать друг с другом беспрепятственно и эффективно.

Единая точка истины: одна схема для GRPC и RESTful API

Используя один .proto файл для описания как gRPC, так и RESTful API, мы получаем единство и согласованность интерфейсов. Это избавляет от необходимости поддерживать разные версии API для разных протоколов, что упрощает обновления и снижает шансы на ошибки. Если вы вносите изменения в .proto файл, они автоматически отражаются и в RESTful API, и в gRPC.

Сокращение дублирования кода и упрощение поддержки

Поскольку gRPC-Gateway транслирует запросы REST в вызовы gRPC, разработчикам не нужно писать отдельные реализации для каждого протокола. Это освобождает усилия и время на создание дополнительных фич, а не на дублирование существующей функциональности.

Автоматическая генерация клиентского кода и документации OpenAPI (Swagger)

gRPC-Gateway позволяет автоматически генерировать клиентские библиотеки для различных языков программирования и документацию API в формате OpenAPI (Swagger). Это не только ускоряет процесс разработки клиентских частей системы, но и обеспечивает актуальность документации, делая взаимодействие с API прозрачным и понятным для всех сторон.

Пример преимущества может выглядеть так: представьте, что ваша команда внедряет новую функцию в систему. Ранее это требовало бы обновления нескольких документов API и реализаций на разных языках. Теперь же, изменения вносятся в один .proto файл, и все необходимые обновления генерируются автоматически.

Эти преимущества делают gRPC-Gateway мощным инструментом, который может значительно упростить процесс разработки и поддержки микросервисных архитектур, делая их более модульными, гибкими и лёгкими в масштабировании.

Недостатки и ограничения gRPC-Gateway

Несмотря на значительные преимущества, gRPC-Gateway не лишен недостатков, особенно когда речь заходит о сложных случаях использования и интеграции с существующими системами. Рассмотрим некоторые из этих ограничений:

Ограниченная настройка и кастомизация

gRPC-Gateway прекрасно работает «из коробки» для многих стандартных сценариев, но его возможности кастомизации могут быть недостаточными для уникальных требований. Например, сложные требования к маршрутизации, тонкая настройка безопасности или специфичная обработка запросов могут потребовать дополнительных усилий и иногда написания дополнительного слоя проксирования.

Более высокий порог входа для новичков

Для эффективного использования gRPC-Gateway, разработчикам необходимо изучить не только основы REST и HTTP, но и особенности работы с gRPC и protobuf. Это может создать дополнительные трудности для новичков или команд, привыкших к более традиционным REST API.

Взаимодействие с другими инструментами и библиотеками

Интеграция с некоторыми инструментами мониторинга, логирования или трассировки может быть затруднена, так как gRPC-Gateway представляет собой ещё один уровень абстракции. Поэтому может потребоваться дополнительная настройка или поиск специализированных решений, которые совместимы с gRPC.

Недостаточный контроль над HTTP-специфичными функциями

Некоторые HTTP-специфичные возможности, такие как управление кэшированием, куками, редиректами, могут быть не так легко реализуемы через gRPC-Gateway. Это может потребовать дополнительных обходных решений или разработки дополнительного кода для контроля над этими аспектами на уровне HTTP.

Проблемы с передачей файлов

Передача файлов может быть проблематичной при использовании gRPC-Gateway. Из-за особенностей gRPC, большие файлы придется разбивать на множество маленьких частей для передачи, что может быть неэффективным и сложным в реализации. В некоторых случаях, может потребоваться более традиционный подход к обработке и передаче файлов.

Влияние на производительность

Хотя gRPC-Gateway относительно эффективен, введение дополнительного прокси-слоя может немного увеличить задержку обработки запросов. В системах, где каждая миллисекунда имеет значение, это может стать критичным фактором.

Заключение

Итак, мы разобрались, что gRPC-Gateway — это не просто ещё одна техническая штучка. Это как мостик между двумя мирами: уютным островом REST и стремительным континентом gRPC. И этот мостик может привести нас к чему-то удивительному — к API, которые проще в поддержке, быстрее в работе и дружелюбнее к разработчикам.

Немного Размышлений

Мир технологий не стоит на месте, и мы тоже. Сегодня выбор в пользу gRPC-Gateway может показаться нововведением, но завтра он уже станет частью «старой доброй классики». Так что, если вы чувствуете, что пора расширять горизонты и исследовать новые технологии, возможно, gRPC-Gateway — это именно то, что нужно.

Вот и Всё... Пока!

Не бойтесь экспериментировать и пробовать новые решения. Возможно, gRPC-Gateway не станет панацеей для всех ваших болей, но он определённо может стать одним из тех инструментов, который вы будете вспоминать с благодарностью. А что будет завтра? Кто знает, может, мы уже встретимся на новом повороте технологической эволюции!

Комментарии (8)


  1. YChebotaev
    03.06.2024 15:43

    А если серьезно, то зачем городить огород, если http+json всеми поддерживается, быстр, а с включенным сжатием догоняет бинарные форматы по размеру?


    1. ZergsLaw Автор
      03.06.2024 15:43

      Статья про то, что ты можешь использовать http+json автогенерируемый -_-


      1. YChebotaev
        03.06.2024 15:43

        Во-первых, к этому делу еще обычно swagger-документация нужна

        А во-вторых, ты же сам описываешь недостатки данного подхода. А преимущества неочевидны


        1. bBars
          03.06.2024 15:43

          Дока тоже генерится, но с помощью ещё одного генератора protoc-gen-openapiv2


  1. gohrytt
    03.06.2024 15:43
    +3

    -> Наша система тупит
    -> Открыть ворота
    -> Бизнес не готов выделить отдельный бюджет
    -> Закрыть ворота
    -> Но нам согласовали неделю на техдолг
    -> Приоткрыть ворота


  1. itmind
    03.06.2024 15:43
    +2

    Откажитесь уже наконец от gin, echo и <иной ваш фреймворк>

    Из статьи я не понял конкретных причин, по которым нужно всем отказаться от gin/echo/fiber и использовать только gRPC-Gateway.

    Допустим у меня один web-сервис с REST API. Используются middleware, такие как basic и jwt аутентификация, возврат различных http кодов ошибок, сессии, рендер шаблонов и статические файлы. Это все так же легко настраивается в gRPC-Gateway ?


  1. panter_dsd
    03.06.2024 15:43

    >Хотя gRPC-Gateway относительно эффективен, введение дополнительного прокси-слоя может немного увеличить задержку обработки запросов

    Не немножко, а достаточно существенно, ибо проц начинает жрать перегонка json туда-сюда.

    Как решение, можно делать не через проксирование http трафика на gRPC порт, а регистрировать в mux методы, которые так же генерируются автоматически, но тогда появляется проблема сбора метрик, ибо сгенерированный код не дает возможность получить uri template. Мы решили эту проблему только через патчинг сгенерированного кода, ибо все другие решения содержали слишком много минусов.


  1. n0ne
    03.06.2024 15:43

    Странное заявление какое-то...надо не от фреймворков отказываться, а приходить к единообразию. Если нужна библиотека от одного фреймворка в другом, то отказ от используемого фреймворка никак не решит проблему с библиотекой.

    Да и создавайте микросервисы на любом фреймворке, а данные гоняйте по gRPC, все счастливы.