بناء بنية Microservice على Golang و gRPC ، الجزء 1

مقدمة في هندسة الخدمات الصغيرة


الجزء 1 من أصل 10


تعديل مقالات إيوان فالنتاين.


هذه سلسلة من عشرة أجزاء ، سأحاول أن أكتب عن بناء الخدمات الصغيرة على Golang مرة في الشهر. سأستخدم protobuf و gRPC كبروتوكول النقل الرئيسي.


المكدس الذي استخدمته: golang و mongodb و grpc و docker و Google Cloud و Kubernetes و NATS و CircleCI و Terraform و go-micro.


لماذا أحتاج هذا؟ حيث استغرق الأمر مني وقتًا طويلاً لفهمها وحل المشكلات المتراكمة. وأردت أيضًا أن أشارككم ما تعلمته عن إنشاء واختبار ونشر خدمات صغيرة على Go وتقنيات جديدة أخرى.


في هذا الجزء ، أريد أن أعرض المفاهيم والتقنيات الأساسية لبناء الخدمات الصغيرة. دعنا نكتب تنفيذ بسيط. سيحتوي المشروع على الكيانات التالية:


  • البضائع
  • جرد
  • المحاكمة
  • المستخدمين
  • الأدوار
  • المصادقة


للمضي قدمًا ، تحتاج إلى تثبيت Golang والمكتبات الضرورية ، بالإضافة إلى إنشاء مستودع git.


النظرية


ما هي هندسة الخدمات الصغيرة؟


تقوم الخدمات الصغيرة بعزل وظيفة منفصلة في خدمة ، مكتفية ذاتيا من حيث الوظيفة التي تؤديها هذه الخدمة. للتوافق مع الخدمات الأخرى ، لديه واجهة معروفة ومحددة مسبقًا.
تتواصل الخدمات الصغيرة مع بعضها البعض باستخدام الرسائل المرسلة من خلال وسيط رسائل وسيط.



بفضل بنية الخدمات الصغيرة ، لا يمكن تحجيم التطبيق بالكامل ، ولكن في أجزاء. على سبيل المثال ، إذا كانت خدمة التخويل "تشنجات" أكثر من غيرها ، فيمكننا زيادة عدد الحالات. يتماشى هذا المفهوم مع مفاهيم الحوسبة السحابية والحاويات بشكل عام.


لماذا جولانج


الخدمات الدقيقة مدعومة بجميع اللغات تقريبًا ، بعد كل شيء ، الخدمات الصغيرة هي مفهوم ، وليست بنية أو أداة محددة. ومع ذلك ، فإن بعض اللغات أكثر ملاءمة ، بالإضافة إلى ذلك ، لديها دعم أفضل للخدمات الدقيقة من غيرها. لغة واحدة مع دعم كبير هي Golang.


تلبية protobuf / gRPC


كما ذكرنا سابقًا ، تنقسم الخدمات الدقيقة إلى قواعد تعليمات برمجية منفصلة ، واحدة من المشكلات المهمة المرتبطة بالخدمات الدقيقة هي الاتصال. إذا كان لديك متراصة ، فيمكنك ببساطة الاتصال بالرمز مباشرة من مكان آخر في برنامجك.


لحل مشكلة الاتصال ، يمكننا استخدام نهج REST التقليدي ونقل البيانات بتنسيق JSON أو XML عبر HTTP. لكن هذا النهج له عيوبه ، على سبيل المثال ، أنه قبل إرسال رسالة ، عليك ترميز بياناتك ، وفك تشفيرها مرة أخرى على الجانب المتلقي. وهذا عام ويزيد من تعقيد الشفرة.


هناك حل! هذا هو بروتوكول gRPC - بروتوكول ثنائي الوزن وخفيف الوزن يلغي إرسال رؤوس HTTP ، وهذا سيوفر لنا بعض وحدات البايت. يتضمن بروتوكول HTTP2 المستقبلي أيضًا استخدام البيانات الثنائية ، التي تتحدث مرة أخرى لصالح gRPC. HTTP2 يسمح بالاتصال ثنائي الاتجاه ، وهو رائع!


يسمح لك GRPC أيضًا بتحديد الواجهة لخدمتك بتنسيق سهل - وهذا هو> protobuf .


تدرب


قم بإنشاء الملف /project/consigment.proto.
وثائق 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; } 

هذا مثال بسيط يحتوي على الخدمة التي تريد تقديمها لخدمات أخرى: خدمة 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 

يجب أن يكون الإخراج ملفًا:


شحنة. 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", } 

إذا ، حدث خطأ ما. انتبه إلى الحجج - أنا هو المسار الذي يبحث فيه المترجم عن الملفات ، --go_out حيث سيتم إنشاء ملف جديد. هناك دائما مساعدة


 $ protoc -h 

هذا هو الرمز الذي يتم إنشاؤه تلقائيًا بواسطة مكتبات gRPC / protobuf بحيث يمكنك ربط تعريف protobuf الخاص بك مع التعليمات البرمجية الخاصة بك.


سنكتب 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) } } 

يرجى قراءة التعليقات المتبقية في التعليمات البرمجية بعناية. على ما يبدو ، نحن هنا ننشئ منطق التنفيذ الذي تتفاعل فيه أساليب gRPC الخاصة بنا باستخدام التنسيقات التي تم إنشاؤها ، وإنشاء خادم gRPC جديد على المنفذ 50051. الآن ستعيش خدمة gRPC هناك.
يمكنك تشغيل هذا باستخدام $ go run main.go ، لكنك لن ترى أي شيء ولن تتمكن من استخدامه ... لذا ، لننشئ عميلًا لرؤيته عمليًا .


لنقم بإنشاء واجهة سطر أوامر تأخذ ملف JSON وتتفاعل مع خدمة gRPC.


في الدليل الجذر ، قم بإنشاء دليل فرعي جديد لـ $ mkdir consignment-cli . في هذا الدليل ، قم بإنشاء ملف cli.go بالمحتويات التالية:


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) } 

الآن قم بإنشاء مجموعة (consignment-cli / consignment.json):


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

الآن ، إذا قمت بتشغيل $ go ، قم بتشغيل main.go من حزمة الميناء ، ثم قمت بتشغيل $ go run cli.go في لوحة طرفية منفصلة. يجب أن تشاهد الرسالة "Created: true".
ولكن كيف يمكننا التحقق مما تم إنشاؤه بالضبط؟ دعنا نقوم بتحديث خدمتنا باستخدام طريقة GetConsignments حتى نتمكن من عرض جميع الدفعات التي تم إنشاؤها.


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; } 

لذا ، قمنا هنا بإنشاء طريقة جديدة على خدمتنا تسمى GetConsignments ، وقمنا أيضًا بإنشاء GetRequest جديدة ، والتي لا تحتوي على أي شيء حتى الآن. أضفنا أيضًا حقل دفعات مرسلة إلى رسالة الرد الخاصة بنا. ستلاحظ أن النوع هنا يحتوي على الكلمة الرئيسية المتكررة حتى النوع. هذا ، كما قد تكون خمنت على الأرجح ، يعني ببساطة معالجة هذا المجال كمجموعة من هذه الأنواع.


لا تتسرع في تشغيل البرنامج ، حيث يعتمد تنفيذ أساليبنا gRPC على مطابقة الواجهة التي تم إنشاؤها بواسطة مكتبة protobuf ، نحتاج إلى التأكد من تطابق التنفيذ مع تعريفنا الأولي.


 //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 الجديدة الخاصة بنا ، وتحديث المستودع والواجهة الخاصة بنا ، والتي تم إنشاؤها على التوالي في تعريف الملف. إذا قمت بتشغيل $ go run main.go مرة أخرى ، يجب أن يعمل البرنامج مرة أخرى.


دعنا نقوم بتحديث أداة cli الخاصة بنا لتشمل القدرة على استدعاء هذه الطريقة ومن الممكن سرد أطرافنا:


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()) } } } 

أضف الكود أعلاه إلى cli.go وقم بتشغيل $ go run cli.go مرة أخرى . سيقوم العميل بتشغيل CreateConsignment ثم استدعاء GetConsignments. ويجب أن ترى أنه في قائمة الإجابات يحتوي على تركيبة الحزب.


وبالتالي ، لدينا أول خدمة صغيرة وعميل يتفاعل معها باستخدام protobuf و gRPC.


سيتضمن الجزء التالي من هذه السلسلة تكامل go-micro ، الذي يعد أساسًا قويًا لإنشاء الخدمات الدقيقة القائمة على gRPC. سننشئ أيضًا خدمتنا الثانية. ضع في اعتبارك عمل خدماتنا في حاويات Docker ، في الجزء التالي من سلسلة المقالات هذه.

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


All Articles