[Перевод] Учебное пособие по микросервису Golang (3)

Микросервисы Go

Оригинальная ссылка:ewanvalentine.io, перевод одобрен авторомEwan Valentine уполномоченный.

Полный код этой статьи:GitHub

В предыдущем разделе мы повторно реализовали и докеризовали микросервисы с помощью go-micro, но для каждого микросервиса было бы слишком громоздко поддерживать свой собственный Makefile отдельно. В этом разделе мы изучим docker-compose для унифицированного управления и развертывания микросервисов, представим третий пользовательский сервис микросервиса и сохраним данные.

MongoDB и Постгрес

Хранилище данных для микросервисов

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

Предусмотреть отдельную базу данных для каждого независимо работающего микросервиса можно, но это мало кто делает из-за громоздкости управления. Как выбрать подходящую базу данных для различных микросервисов, см.:How to choose a database for your microservices

Выбирайте между реляционными базами данных и NoSQL

Если надежность и непротиворечивость хранимых данных не столь высоки, то NoSQL будет хорошим выбором, поскольку формат данных, который он может хранить, очень гибкий.Например, данные часто сохраняются в виде JSON для обработки.В этом разделе, выбрана производительность.И экологически совершенная MongoDB

Если данные, которые должны быть сохранены, относительно полны и отношения между данными также сильно коррелированы, можно использовать реляционную базу данных. Растяните структуру данных, которые нужно сохранить заранее, и посмотрите, следует ли больше читать или писать больше в зависимости от бизнеса? Сложны ли высокочастотные запросы? …Ввиду небольшого объема данных и операций в этой статье автор выбрал Postgres, а читатели могут заменить его на MySQL и т.д.

Больше ссылок:Как выбрать базу данных NoSQL,Разберите варианты использования реляционных баз данных и NoSQL

docker-compose

Причина введения

После того, как микрослужба была докеризована в предыдущем разделе, она запускается в упрощенном контейнере, который содержит только необходимые зависимости службы. Пока что для запуска контейнера микросервиса он находится в его Makefiledocker runВ то же время установите его переменные среды, это очень хлопотно управлять после большего количества служб.

основное использование

docker-composeинструмент может напрямую использоватьdocker-compose.yamlЧтобы организовать несколько контейнеров и управлять ими, а также одновременно установить метаданные и среду выполнения (переменные среды) для каждого контейнера, файлserviceЭлементы конфигурации приходят как раньшеdocker runКоманда для запуска контейнера. Например:

команда docker для управления контейнерами

$ docker run -p 50052:50051 \
  -e MICRO_SERVER_ADDRESS=:50051 \
  -e MICRO_REGISTRY=mdns \
  vessel-service

Эквивалент docker-compose для управления

version: '3.1'
vessel-service:
  build: ./vessel-service
  ports:
    - 50052:50051
  environment:
    MICRO_ADRESS: ":50051"
    MICRO_REGISTRY: "mdns"

Если вы хотите добавлять, убирать и настраивать микросервисы, очень удобно напрямую модифицировать docker-compose.yaml.

Больше ссылок:Оркестрация контейнеров с помощью docker-compose

Оркестрация контейнера для текущего проекта

Для текущего проекта используйте docker-compose для управления тремя контейнерами и создайте новый файл в корневом каталоге проекта:

# docker-compose.yaml
# 同样遵循严格的缩进
version: '3.1'

# services 定义容器列表
services:
   consignment-cli:
    build: ./consignment-cli
    environment:
      MICRO_REGISTRY: "mdns"

  consignment-service:
    build: ./consignment-service
    ports:
      - 50051:50051
    environment:
      MICRO_ADRESS: ":50051"
      MICRO_REGISTRY: "mdns"
      DB_HOST: "datastore:27017"

  vessel-service:
    build: ./vessel-service
    ports:
      - 50052:50051
    environment:
      MICRO_ADRESS: ":50051"
      MICRO_REGISTRY: "mdns"

Сначала мы указываем, что используемая версия docker-compose — 3.1, затем используемservicesчтобы перечислить три контейнера, которыми нужно управлять.

Каждый микросервис определяет имя своего контейнера,buildDockerfile в указанном каталоге будет использоваться для компиляции образа или может быть использован напрямуюimageПараметры напрямую указывают на скомпилированный образ (который будет использоваться позже), другие параметры указывают правила сопоставления портов, переменные среды и т. д. контейнера.

быть пригодным для использованияdocker-compose buildскомпилировать и сгенерировать три соответствующих изображения; использоватьdocker-compose runдля запуска указанного контейнера,docker-compose up -dМожет работать в фоновом режиме; используйтеdocker stop $(docker ps -aq )чтобы остановить все запущенные контейнеры.

текущий результат

Эффект от использования docker-compose следующий:

3.1

Protobuf и операции с базой данных

Повторное использование и его ограничения

На данный момент два наших файла протокола protobuf определяют структуру данных запросов и ответов клиента и сервера микросервиса. Из-за нормативного характера protobuf сгенерированная структура также может использоваться в качестве модели таблицы базы данных для манипулирования данными. Такое повторное использование имеет свои ограничения: например, тип данных в protobuf должен строго соответствовать полям таблицы базы данных, а они тесно связаны. Многим людям не нравятся структуры данных protobuf как табличные структуры в базах данных:Do you use Protobufs in place of structs ?

Преобразование логики среднего уровня

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

func (service *Service)(ctx context.Context, req *proto.User, res *proto.Response) error {
	entity := &models.User{
		Name: req.Name.
			Email: req.Email,
		Password: req.Password,
	}
	err := service.repo.Create(entity)

	// 无中间转换层
	// err := service.repo.Create(req)
	...
}

Кажется удобным изолировать модели сущностей базы данных и структуры proto.* таким образом. Однако, когда в .proto определены различные вложения сообщений, модели тоже должны быть вложенными, а это проблематично.

Читателю решать, изолировать или нет, насколько я понимаю, нет необходимости использовать модели для преобразования в середине, protobuf достаточно стандартизирован и может использоваться напрямую.

рефакторинг консигнационного сервиса

Оглядываясь назад на первую микросервисную консигнационную службу, вы обнаружите, что реализация сервера, реализация интерфейса и т. д. все подключены к main.go, а функции работают.Теперь нам нужно разделить код, чтобы сделать структуру проекта более понятной. и проще в обслуживании.

Структура кода MVC

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

main.go
models/
  user.go
handlers/
  auth.go 
  user.go
services/
  auth.go

Структура кода микросервиса

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

main.go
users/
  services/
    auth.go
  handlers/
    auth.go
    user.go
  users/
    user.go
containers/
  services/
    manage.go
  models/
    container.go

Эта организация называется управляемой категорией (доменом), а не функцией MVC.

Рефакторинг консигнационного сервиса

Из-за простоты микросервисов мы поместим весь код, относящийся к сервису, в одну папку и дадим каждому файлу соответствующее имя.

Создайте три файла в разделе consignmet-service/: handler.go, datastore.go и репозиторий.go.

consignmet-service/ 
    ├── Dockerfile
    ├── Makefile
    ├── datastore.go	# 创建与 MongoDB 的主会话
    ├── handler.go	# 实现微服务的服务端,处理业务逻辑
    ├── main.go		# 注册并启动服务
    ├── proto
    └── repository.go	# 实现数据库的基本 CURD 操作

datastore.go отвечает за подключение к MongoDB

package main
import "gopkg.in/mgo.v2"

// 创建与 MongoDB 交互的主回话
func CreateSession(host string) (*mgo.Session, error) {
	s, err := mgo.Dial(host)
	if err != nil {
		return nil, err
	}
	s.SetMode(mgo.Monotonic, true)
	return s, nil
}

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

репозиторий.go отвечает за взаимодействие с MongoDB

Теперь разберем код, взаимодействующий с БД в main.go, в чем можно разобраться, обратившись к комментариям:

package main
import (...)

const (
	DB_NAME        = "shippy"
	CON_COLLECTION = "consignments"
)

type Repository interface {
	Create(*pb.Consignment) error
	GetAll() ([]*pb.Consignment, error)
	Close()
}

type ConsignmentRepository struct {
	session *mgo.Session
}

// 接口实现
func (repo *ConsignmentRepository) Create(c *pb.Consignment) error {
	return repo.collection().Insert(c)
}

// 获取全部数据
func (repo *ConsignmentRepository) GetAll() ([]*pb.Consignment, error) {
	var cons []*pb.Consignment
	// Find() 一般用来执行查询,如果想执行 select * 则直接传入 nil 即可
	// 通过 .All() 将查询结果绑定到 cons 变量上
	// 对应的 .One() 则只取第一行记录
	err := repo.collection().Find(nil).All(&cons)
	return cons, err
}

// 关闭连接
func (repo *ConsignmentRepository) Close() {
	// Close() 会在每次查询结束的时候关闭会话
	// Mgo 会在启动的时候生成一个 "主" 会话
	// 你可以使用 Copy() 直接从主会话复制出新会话来执行,即每个查询都会有自己的数据库会话
	// 同时每个会话都有自己连接到数据库的 socket 及错误处理,这么做既安全又高效
	// 如果只使用一个连接到数据库的主 socket 来执行查询,那很多请求处理都会阻塞
	// Mgo 因此能在不使用锁的情况下完美处理并发请求
	// 不过弊端就是,每次查询结束之后,必须确保数据库会话要手动 Close
	// 否则将建立过多无用的连接,白白浪费数据库资源
	repo.session.Close()
}

// 返回所有货物信息
func (repo *ConsignmentRepository) collection() *mgo.Collection {
	return repo.session.DB(DB_NAME).C(CON_COLLECTION)
}

разделить main.go

package main
import (...)

const (
	DEFAULT_HOST = "localhost:27017"
)

func main() {

	// 获取容器设置的数据库地址环境变量的值
	dbHost := os.Getenv("DB_HOST")
	if dbHost == ""{
		 dbHost = DEFAULT_HOST
	}
	session, err := CreateSession(dbHost)
	// 创建于 MongoDB 的主会话,需在退出 main() 时候手动释放连接
	defer session.Close()
	if err != nil {
		log.Fatalf("create session error: %v\n", err)
	}

	server := micro.NewService(
		// 必须和 consignment.proto 中的 package 一致
		micro.Name("go.micro.srv.consignment"),
		micro.Version("latest"),
	)

	// 解析命令行参数
	server.Init()
	// 作为 vessel-service 的客户端
	vClient := vesselPb.NewVesselServiceClient("go.micro.srv.vessel", server.Client())
	// 将 server 作为微服务的服务端
	pb.RegisterShippingServiceHandler(server.Server(), &handler{session, vClient})

	if err := server.Run(); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

Реализовать handler.go на стороне сервера

Выделите код, который реализует интерфейс сервера микросервиса в main.go, в handler.go для реализации обработки бизнес-логики.

package main
import (...)

// 微服务服务端 struct handler 必须实现 protobuf 中定义的 rpc 方法
// 实现方法的传参等可参考生成的 consignment.pb.go
type handler struct {
	session *mgo.Session
	vesselClient vesselPb.VesselServiceClient
}

// 从主会话中 Clone() 出新会话处理查询
func (h *handler)GetRepo()Repository  {
	return &ConsignmentRepository{h.session.Clone()}
}

func (h *handler)CreateConsignment(ctx context.Context, req *pb.Consignment, resp *pb.Response) error {
	defer h.GetRepo().Close()

	// 检查是否有适合的货轮
	vReq := &vesselPb.Specification{
		Capacity:  int32(len(req.Containers)),
		MaxWeight: req.Weight,
	}
	vResp, err := h.vesselClient.FindAvailable(context.Background(), vReq)
	if err != nil {
		return err
	}

	// 货物被承运
	log.Printf("found vessel: %s\n", vResp.Vessel.Name)
	req.VesselId = vResp.Vessel.Id
	//consignment, err := h.repo.Create(req)
	err = h.GetRepo().Create(req)
	if err != nil {
		return err
	}
	resp.Created = true
	resp.Consignment = req
	return nil
}

func (h *handler)GetConsignments(ctx context.Context, req *pb.GetRequest, resp *pb.Response) error {
	defer h.GetRepo().Close()
	consignments, err := h.GetRepo().GetAll()
	if err != nil {
		return err
	}
	resp.Consignments = consignments
	return nil
}

До сих пор main.go был разделен, и файлы кода четко разделены, что очень освежает.

Copy() и Clone() библиотеки mgo

В GetRepo() в handler.go мы используем Clone() для создания нового подключения к базе данных.

Вы можете видеть, что после создания основного сеанса в main.go мы больше никогда его не используем, а вместо этого используемsession.Clonse()Чтобы создать новый сеанс для обработки запросов, см. репозиторий.goClose()Обратите внимание: если для каждого запроса используется основной сеанс, то все запросы выполняются на одном и том же базовом сокете, а последующие запросы будут заблокированы, что не позволяет использовать преимущества естественной поддержки параллелизма в Go.

Чтобы избежать блокировки запросов, библиотека mgo предоставляетCopy()а такжеClone()для создания нового сеанса, они похожи по функциональности, но есть важные различия в тонкостях. Новый сеанс от Clone повторно использует сокет основного сеанса, что позволяет избежать накладных расходов на время и ресурсы трехэтапного рукопожатия при создании сокета, что особенно подходит для быстрых запросов на запись. Если выполняются сложные запросы и операции с большими объемами данных, сокет все равно будет заблокирован, что приведет к блокировке последующих запросов. Copy создает новый сокет для сеанса, что дорого.

Их следует выбирать в соответствии с различными сценариями применения.Запрос в этой статье не является ни сложным, ни большим, а сокет основного сеанса можно использовать повторно напрямую. Но вы должны закрыть() после использования, помните.

рефакторинг судового сервиса

После дизассемблирования кода consignment-service/main.go рефакторим судно-сервис таким же образом

Добавить грузовое судно

Добавляем сюда метод: добавляем новый фрахтовщик, меняем файл protobuf следующим образом:

syntax = "proto3";
package go.micro.srv.vessel;

service VesselService {
    // 检查是否有能运送货物的轮船
    rpc FindAvailable (Specification) returns (Response) {}
    // 创建货轮
    rpc Create(Vessel) returns (Response){}
}

// ...

// 货轮装得下的话
// 返回的多条货轮信息
message Response {
    Vessel vessel = 1;
    repeated Vessel vessels = 2;
    bool created = 3;
}

мы создалиCreate()метод для создания нового грузового судна. Параметр — Vessel и возвращает Response. Обратите внимание, что созданное поле добавляется в Response, чтобы указать, успешно ли создано. использоватьmake buildСоздайте новый файл serve.pb.go.

Раздельная работа с базой данных и обработка бизнес-логики

Затем реализуйте его в соответствующем репозитории.go и handler.go.Create()

// vesell-service/repository.go
// 完成与数据库交互的创建动作
func (repo *VesselRepository) Create(v *pb.Vessel) error {
	return repo.collection().Insert(v)
}
// vesell-service/handler.go
func (h *handler) GetRepo() Repository {
	return &VesselRepository{h.session.Clone()}
}

// 实现微服务的服务端
func (h *handler) Create(ctx context.Context, req *pb.Vessel, resp *pb.Response) error {
	defer h.GetRepo().Close()
	if err := h.GetRepo().Create(req); err != nil {
		return err
	}
	resp.Vessel = req
	resp.Created = true
	return nil
}

Представляем MongoDB

После рефакторинга обоих микросервисов пришло время представить MongoDB в контейнере. Добавьте опцию хранилища данных в docker-compose.yaml:

services:
  ...
  datastore:
    image: mongo
    ports:
      - 27017:27017

Обновите переменные среды двух микросервисов одновременно, увеличьтеDB_HOST: "datastore:27017", здесь мы используем datastore в качестве имени хоста вместо localhost, потому что в docker встроен надежный механизм DNS. Ссылаться на:Встроенный в Docker рабочий механизм dnsserver

Модифицированный docker-compose.yaml:

# docker-compose.yaml
version: '3.1'

services:
  consigment-cli:
    build: ./consignment-cli
    environment:
      MICRO_REGISTRY: "mdns"

  consignment-service:
    build: ./consignment-service
    ports:
      - 50051:50051
    environment:
      MICRO_ADRESS: ":50051"
      MICRO_REGISTRY: "mdns"
      DB_HOST: "datastore:27017"

  vessel-service:
    build: ./vessel-service
    ports:
      - 50052:50051
    environment:
      MICRO_ADRESS: ":50051"
      MICRO_REGISTRY: "mdns"
      DB_HOST: "datastore:27017"

  datastore:
    image: mongo
    ports:
      - 27017:27017

После изменения кода вам необходимо повторноmake build, вам нужно построить образdocker-compose build --no-cacheперекомпилировать все.

user-service

Знакомство с Постгресом

Теперь давайте создадим третий микросервис, вdocker-compose.yamlПредставьте Постгрес:

...
  user-service:
    build: ./user-service
    ports:
      - 50053:50051
    environment:
      MICRO_ADDRESS: ":50051"
      MICRO_REGISTRY: "mdns"

  ...
  database:
    image: postgres
    ports:
      - 5432:5432

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

handler.go, main.go, repository.go, database.go, Dockerfile, Makefile

Определить файл protobuf

Создайте proto/user/user.proto со следующим содержимым:

// user-service/user/user.proto
syntax = "proto3";

package go.micro.srv.user;

service UserService {
    rpc Create (User) returns (Response) {}
    rpc Get (User) returns (Response) {}
    rpc GetAll (Request) returns (Response) {}
    rpc Auth (User) returns (Token) {}
    rpc ValidateToken (Token) returns (Token) {}
}

// 用户信息
message User {
    string id = 1;
    string name = 2;
    string company = 3;
    string email = 4;
    string password = 5;
}

message Request {
}

message Response {
    User user = 1;
    repeated User users = 2;
    repeated Error errors = 3;
}

message Token {
    string token = 1;
    bool valid = 2;
    Error errors = 3;
}

message Error {
    int32 code = 1;
    string description = 2;
}

Убедитесь, что ваш пользовательский сервис имеет Makefile, аналогичный первым двум микросервисам, используйтеmake buildдля создания кода gRPC.

handler.go, реализующий обработку бизнес-логики

В серверном коде, реализованном handler.go, модуль аутентификации будет использовать JWT для аутентификации в следующем разделе.

// user-service/handler.go

package main

import (
	"context"
	pb "shippy/user-service/proto/user"
)

type handler struct {
	repo Repository
}

func (h *handler) Create(ctx context.Context, req *pb.User, resp *pb.Response) error {
	if err := h.repo.Create(req); err != nil {
		return nil
	}
	resp.User = req
	return nil
}

func (h *handler) Get(ctx context.Context, req *pb.User, resp *pb.Response) error {
	u, err := h.repo.Get(req.Id);
	if err != nil {
		return err
	}
	resp.User = u
	return nil
}

func (h *handler) GetAll(ctx context.Context, req *pb.Request, resp *pb.Response) error {
	users, err := h.repo.GetAll()
	if err != nil {
		return err
	}
	resp.Users = users
	return nil
}

func (h *handler) Auth(ctx context.Context, req *pb.User, resp *pb.Token) error {
	_, err := h.repo.GetByEmailAndPassword(req)
	if err != nil {
		return err
	}
	resp.Token = "`x_2nam"
	return nil
}

func (h *handler) ValidateToken(ctx context.Context, req *pb.Token, resp *pb.Token) error {
	return nil
}

репозиторий.go для взаимодействия с базой данных

package main

import (
	"github.com/jinzhu/gorm"
	pb "shippy/user-service/proto/user"
)

type Repository interface {
	Get(id string) (*pb.User, error)
	GetAll() ([]*pb.User, error)
	Create(*pb.User) error
	GetByEmailAndPassword(*pb.User) (*pb.User, error)
}

type UserRepository struct {
	db *gorm.DB
}

func (repo *UserRepository) Get(id string) (*pb.User, error) {
	var u *pb.User
	u.Id = id
	if err := repo.db.First(&u).Error; err != nil {
		return nil, err
	}
	return u, nil
}

func (repo *UserRepository) GetAll() ([]*pb.User, error) {
	var users []*pb.User
	if err := repo.db.Find(&users).Error; err != nil {
		return nil, err
	}
	return users, nil
}

func (repo *UserRepository) Create(u *pb.User) error {
	if err := repo.db.Create(&u).Error; err != nil {
		return err
	}
	return nil
}

func (repo *UserRepository) GetByEmailAndPassword(u *pb.User) (*pb.User, error) {
	if err := repo.db.Find(&u).Error; err != nil {
		return nil, err
	}
	return u, nil
}

Использование UUID

Мы изменяем строку UUID, созданную ORM, на целое число, которое безопаснее использовать в качестве первичного ключа или идентификатора таблицы. MongoDB использует аналогичную технику, но Postgres требует от нас генерировать ее вручную с помощью сторонней библиотеки. существуетuser-service/proto/userСоздайте файл extension.go в каталоге:

package go_micro_srv_user

import (
	"github.com/jinzhu/gorm"
	uuid "github.com/satori/go.uuid"
	"github.com/labstack/gommon/log"
)

func (user *User) BeforeCreate(scope *gorm.Scope) error {
	uuid, err := uuid.NewV4()
	if err != nil {
		log.Fatalf("created uuid error: %v\n", err)
	}
	return scope.SetColumn("Id", uuid.String())
}

функцияBeforeCreate()Указывает, что библиотека GORM использует uuid в качестве значения столбца ID. Ссылаться на:doc.gorm.io/callbacks

GORM

GormЭто простая в использовании и легкая структура ORM, которая поддерживает Postgres, MySQL, Sqlite и другие базы данных.

Пока три микросервиса включают небольшой объем данных и несколько операций, и их можно поддерживать с помощью собственного SQL, поэтому вам решать, хотите ли вы ORM или нет.

user-cli

Как и в случае с консигнационным сервисом, теперь создайте приложение командной строки user-cli для тестирования пользовательского сервиса.

Создайте каталог user-cli в корневом каталоге проекта и создайте файл cli.go:

package main

import (
	"log"
	"os"

	pb "shippy/user-service/proto/user"
	microclient "github.com/micro/go-micro/client"
	"github.com/micro/go-micro/cmd"
	"golang.org/x/net/context"
	"github.com/micro/cli"
	"github.com/micro/go-micro"
)


func main() {

	cmd.Init()

	// 创建 user-service 微服务的客户端
	client := pb.NewUserServiceClient("go.micro.srv.user", microclient.DefaultClient)

	// 设置命令行参数
	service := micro.NewService(
		micro.Flags(
			cli.StringFlag{
				Name:  "name",
				Usage: "You full name",
			},
			cli.StringFlag{
				Name:  "email",
				Usage: "Your email",
			},
			cli.StringFlag{
				Name:  "password",
				Usage: "Your password",
			},
			cli.StringFlag{
				Name: "company",
				Usage: "Your company",
			},
		),
	)

	service.Init(
		micro.Action(func(c *cli.Context) {
			name := c.String("name")
			email := c.String("email")
			password := c.String("password")
			company := c.String("company")

			r, err := client.Create(context.TODO(), &pb.User{
				Name: name,
				Email: email,
				Password: password,
				Company: company,
			})
			if err != nil {
				log.Fatalf("Could not create: %v", err)
			}
			log.Printf("Created: %v", r.User.Id)

			getAll, err := client.GetAll(context.Background(), &pb.Request{})
			if err != nil {
				log.Fatalf("Could not list users: %v", err)
			}
			for _, v := range getAll.Users {
				log.Println(v)
			}

			os.Exit(0)
		}),
	)

	// 启动客户端
	if err := service.Run(); err != nil {
		log.Println(err)
	}
}

контрольная работа

запустить успешно

3.3

Перед этим нужно вручную вытащить образ Postgres и запустить:

$ docker pull postgres
$ docker run --name postgres -e POSTGRES_PASSWORD=postgres -d -p 5432:5432 postgres

Пользовательские данные успешно созданы и сохранены:

image-20180526203001681

Суммировать

На данный момент мы создали три микросервиса: консигнационный сервис, судовой сервис и пользовательский сервис, все они реализованы и докеризованы с помощью go-micro и единообразно управляются с помощью docker-compose. Дополнительно мы используем библиотеку GORM для взаимодействия с базой данных Postgres и хранения в ней данных командной строки.

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