Construire une architecture de microservices sur Golang et gRPC, partie 1

Introduction Ă  l'architecture des microservices


Partie 1 sur 10


Adaptation d'articles d'Ewan Valentine.


Il s'agit d'une série de dix parties, je vais essayer d'écrire une fois par mois sur la construction de microservices sur Golang. J'utiliserai protobuf et gRPC comme protocole de transport principal.


La pile que j'ai utilisée: golang, mongodb, grpc, docker, Google Cloud, Kubernetes, NATS, CircleCI, Terraform et go-micro.


Pourquoi ai-je besoin de ça? Puisqu'il m'a fallu beaucoup de temps pour le comprendre et résoudre les problÚmes accumulés. Je voulais également partager avec vous ce que j'ai appris sur la création, le test et le déploiement de microservices sur Go et d'autres nouvelles technologies.


Dans cette partie, je veux montrer les concepts et technologies de base pour la construction de microservices. Écrivons une implĂ©mentation simple. Le projet comprendra les entitĂ©s suivantes:


  • cargaison
  • inventaire
  • essai
  • les utilisateurs
  • les rĂŽles
  • authentification


Pour aller plus loin, vous devez installer Golang et les bibliothÚques nécessaires, ainsi que créer un référentiel git.


Théorie


Qu'est-ce que l'architecture de microservice?


Les microservices isolent une fonctionnalité distincte dans un service, autosuffisant en termes de fonction exécutée par ce service. Pour la compatibilité avec d'autres services, il possÚde une interface bien connue et prédéfinie.
Les microservices communiquent entre eux à l'aide de messages transmis par un intermédiaire, un courtier de messages.



GrĂące Ă  l'architecture de microservice, l'application ne peut pas ĂȘtre mise Ă  l'Ă©chelle en totalitĂ©, mais en partie. Par exemple, si le service d'autorisation «tressaute» plus souvent que d'autres, nous pouvons augmenter le nombre d'instances. Ce concept est conforme aux concepts du cloud computing et de la conteneurisation en gĂ©nĂ©ral.


Pourquoi Golang


Les microservices sont pris en charge dans presque toutes les langues, aprÚs tout, les microservices sont un concept, pas une structure ou un outil spécifique. Cependant, certaines langues sont mieux adaptées et, en outre, prennent mieux en charge les microservices que d'autres. Une langue avec un grand soutien est le Golang.


Rencontrez protobuf / gRPC


Comme mentionné précédemment, les microservices sont divisés en bases de code distinctes, l'un des problÚmes importants associés aux microservices est la communication. Si vous avez un monolithe, vous pouvez simplement appeler le code directement depuis un autre endroit de votre programme.


Pour résoudre le problÚme de communication, nous pouvons utiliser l'approche REST traditionnelle et transférer des données au format JSON ou XML via HTTP. Mais cette approche a ses inconvénients, par exemple, qu'avant d'envoyer un message, vous devez coder vos données et les décoder à nouveau du cÎté de la réception. Et cela est une surcharge et augmente la complexité du code.


Il y a une solution! Il s'agit du protocole gRPC - un protocole binaire lĂ©ger qui Ă©limine la transmission des en-tĂȘtes HTTP, et cela nous fera Ă©conomiser quelques octets. Le futur HTTP2 implique Ă©galement l'utilisation de donnĂ©es binaires, ce qui parle Ă  nouveau en faveur de gRPC. HTTP2 permet une communication bidirectionnelle, et c'est gĂ©nial!


GRPC vous permet également de définir l'interface de votre service dans un format convivial - c'est> protobuf .


Pratique


Créez le fichier /project/consigment.proto.
Documentation officielle de protobuf


consigment.proto
//consigment.proto syntax = "proto3"; package go.micro.srv.consignment; service ShippingService { rpc CreateConsignment(Consignment) returns (Response) {} } message Consignment { string id = 1; string description = 2; int32 weight = 3; repeated Container containers = 4; string vessel_id = 5; } message Container { string id = 1; string customer_id = 2; string origin = 3; string user_id = 4; } message Response { bool created = 1; Consignment consignment = 2; } 

Il s'agit d'un exemple simple qui contient le service que vous souhaitez fournir à d'autres services: le service ShippingService, puis nous définirons nos messages. Protobuf est un protocole typé statiquement, et nous pouvons créer des types personnalisés (similaires aux structures de golang). Ici, le conteneur est imbriqué dans le lot.


Installez les bibliothĂšques, le compilateur et compilez notre protocole:


 $ 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 

La sortie doit ĂȘtre un fichier:


consignment.pb.go
 // Code generated by protoc-gen-go. DO NOT EDIT. // source: consignment.proto package consignment import ( fmt "fmt" proto "github.com/golang/protobuf/proto" context "golang.org/x/net/context" grpc "google.golang.org/grpc" math "math" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type Consignment struct { Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` Weight int32 `protobuf:"varint,3,opt,name=weight,proto3" json:"weight,omitempty"` Containers []*Container `protobuf:"bytes,4,rep,name=containers,proto3" json:"containers,omitempty"` VesselId string `protobuf:"bytes,5,opt,name=vessel_id,json=vesselId,proto3" json:"vessel_id,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *Consignment) Reset() { *m = Consignment{} } func (m *Consignment) String() string { return proto.CompactTextString(m) } func (*Consignment) ProtoMessage() {} func (*Consignment) Descriptor() ([]byte, []int) { return fileDescriptor_3804bf87090b51a9, []int{0} } func (m *Consignment) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Consignment.Unmarshal(m, b) } func (m *Consignment) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Consignment.Marshal(b, m, deterministic) } func (m *Consignment) XXX_Merge(src proto.Message) { xxx_messageInfo_Consignment.Merge(m, src) } func (m *Consignment) XXX_Size() int { return xxx_messageInfo_Consignment.Size(m) } func (m *Consignment) XXX_DiscardUnknown() { xxx_messageInfo_Consignment.DiscardUnknown(m) } var xxx_messageInfo_Consignment proto.InternalMessageInfo func (m *Consignment) GetId() int32 { if m != nil { return m.Id } return 0 } func (m *Consignment) GetDescription() string { if m != nil { return m.Description } return "" } func (m *Consignment) GetWeight() int32 { if m != nil { return m.Weight } return 0 } func (m *Consignment) GetContainers() []*Container { if m != nil { return m.Containers } return nil } func (m *Consignment) GetVesselId() string { if m != nil { return m.VesselId } return "" } type Container struct { Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` CustomerId string `protobuf:"bytes,2,opt,name=customer_id,json=customerId,proto3" json:"customer_id,omitempty"` Origin string `protobuf:"bytes,3,opt,name=origin,proto3" json:"origin,omitempty"` UserId string `protobuf:"bytes,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *Container) Reset() { *m = Container{} } func (m *Container) String() string { return proto.CompactTextString(m) } func (*Container) ProtoMessage() {} func (*Container) Descriptor() ([]byte, []int) { return fileDescriptor_3804bf87090b51a9, []int{1} } func (m *Container) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Container.Unmarshal(m, b) } func (m *Container) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Container.Marshal(b, m, deterministic) } func (m *Container) XXX_Merge(src proto.Message) { xxx_messageInfo_Container.Merge(m, src) } func (m *Container) XXX_Size() int { return xxx_messageInfo_Container.Size(m) } func (m *Container) XXX_DiscardUnknown() { xxx_messageInfo_Container.DiscardUnknown(m) } var xxx_messageInfo_Container proto.InternalMessageInfo func (m *Container) GetId() int32 { if m != nil { return m.Id } return 0 } func (m *Container) GetCustomerId() string { if m != nil { return m.CustomerId } return "" } func (m *Container) GetOrigin() string { if m != nil { return m.Origin } return "" } func (m *Container) GetUserId() string { if m != nil { return m.UserId } return "" } type Response struct { Created bool `protobuf:"varint,1,opt,name=created,proto3" json:"created,omitempty"` Consignment *Consignment `protobuf:"bytes,2,opt,name=consignment,proto3" json:"consignment,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *Response) Reset() { *m = Response{} } func (m *Response) String() string { return proto.CompactTextString(m) } func (*Response) ProtoMessage() {} func (*Response) Descriptor() ([]byte, []int) { return fileDescriptor_3804bf87090b51a9, []int{2} } func (m *Response) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Response.Unmarshal(m, b) } func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Response.Marshal(b, m, deterministic) } func (m *Response) XXX_Merge(src proto.Message) { xxx_messageInfo_Response.Merge(m, src) } func (m *Response) XXX_Size() int { return xxx_messageInfo_Response.Size(m) } func (m *Response) XXX_DiscardUnknown() { xxx_messageInfo_Response.DiscardUnknown(m) } var xxx_messageInfo_Response proto.InternalMessageInfo func (m *Response) GetCreated() bool { if m != nil { return m.Created } return false } func (m *Response) GetConsignment() *Consignment { if m != nil { return m.Consignment } return nil } func init() { proto.RegisterType((*Consignment)(nil), "Consignment") proto.RegisterType((*Container)(nil), "Container") proto.RegisterType((*Response)(nil), "Response") } func init() { proto.RegisterFile("consignment.proto", fileDescriptor_3804bf87090b51a9) } var fileDescriptor_3804bf87090b51a9 = []byte{ // 281 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x91, 0xbf, 0x4e, 0x33, 0x31, 0x10, 0xc4, 0xbf, 0xcb, 0xff, 0x5b, 0x7f, 0x02, 0xc5, 0x05, 0x58, 0x50, 0x70, 0xba, 0x2a, 0xa2, 0x70, 0x11, 0x9e, 0x00, 0xa5, 0x4a, 0xeb, 0xd0, 0xa3, 0x60, 0xaf, 0x2e, 0x2b, 0x11, 0xfb, 0x64, 0x3b, 0xe1, 0x75, 0x78, 0x54, 0x74, 0xbe, 0x1c, 0x18, 0x51, 0xce, 0xac, 0x67, 0xf7, 0xa7, 0x31, 0x2c, 0xb5, 0xb3, 0x81, 0x1a, 0x7b, 0x44, 0x1b, 0x65, 0xeb, 0x5d, 0x74, 0xf5, 0x67, 0x01, 0x6c, 0xf3, 0xe3, 0xf2, 0x2b, 0x18, 0x91, 0x11, 0x45, 0x55, 0xac, 0xa6, 0x6a, 0x44, 0x86, 0x57, 0xc0, 0x0c, 0x06, 0xed, 0xa9, 0x8d, 0xe4, 0xac, 0x18, 0x55, 0xc5, 0xaa, 0x54, 0xb9, 0xc5, 0x6f, 0x60, 0xf6, 0x81, 0xd4, 0x1c, 0xa2, 0x18, 0xa7, 0xd4, 0x45, 0xf1, 0x47, 0x00, 0xed, 0x6c, 0xdc, 0x93, 0x45, 0x1f, 0xc4, 0xa4, 0x1a, 0xaf, 0xd8, 0x1a, 0xe4, 0x66, 0xb0, 0x54, 0x36, 0xe5, 0xf7, 0x50, 0x9e, 0x31, 0x04, 0x7c, 0x7f, 0x25, 0x23, 0xa6, 0xe9, 0xc6, 0xa2, 0x37, 0xb6, 0xa6, 0x3e, 0x42, 0xf9, 0x9d, 0xfa, 0xc3, 0xf7, 0x00, 0x4c, 0x9f, 0x42, 0x74, 0x47, 0xf4, 0x5d, 0xb6, 0xe7, 0x83, 0xc1, 0xda, 0x9a, 0x0e, 0xcf, 0x79, 0x6a, 0xc8, 0x26, 0xbc, 0x52, 0x5d, 0x14, 0xbf, 0x85, 0xf9, 0x29, 0xf4, 0xa1, 0x49, 0x3f, 0xe8, 0xe4, 0xd6, 0xd4, 0x2f, 0xb0, 0x50, 0x18, 0x5a, 0x67, 0x03, 0x72, 0x01, 0x73, 0xed, 0x71, 0x1f, 0xb1, 0x3f, 0xb9, 0x50, 0x83, 0xe4, 0x12, 0x58, 0x56, 0x66, 0xba, 0xcb, 0xd6, 0xff, 0x65, 0x56, 0xa5, 0xca, 0x1f, 0xac, 0x9f, 0xe1, 0x7a, 0x77, 0xa0, 0xb6, 0x25, 0xdb, 0xec, 0xd0, 0x9f, 0x49, 0x23, 0x97, 0xb0, 0xdc, 0xa4, 0x6d, 0x79, 0xff, 0xbf, 0x56, 0xdc, 0x95, 0x72, 0x40, 0xa9, 0xff, 0xbd, 0xcd, 0xd2, 0x8f, 0x3d, 0x7d, 0x05, 0x00, 0x00, 0xff, 0xff, 0x84, 0x5c, 0xa4, 0x06, 0xc6, 0x01, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. var _ context.Context var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion4 // ShippingServiceClient is the client API for ShippingService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type ShippingServiceClient interface { CreateConsignment(ctx context.Context, in *Consignment, opts ...grpc.CallOption) (*Response, error) } type shippingServiceClient struct { cc *grpc.ClientConn } func NewShippingServiceClient(cc *grpc.ClientConn) ShippingServiceClient { return &shippingServiceClient{cc} } func (c *shippingServiceClient) CreateConsignment(ctx context.Context, in *Consignment, opts ...grpc.CallOption) (*Response, error) { out := new(Response) err := c.cc.Invoke(ctx, "/ShippingService/CreateConsignment", in, out, opts...) if err != nil { return nil, err } return out, nil } // ShippingServiceServer is the server API for ShippingService service. type ShippingServiceServer interface { CreateConsignment(context.Context, *Consignment) (*Response, error) } func RegisterShippingServiceServer(s *grpc.Server, srv ShippingServiceServer) { s.RegisterService(&_ShippingService_serviceDesc, srv) } func _ShippingService_CreateConsignment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Consignment) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(ShippingServiceServer).CreateConsignment(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/ShippingService/CreateConsignment", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ShippingServiceServer).CreateConsignment(ctx, req.(*Consignment)) } return interceptor(ctx, in, info, handler) } var _ShippingService_serviceDesc = grpc.ServiceDesc{ ServiceName: "ShippingService", HandlerType: (*ShippingServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "CreateConsignment", Handler: _ShippingService_CreateConsignment_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "consignment.proto", } 

Si, alors quelque chose s'est mal passĂ©. Faites attention aux arguments -I est le chemin oĂč le compilateur recherche les fichiers, --go_out oĂč un nouveau fichier sera créé. Il y a toujours de l'aide


 $ protoc -h 

Il s'agit du code généré automatiquement par les bibliothÚques gRPC / protobuf afin que vous puissiez associer votre définition de protobuf à votre propre code.


Nous écrirons main.go


main.go
 package seaport import ( "log" "net" //    pbf "seaport/consignment" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/reflection" ) const ( port = ":50051" ) //IRepository -   type IRepository interface { Create(*pbf.Consignment) (*pbf.Consignment, error) } // Repository -    , //        type Repository struct { consignments []*pbf.Consignment } //Create -    func (repo *Repository) Create(consignment *pbf.Consignment) (*pbf.Consignment, error) { updated := append(repo.consignments, consignment) repo.consignments = updated return consignment, nil } //         //       .     //         . . type service struct { repo IRepository } // CreateConsignment -        , //    create,      //     gRPC. func (s *service) CreateConsignment(ctx context.Context, req *pbf.Consignment) (*pbf.Response, error) { //      consignment, err := s.repo.Create(req) if err != nil { return nil, err } //   `Response`, //        return &pbf.Response{Created: true, Consignment: consignment}, nil } func main() { repo := &Repository{} //   gRPC    tcp lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() //      gRPC,    //        //  `Response`,       pbf.RegisterShippingServiceServer(s, &service{repo}) //      gRPC. reflection.Register(s) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } 

Veuillez lire attentivement les commentaires laissés dans le code. Apparemment, nous créons ici une logique d'implémentation dans laquelle nos méthodes gRPC interagissent en utilisant les formats générés, créant un nouveau serveur gRPC sur le port 50051. Maintenant, notre service gRPC y vivra.
Vous pouvez l'exécuter avec $ go run main.go , mais vous ne verrez rien et vous ne pourrez pas l'utiliser ... Alors, créons un client pour le voir en action.


Créons une interface de ligne de commande qui prend un fichier JSON et interagit avec notre service gRPC.


Dans le répertoire racine, créez un nouveau sous-répertoire de $ mkdir consignment-cli . Dans ce répertoire, créez un fichier cli.go avec le contenu suivant:


cli.go
 package main import ( "encoding/json" "io/ioutil" "log" "os" pbf "seaport/consignment" "golang.org/x/net/context" "google.golang.org/grpc" ) const ( address = "localhost:50051" defaultFilename = "consignment.json" ) //    func parseFile(file string) (*pbf.Consignment, error) { var consignment *pbf.Consignment data, err := ioutil.ReadFile(file) if err != nil { return nil, err } json.Unmarshal(data, &consignment) return consignment, err } func main() { //     conn, err := grpc.Dial(address, grpc.WithInsecure()) if err != nil { log.Fatalf("  : %v", err) } defer conn.Close() client := pbf.NewShippingServiceClient(conn) //    consignment.json, //          file := defaultFilename if len(os.Args) > 1 { file = os.Args[1] } consignment, err := parseFile(file) if err != nil { log.Fatalf("   : %v", err) } r, err := client.CreateConsignment(context.Background(), consignment) if err != nil { log.Fatalf("  : %v", err) } log.Printf(": %t", r.Created) } 

Créez maintenant un lot (consignment-cli / consignment.json):


 { "description": "  ", "weight": 100, "containers": [ { "customer_id": "_001", "user_id": "_001", "origin": " " } ], "vessel_id": "_001" } 

Maintenant, si vous exécutez $ go run main.go à partir du package seaport , puis exécutez $ go run cli.go dans un panneau de terminal séparé. Vous devriez voir le message «Créé: vrai».
Mais comment vérifier exactement ce qui a été créé? Mettons à jour notre service à l'aide de la méthode GetConsignments afin que nous puissions afficher tous nos lots créés.


consigment.proto
 //consigment.proto syntax = "proto3"; service ShippingService{ rpc CreateConsignment(Consignment) returns (Response) {} //    rpc GetConsignments(GetRequest) returns (Response) {} } message Consignment { int32 id = 1; string description = 2; int32 weight = 3; repeated Container containers = 4; string vessel_id = 5; } message Container { int32 id =1; string customer_id =2; string origin = 3; string user_id = 4; } //    message GetRequest {} message Response { bool created = 1; Consignment consignment = 2; //     //     repeated Consignment consignments = 3; } 

Donc, ici, nous avons créé une nouvelle méthode sur notre service appelée GetConsignments , nous avons également créé une nouvelle GetRequest , qui ne contient encore rien. Nous avons également ajouté un champ de lots envoyés à notre message de réponse. Vous remarquerez que le type ici a le mot-clé répété jusqu'au type. Comme vous l'avez probablement deviné, cela signifie simplement de traiter ce champ comme un tableau de ces types.


Ne vous précipitez pas pour exécuter le programme, l'implémentation de nos méthodes gRPC est basée sur la correspondance de l'interface créée par la bibliothÚque protobuf, nous devons nous assurer que notre implémentation correspond à notre définition de 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 } 

Ici, nous avons inclus notre nouvelle méthode GetConsignments, mis à jour notre référentiel et notre interface, respectivement créés dans la définition consignments.proto. Si vous exécutez à nouveau $ go run main.go , le programme devrait fonctionner à nouveau.


Mettons à jour notre outil cli pour inclure la possibilité d'appeler cette méthode et il est possible de lister nos parties:


cli.go
 package main import ( "encoding/json" "io/ioutil" "log" "os" pbf "seaport/consignment" "golang.org/x/net/context" "google.golang.org/grpc" ) const ( address = "localhost:50051" defaultFilename = "consignment.json" ) //    func parseFile(file string) (*pbf.Consignment, error) { var consignment *pbf.Consignment data, err := ioutil.ReadFile(file) if err != nil { return nil, err } json.Unmarshal(data, &consignment) return consignment, err } func main() { //     conn, err := grpc.Dial(address, grpc.WithInsecure()) if err != nil { log.Fatalf("  : %v", err) } defer conn.Close() client := pbf.NewShippingServiceClient(conn) //    consignment.json, //          file := defaultFilename if len(os.Args) > 1 { file = os.Args[1] } consignment, err := parseFile(file) if err != nil { log.Fatalf("   : %v", err) } r, err := client.CreateConsignment(context.Background(), consignment) if err != nil { log.Fatalf("  : %v", err) } log.Printf(": %t", r.Created) getAll, err := client.GetConsignments(context.Background(), &pbf.GetRequest{}) if err != nil { log.Fatalf("    : %v", err) } for _, cns := range getAll.Consignments { fmt.Printf("Id: %v\n", cns.GetId()) fmt.Printf("Description: %v\n", cns.GetDescription()) fmt.Printf("Weight: %d\n", cns.GetWeight()) fmt.Printf("VesselId: %v\n", cns.GetVesselId()) for _, cnt := range cns.GetContainers() { fmt.Printf("\tId: %v\n", cnt.GetId()) fmt.Printf("\tUserId: %v\n", cnt.GetUserId()) fmt.Printf("\tCustomerId: %v\n", cnt.GetCustomerId()) fmt.Printf("\tOrigin: %v\n", cnt.GetOrigin()) } } } 

Ajoutez le code ci-dessus à cli.go et exécutez $ go run cli.go à nouveau . Le client exécutera CreateConsignment puis appellera GetConsignments. Et vous devriez voir que dans la liste des réponses contient la composition du parti.


Ainsi, nous avons le premier microservice et le premier client Ă  interagir avec lui en utilisant protobuf et gRPC.


La prochaine partie de cette série comprendra l'intégration go-micro, qui est une base puissante pour la création de microservices basés sur gRPC. Nous allons également créer notre deuxiÚme service. Considérez le travail de nos services dans les conteneurs Docker, dans la prochaine partie de cette série d'articles.

Source: https://habr.com/ru/post/fr425653/


All Articles