FROM alpine:latest
RUN mkdir /app
WORKDIR /app
ADD consignment-service /app/consignment-service
CMD ["./consignment-service"]
build:
...
GOOS=linux GOARCH=amd64 go build
docker build -t consignment .
run:
docker run -p 50051:50051 shippy-service-consignment
go get -u github.com/micro/protobuf/{proto,protoc-gen-go}
build:
protoc -I. --go_out=plugins=micro:. \ proto/consignment/consignment.proto
GOOS=linux GOARCH=amd64 go build
docker build -t consignment .
run:
docker run -p 50051:50051 shippy-service-consignment
// shippy-service-consignment/main.go
package main
import (
"fmt"
//Импортируем основной protobuf код
pb "github.com/EwanValentine/shippy/consignment-service/proto/consignment"
"github.com/micro/go-micro"
"context"
)
//repository - интерфейс хранилища
type repository interface {
Create(*pb.Consignment) (*pb.Consignment, error)
GetAll() []*pb.Consignment
}
// Repository - структура для эмитации хранилища,
// после мы заменим её настоящим хранилищем
type Repository struct {
consignments []*pb.Consignment
}
func (repo *Repository) Create(consignment *pb.Consignment) (*pb.Consignment, error) {
updated := append(repo.consignments, consignment)
repo.consignments = updated
return consignment, nil
}
func (repo *Repository) GetAll() []*pb.Consignment {
return repo.consignments
}
// Служба должна реализовать все методы для удовлетворения сервиса
// которые мы определили в нашем определении proto.
// Вы можете проверить интерфейсы в сгенерированном коде для точных сигнатур методов.
type service struct {
repo repository
}
// CreateConsignment - мы создали только один метод для нашего сервиса,
// который является методом create, который принимает контекст и запрос
// потом они обрабатываются сервером gRPC.
func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment, res *pb.Response) error {
// Save our consignment
consignment, err := s.repo.Create(req)
if err != nil {
return err
}
// Return matching the `Response` message we created in our
// protobuf definition.
res.Created = true
res.Consignment = consignment
return nil
}
//GetConsignments - метод для получения всех партий из ответа сервера
func (s *service) GetConsignments(ctx context.Context, req *pb.GetRequest, res *pb.Response) error {
consignments := s.repo.GetAll()
res.Consignments = consignments
return nil
}
func main() {
repo := &Repository{}
// Регистрируем новый сервис через Go-micro
srv := micro.NewService(
// Это имя должно совпадать с именем пакета объявленного в файле proto
micro.Name("shippy.service.consignment"),
)
// Init will parse the command line flags.
srv.Init()
// Регистрация обработчитка
pb.RegisterShippingServiceHandler(srv.Server(), &service{repo})
// Запуск сервера
log.Println("Запуск сервера")
if err := srv.Run(); err != nil {
fmt.Println(err)
}
}
build:
protoc -I. --go_out=plugins=micro:. \ proto/consignment/consignment.proto
GOOS=linux GOARCH=amd64 go build
docker build -t consignment .
run:
docker run -p 50051:50051 \ -e MICRO_SERVER_ADDRESS=:50051 \ shippy-service-consignment
package main
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"context"
pb "github.com/EwanValentine/shippy-service-consignment/proto/consignment"
micro "github.com/micro/go-micro"
)
const (
address = "localhost:50051"
defaultFilename = "consignment.json"
)
func parseFile(file string) (*pb.Consignment, error) {
var consignment *pb.Consignment
data, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
json.Unmarshal(data, &consignment)
return consignment, err
}
func main() {
service := micro.NewService(micro.Name("shippy.cli.consignment"))
service.Init()
client := pb.NewShippingServiceClient("shippy.service.consignment", service.Client())
// Contact the server and print out its response.
file := defaultFilename
if len(os.Args) > 1 {
file = os.Args[1]
}
consignment, err := parseFile(file)
if err != nil {
log.Fatalf("Could not parse file: %v", err)
}
r, err := client.CreateConsignment(context.Background(), consignment)
if err != nil {
log.Fatalf("Could not greet: %v", err)
}
log.Printf("Created: %t", r.Created)
getAll, err := client.GetConsignments(context.Background(), &pb.GetRequest{})
if err != nil {
log.Fatalf("Could not list consignments: %v", err)
}
for _, v := range getAll.Consignments {
log.Println(v)
}
}
build:
GOOS=linux GOARCH=amd64 go build
docker build -t shippy-cli-consignment .
run:
docker run shippy-cli-consignment
FROM alpine:latest
RUN mkdir -p /app
WORKDIR /app
ADD consignment.json /app/consignment.json
ADD consignment-cli /app/consignment-cli
CMD ["./shippy-cli-consignment"]
# consignment-service/Dockerfile
# Мы используем официальное изображение golang, которое содержит все
# правильные инструменты сборки и библиотеки. Обратите внимание на `as builder`,
# это дает этому контейнеру имя, на которое мы можем ссылаться позже.
FROM golang:alpine as builder
RUN apk --no-cache add git
# Установит рабочий каталог на наш текущий сервис в gopath
WORKDIR /app/shippy-service-consignment
# Скопирует текущий код в рабочий каталог
COPY . .
RUN go mod download
# Создаст двоичный файл с флагами, который позволит
# нам запустить этот двоичный файл в Alpine.
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o shippy-service-consignment
# Здесь мы используем второй оператор FROM,
# это говорит Docker начать новый процесс сборки с этим же образом.
FROM alpine:latest
# Пакет, связанный с безопасностью - хорошо бы его иметь
RUN apk --no-cache add ca-certificates
# Как и прежде, создайте каталог для нашего приложения.
RUN mkdir /app
WORKDIR /app
# Здесь вместо того, чтобы копировать двоичный файл с нашего хоста,
# мы извлекаем двоичный файл из контейнера с именем `builder`
# Это позволяет заглянуть в наш предыдущий образ,
# и найти двоичный файл, который мы создали ранее,
# и поместить его в этот контейнер. Удивительно!
COPY --from=builder /app/shippy-service-consignment/shippy-service-consignment .
# Запустит бинарный файл как обычно! На этот раз с бинарной сборкой в
# отдельном контейнере со всеми правильными зависимостями и
# run time библиотеками.
CMD ["./shippy-service-consignment"]
// shippy-service-vessel/proto/vessel/vessel.proto
syntax = "proto3";
package vessel;
service VesselService {
rpc FindAvailable(Specification) returns (Response) {}
}
message Vessel {
string id = 1;
int32 capacity = 2;
int32 max_weight = 3;
string name = 4;
bool available = 5;
string owner_id = 6;
}
message Specification {
int32 capacity = 1;
int32 max_weight = 2;
}
message Response {
Vessel vessel = 1;
repeated Vessel vessels = 2;
}
// vessel-service/Makefile
build:
protoc -I. --go_out=plugins=micro:. proto/vessel/vessel.proto
docker build -t shippy-service-vessel .
run:
docker run -p 50052:50051 -e MICRO_SERVER_ADDRESS=:50051 shippy-service-vessel
# vessel-service/Dockerfile
FROM golang:alpine as builder
RUN apk --no-cache add git
WORKDIR /app/shippy-service-vessel
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o shippy-service-vessel
FROM alpine:latest
RUN apk --no-cache add ca-certificates
RUN mkdir /app
WORKDIR /app
COPY --from=builder /app/shippy-service-vessel .
CMD ["./shippy-service-vessel"]
// vessel-service/main.go
package main
import (
"context"
"errors"
"fmt"
pb "github.com/EwanValentine/shippy/vessel-service/proto/vessel"
"github.com/micro/go-micro"
)
type Repository interface {
FindAvailable(*pb.Specification) (*pb.Vessel, error)
}
type VesselRepository struct {
vessels []*pb.Vessel
}
// FindAvailable - проверяет спецификацию по карте судов,
// если вместимость и максимальный вес ниже вместимости судна и максимального веса,
// тогда возвращаем это судно в ответ.
func (repo *VesselRepository) FindAvailable(spec *pb.Specification) (*pb.Vessel, error) {
for _, vessel := range repo.vessels {
if spec.Capacity <= vessel.Capacity && spec.MaxWeight <= vessel.MaxWeight {
return vessel, nil
}
}
//если не найдём нужного судна
return nil, errors.New("судов с задаными параметрами не найдено")
}
// Наш обработчик сервиса grpc
type service struct {
repo repository
}
func (s *service) FindAvailable(ctx context.Context, req *pb.Specification, res *pb.Response) error {
// Поиск следующего подходящего судна
vessel, err := s.repo.FindAvailable(req)
if err != nil {
return err
}
// Определяем ответ с заполненым полем судна
res.Vessel = vessel
return nil
}
func main() {
vessels := []*pb.Vessel{
&pb.Vessel{Id: "vessel001", Name: "Boaty McBoatface", MaxWeight: 200000, Capacity: 500},
}
repo := &VesselRepository{vessels}
srv := micro.NewService(
micro.Name("shippy.service.vessel"),
)
srv.Init()
// Регистрация нашего сервиса
pb.RegisterVesselServiceHandler(srv.Server(), &service{repo})
if err := srv.Run(); err != nil {
fmt.Println(err)
}
}
package main
import (
"context"
"fmt"
"log"
"sync"
pb "github.com/EwanValentine/shippy-service-consignment/proto/consignment"
vesselProto "github.com/EwanValentine/shippy-service-vessel/proto/vessel"
"github.com/micro/go-micro"
)
const (
port = ":50051"
)
type repository interface {
Create(*pb.Consignment) (*pb.Consignment, error)
GetAll() []*pb.Consignment
}
// Repository - структура для эмитации хранилища,
// после мы заменим её настоящим хранилищем
type Repository struct {
mu sync.RWMutex
consignments []*pb.Consignment
}
//Create - создаём новую партию груза
func (repo *Repository) Create(consignment *pb.Consignment) (*pb.Consignment, error) {
repo.mu.Lock()
updated := append(repo.consignments, consignment)
repo.consignments = updated
repo.mu.Unlock()
return consignment, nil
}
//GetAll - метод получения всех партий из хранилища
func (repo *Repository) GetAll() []*pb.Consignment {
return repo.consignments
}
// Служба должна реализовать все методы для удовлетворения сервиса
// которые мы определили в нашем определении proto.
// Вы можете проверить интерфейсы в сгенерированном коде для точных сигнатур методов
type service struct {
repo repository
vesselClient vesselProto.VesselServiceClient
}
// CreateConsignment - мы создали только один метод для нашего сервиса create,
// который принимает контекст и запрос, после он обрабатывается сервером gRPC.
func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment, res *pb.Response) error {
// Здесь мы опледеляем судно исходя из веса нашего груза,
// и количества контейнеров
vesselResponse, err := s.vesselClient.FindAvailable(context.Background(), &vesselProto.Specification{
MaxWeight: req.Weight,
Capacity: int32(len(req.Containers)),
})
log.Printf("Судно найдено: %s \n", vesselResponse.Vessel.Name)
if err != nil {
return err
}
// В ответ мы передадим id судна
req.VesselId = vesselResponse.Vessel.Id
// Сохраним партию груза в репозиторий
consignment, err := s.repo.Create(req)
if err != nil {
return err
}
res.Created = true
res.Consignment = consignment
return nil
}
// GetConsignments - метод для получения всех партий из ответа сервера
func (s *service) GetConsignments(ctx context.Context, req *pb.GetRequest, res *pb.Response) error {
consignments := s.repo.GetAll()
res.Consignments = consignments
return nil
}
func main() {
//Создание пустого хранилища
repo := &Repository{}
//Создание экземпляра micro
srv := micro.NewService(
micro.Name("shippy.service.consignment"),
)
srv.Init()
vesselClient := vesselProto.NewVesselServiceClient("shippy.service.vessel", srv.Client())
// Регистрация службы ответов на сервере gRPC.
pb.RegisterShippingServiceHandler(srv.Server(), &service{repo, vesselClient})
// Запуск сервера
if err := srv.Run(); err != nil {
fmt.Println(err)
}
}
{
"description": "Тестовая партия груза",
"weight": 55000,
"containers": [
{
"customer_id": "Заказчик_001",
"user_id": "Пользователь_001",
"origin": "Ростов-на-Дону"
},
{
"customer_id": "Заказчик_002",
"user_id": "Пользователь_001",
"origin": "Новоросийск"
},
{
"customer_id": "Заказчик_003",
"user_id": "Пользователь_001",
"origin": "Туапсе"
}
]
}
К сожалению, не доступен сервер mySQL