Построение микросервисной архитектуры на Golang и gRPC, часть 1

Введение в микросервисную архитектуру

 

Адаптация статей Ewan Valentine.

 

Это серия из десяти частей, я постараюсь раз в месяц писать про построение микросервисов на Golang. Я буду использовать protobuf и gRPC в качестве основного транспортного протокола.

 

Стек, который я использовал: golang, mongodb, grpc, docker, Google Cloud, Kubernetes, NATS, CircleCI, Terraform и go-micro.

 

Зачем мне это? Поскольку мне потребовалось много времени, чтобы разобраться в этом и решить накопившиеся проблемы. Так же я хотел поделиться с вами тем, что я узнал о создании, тестировании и развертывании микросервисов на Go и другие новые технологии.

 

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

 

  • грузы
  • инвентарь
  • суда
  • пользователи
  • роли
  • аутентификация

 

image

Что бы идти далее, вам нужно установить Golang и необходимые библиотек, а так же создать git репозиторий (надеюсь вам не составит труда разобраться, иначе я рекомендую сначала разобраться с основами).

 

Теория

 

Что такое микросервисная архитектура?

 

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

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

 

Почему Golang?

 

Микросервисы поддерживаются практически всеми языками, в конце концов, микросервисы представляют собой концепцию, а не определенную структуру или инструмент. При этом некоторые языки лучше подходят и, кроме того, имеют лучшую поддержку для микросервисов, чем другие. Одним языком с большой поддержкой является Golang.

 

Познакомимся с protobuf/gRPC

 

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

 

Что бы решить проблему связи, мы можем использовать традиционный подход REST и передавать данные в формате JSON или XML через HTTP. Но у такого подхода есть свои минусы, например то что перед отправкой сообщения придётся кодировать свои данные, а на стороне принимающей декодировать обратно. А это накладные расходы и увеличивает сложность кода.

 

Есть решение! Это протокол gRPC — лёгкий, основанный на двоичном коде, что исключает передачу заголовков HTTP, и это сэкономит нам некоторое количество байт. Так же будущий протокол HTTP2 подразумевает использование двоичных данных, что снова говорит в пользу gRPC. HTTP2 позволяет вести двунаправленную связь, и это круто!

 

Так же gRPC позволяет определить интерфейс к вашему сервису в дружеском формате — это > protobuf.

 

Практика

 

Создадим файл /project/consigment.proto.
Официальная документация protobuf

 

consigment.proto

 

Это простой пример, который содержит сервис который вы хотите предоставить другим сервисам: service ShippingService, затем мы определим свои сообщения. Protobuf статически типизированный протокол, и мы можем создавать пользовательские типы (похоже на структуры в golang). Здесь контейнер, вложен в партию.

 

Установим библиотеки, компилятор и скомпилируем наш протокол:

 

$ go get -u google.golang.org/grpc
$ go get -u github.com/golang/protobuf/protoc-gen-go
$ sudo apt install protobuf-compiler
$ mkdir consignment && cd consignment
$ protoc -I=. --go_out=plugins=grpc:. consignment.proto

 

На выходе должен получиться файл:

 

consignment.pb.go

 

Это код, который автоматически генерируется библиотеками gRPC / protobuf, чтобы вы могли связать свое определение protobuf с вашим собственным кодом.

 

Напишем main.go

 

main.go

 

Пожалуйста, внимательно прочитайте комментарии, оставленные в коде. Судя по всему, здесь мы создаем логику реализации, в которой наши методы gRPC взаимодействуют, используя сгенерированные форматы, создавая новый сервер gRPC на порту 50051. Теперь там будет жить наш gRPC сервис.
Вы можете запустить это с помощью $ go run main.go, но вы ничего не увидите, и вы не сможете его использовать… Итак, давайте создадим клиента, чтобы увидеть его в действии.

 

Давайте создадим интерфейс командной строки, который возьмет файл JSON и будет взаимодействовать с нашей службой gRPC.

 

В корневом каталоге создайте новый подкаталог $ mkdir consignment-cli. В этом каталоге создайте файл cli.go со следующим содержимым:

 

cli.go

 

Теперь создайте партию (consignment-cli / consignment.json):

 

{
  "description": "Тестовая партия груза",
  "weight": 100,
  "containers": [
    {
      "customer_id": "Заказчик_001",
      "user_id": "Пользователь_001",
      "origin": "Порт Находка"
    }
  ],
  "vessel_id": "судно_001"
}

 

Теперь, если вы запустите $ go run main.go из пакета seaport, а затем в отдельной панели терминала запустите $ go run cli.go. Вы должны увидеть сообщение «Created: true».
Но как мы можем проверить, что именно было создано? Давайте обновим нашу службу с помощью метода GetConsignments, чтобы мы могли просматривать все наши созданные партии.

 

consigment.proto

 

Итак, здесь мы создали новый метод на нашем сервисе под названием GetConsignments, мы также создали новый GetRequest, который пока не содержит ничего. Мы также добавили поле отправленных партий в наше ответное сообщение. Вы заметите, что тип здесь имеет ключевое слово repeated до типа. Это, как вы, наверное, догадались, просто означает рассматривать это поле как массив этих типов.

 

Не спешите запускать программу, реализация наших методов gRPC основана на сопоставлении интерфейса, созданного библиотекой protobuf, нам необходимо убедиться, что наша реализация соответствует нашему proto определению.

 

//seaport/main.go
//IRepository - интерфейс хранилища
type IRepository interface {
    Create(*pbf.Consignment) (*pbf.Consignment, error)
    GetAll() []*pbf.Consignment
}
//GetAll - метод получения всех партий из хранилища
func (repo *Repository) GetAll() []*pbf.Consignment {
    return repo.consignments
}
//GetConsignments - метод для получения всех партий из ответа сервера
func (s *service) GetConsignments(ctx context.Context, req *pbf.GetRequest) (*pbf.Response, error) {
    consignments := s.repo.GetAll()
    return &pbf.Response{Consignments: consignments}, nil
}

 

Здесь мы включили наш новый метод GetConsignments, обновили наше хранилище и интерфейс, соответственно созданным в определении consignments.proto. Если вы снова запустите $ go run main.go, то программа должно снова заработать.

 

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

 

cli.go

 

Добавим код выше в cli.go и снова запустите $ go run cli.go. Клиент запустит CreateConsignment, а затем вызовет GetConsignments. И вы должны увидеть, что в ответе список содержит состав партии.

 

Таким образом, у нас есть первый микросервис и клиент, чтобы взаимодействовать с ним, используя protobuf и gRPC.

 

Следующая часть этой серии будет включать интеграцию go-micro, которая является мощной основой для создания микросервисов на основе gRPC. Мы также создадим наш второй сервис. Рассмотрим работу наших сервисов в контейнерах Docker, в следующей части этой серии статей.