Как организовать обмен данными между микросервисами с использованием Protobuf?

Ответ

Protobuf (Protocol Buffers) — это бинарный формат сериализации данных от Google. Он эффективен по скорости и размеру данных, что делает его отличным выбором для межсервисного взаимодействия.

Основные способы организации обмена:

  1. gRPC (рекомендуемый способ)

    gRPC — это фреймворк для удалённого вызова процедур (RPC), который использует Protobuf по умолчанию. Это нативный и наиболее эффективный способ.

    • Как работает: Вы описываете сервисы и сообщения в .proto файлах. Генератор кода создаёт клиентские и серверные заготовки на Go.
    • Преимущества: Строгая типизация, высокая производительность, поддержка стриминга, автоматическая сериализация/десериализация.
    // Пример запуска gRPC сервера
    import (
        "google.golang.org/grpc"
        "net"
        pb "path/to/your/proto/package"
    )
    
    func main() {
        lis, err := net.Listen("tcp", ":50051")
        // ... обработка ошибки
        s := grpc.NewServer()
        pb.RegisterYourServiceServer(s, &myServer{})
        s.Serve(lis)
    }
  2. HTTP + Protobuf (бинарный)

    Можно передавать бинарные Protobuf-сообщения через обычный HTTP. Это быстрее, чем JSON, но требует от клиентов умения работать с бинарным форматом.

    • Как работает: Сервер и клиент договариваются об использовании Content-Type: application/protobuf.
    • Сериализация: data, err := proto.Marshal(myMessage)
    • Десериализация: err := proto.Unmarshal(bodyBytes, myMessage)
  3. HTTP + JSON (для совместимости)

    Для взаимодействия с браузерами или системами, которые работают только с JSON, Protobuf-сообщения можно конвертировать в JSON и обратно. Для этого используется специальный пакет protojson.

    import "google.golang.org/protobuf/encoding/protojson"
    
    // Сериализация в JSON
    jsonBytes, err := protojson.Marshal(myMessage)
    
    // Десериализация из JSON
    err = protojson.Unmarshal(jsonBytes, myMessage)

Управление .proto файлами

Ключевая задача — обеспечить использование одних и тех же версий .proto файлов во всех микросервисах. Лучшая практика — вынести их в отдельный Git-репозиторий и подключать его к проектам микросервисов через Git-сабмодули или использовать системы управления зависимостями, такие как Buf.

Лучшие практики:

  • Версионирование: Включайте версию API в имя пакета Protobuf (например, package myapi.v1;).
  • Обратная совместимость: При изменении .proto файлов не удаляйте поля и не меняйте их номера, чтобы сохранить обратную совместимость.
  • Интерцепторы (Interceptors) в gRPC: Используйте их для сквозной функциональности: логирования, сбора метрик, аутентификации.

Ответ 18+ 🔞

А, ну вот, протоколы, сериализация... Ёперный театр, опять эти ваши бинарные форматы! Ну ладно, слушай сюда, раз уж спросил.

Protobuf, блядь, — это такая штука от Гугла, которая данные твои в бинарный вид упаковывает. Быстро, компактно, прям как чемодан у бабки в автобусе — всё впихнули, ничего не торчит. Идеально, чтобы сервисы между собой болтали, не тратя время на ерунду.

Как этим добром обмениваться? Вариантов — овердохуища!

  1. gRPC (самый правильный путь, без вариантов)

    Это типа как RPC, только на стероидах. Всё из коробки, по уму. Ты описываешь в .proto-файле, что у тебя за сервис и какие у него сообщения, а потом магическим кодогенератором на Go всё это превращается в готовые заглушки. Красота, блядь!

    • Что хорошего: Типы проверяются, летает всё как угорелое, стриминг поддерживает, и тебе даже думать не надо, как это сериализовать — всё само.
      // Ну вот, смотри, как сервер gRPC поднять
      import (
      "google.golang.org/grpc"
      "net"
      pb "path/to/your/proto/package"
      )
      func main() {
      lis, err := net.Listen("tcp", ":50051")
      // ... ошибку обработай, конечно, а то пиздец будет
      s := grpc.NewServer()
      pb.RegisterYourServiceServer(s, &myServer{})
      s.Serve(lis) // И поехали!
      }
  2. HTTP, но с бинарным Protobuf (для любителей поизвращаться)

    Можно и просто по HTTP гонять эти бинарные сообщения. Быстрее JSON, конечно, но клиент должен быть не лыком шит и понимать, что ему в ответ прилетает не текст, а какая-то бинарная каша.

    • Как: Договариваешься с клиентом, что Content-Type будет application/protobuf. Всё, дальше магия:
    • Упаковать: data, err := proto.Marshal(myMessage)
    • Распаковать: err := proto.Unmarshal(bodyBytes, myMessage)
  3. HTTP + JSON (для тех, у кого всё через жопу)

    Ну а если твой клиент — это какой-нибудь древний браузер или система, которая только JSON и жуёт? Без паники! Можно конвертнуть Protobuf в JSON. Для этого есть специальная либа protojson. Но это уже не та скорость, конечно.

    import "google.golang.org/protobuf/encoding/protojson"
    // В JSON
    jsonBytes, err := protojson.Marshal(myMessage)
    // Из JSON обратно
    err = protojson.Unmarshal(jsonBytes, myMessage)

А теперь про самое важное — .proto файлы!

Вот тут, блядь, собака порылась. Главная задача — чтобы во всех твоих микросервисах была одна и та же хуйня в этих файлах. Идеальный вариант — выкинуть их в отдельный репозиторий Git и таскать оттуда, как зависимость. Через сабмодули или, что ещё лучше, через специальные инструменты вроде Buf. Иначе будет бардак, волнение ебать, и все друг друга перестанут понимать.

Чтобы не было пиздеца, запомни:

  • Версии — в пакет! Пиши в названии пакета версию: package myapi.v1;. Потом, когда всё сломаешь, будет myapi.v2.
  • Не ломай старое! Меняешь .proto файл — не удаляй старые поля и не меняй им номера. Иначе твой старый сервис посмотрит на новое сообщение и просто охуеет.
  • Перехватчики (Interceptors) в gRPC — твои друзья. Через них удобно логирование, метрики или проверку прав впиливать. Один раз написал — работает везде. Удобно, блядь!

Вот и вся наука. Главное — не выёбывайся и используй gRPC, где можно. А то нагородишь HTTP-эндпоинтов с JSON, а потом будешь разбираться, почему всё тормозит.