Go实战--golang中使用gRPC和Protobuf实现高性能api(golang/protobuf、google.golang.org/grpc)

前端之家收集整理的这篇文章主要介绍了Go实战--golang中使用gRPC和Protobuf实现高性能api(golang/protobuf、google.golang.org/grpc)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

生命不止,继续 go go go !!!

号外号外,插播一条广告,通过博客的uv可以看到周五,程序员是不怎么干活的:

本篇博客,使用gRPC和Protobuf,实现所谓的高性能api。

protobuf

golang中的protobuf大家应该不会很陌生,之前也有博客介绍过:
Go实战–go中使用google/protobuf(The way to go)

Protocol Buffers (a.k.a.,protobuf) are Google’s language-neutral,platform-neutral,extensible mechanism for serializing structured data. You can find protobuf’s documentation on the Google Developers site.

获取

  1. go get -u github.com/golang/protobuf/proto
  2. go get -u github.com/golang/protobuf/protoc-gen-go

Protobuf语法
下面简要介绍Protobuf语法:
参考:http://www.jb51.cc/article/p-fqzbjyww-d.html
官方:
https://developers.google.com/protocol-buffers/
https://developers.google.com/protocol-buffers/docs/gotutorial

Message定义
一个message类型定义描述了一个请求或相应的消息格式,可以包含多种类型字段。例如定义一个搜索请求的消息格式,每个请求包含查询字符串、页码、每页数目。

  1. Syntax = "proto3";
  2. package tutorial;

首行声明使用的protobuf版本为proto3

  1. message Person {
  2. string name = 1;
  3. int32 id = 2; // Unique ID number for this person.
  4. string email = 3;
  5.  
  6. enum PhoneType {
  7. MOBILE = 0;
  8. HOME = 1;
  9. WORK = 2;
  10. }
  11.  
  12. message PhoneNumber {
  13. string number = 1;
  14. PhoneType type = 2;
  15. }
  16.  
  17. repeated PhoneNumber phones = 4;
  18. }
  19.  
  20. // Our address book file is just one of these.
  21. message AddressBook {
  22. repeated Person people = 1;
  23. }

生成

  1. protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto

写消息

  1. book := &pb.AddressBook{}
  2. // ...
  3.  
  4. // Write the new address book back to disk.
  5. out,err := proto.Marshal(book)
  6. if err != nil {
  7. log.Fatalln("Failed to encode address book:",err)
  8. }
  9. if err := @R_404_437@til.WriteFile(fname,out,0644); err != nil {
  10. log.Fatalln("Failed to write address book:",err)
  11. }

读消息

  1. // Read the existing address book.
  2. in,err := @R_404_437@til.ReadFile(fname)
  3. if err != nil {
  4. log.Fatalln("Error reading file:",err)
  5. }
  6. book := &pb.AddressBook{}
  7. if err := proto.Unmarshal(in,book); err != nil {
  8. log.Fatalln("Failed to parse address book:",err)
  9. }

grpc

golang中的rpc大家也不会陌生,之前也有介绍过奥:
Go实战–go中使用rpc(The way to go)

什么是rpc
RPC是Remote Procedure CallProtocol的缩写,即—远程过程调用协议。

RPC是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用或远程方法调用,信息数据。通过它可以使函数调用模式网络化。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

net/rpc
Go语言标准库能够自带一个rpc框架还是非常给力的,这可以很大程度的降低写后端网络通信服务的门槛,特别是在大规模的分布式系统中,rpc基本是跨机器通信的标配。rpc能够最大程度屏蔽网络细节,让开发者专注在服务功能的开发上面

什么是grpc
gRPC 是一个高性能、开源、通用的RPC框架,由Google推出,基于HTTP/2协议标准设计开发,默认采用Protocol Buffers数据序列化协议,支持多种开发语言。gRPC提供了一种简单的方法来精确的定义服务,并且为客户端和服务端自动生成可靠的功能库。

在gRPC客户端可以直接调用不同服务器上的远程程序,使用姿势看起来就像调用本地程序一样,很容易去构建分布式应用和服务。和很多RPC系统一样,服务端负责实现定义好的接口并处理客户端的请求,客户端根据接口描述直接调用需要的服务。客户端和服务端可以分别使用gRPC支持的不同语言实现。

grpc-go
github地址:
https://github.com/grpc/grpc-go

Star: 4402

文档地址:
https://godoc.org/google.golang.org/grpc

获取
go get -u google.golang.org/grpc

@H_404_187@应用

文件结构

proto文件
customer.proto

  1. Syntax = "proto3";
  2. package customer;
  3.  
  4.  
  5. // The Customer service definition.
  6. service Customer {
  7. // Get all Customers with filter - A server-to-client streaming RPC.
  8. rpc GetCustomers(CustomerFilter) returns (stream CustomerRequest) {}
  9. // Create a new Customer - A simple RPC
  10. rpc CreateCustomer (CustomerRequest) returns (CustomerResponse) {}
  11. }
  12.  
  13. // Request message for creating a new customer
  14. message CustomerRequest {
  15. int32 id = 1; // Unique ID number for a Customer.
  16. string name = 2;
  17. string email = 3;
  18. string phone= 4;
  19.  
  20. message Address {
  21. string street = 1;
  22. string city = 2;
  23. string state = 3;
  24. string zip = 4;
  25. bool isShippingAddress = 5;
  26. }
  27.  
  28. repeated Address addresses = 5;
  29. }
  30.  
  31. message CustomerResponse {
  32. int32 id = 1;
  33. bool success = 2;
  34. }
  35. message CustomerFilter {
  36. string keyword = 1;
  37. }

生成customer.pb.go

  1. protoc --go_out=plugins=grpc:. customer.proto
  1. // Code generated by protoc-gen-go. DO NOT EDIT.
  2. // source: customer.proto
  3.  
  4. /* Package customer is a generated protocol buffer package. It is generated from these files: customer.proto It has these top-level messages: CustomerRequest CustomerResponse CustomerFilter */
  5. package customer
  6.  
  7. import proto "github.com/golang/protobuf/proto"
  8. import fmt "fmt"
  9. import math "math"
  10.  
  11. import (
  12. context "golang.org/x/net/context"
  13. grpc "google.golang.org/grpc"
  14. )
  15.  
  16. // Reference imports to suppress errors if they are not otherwise used.
  17. var _ = proto.Marshal
  18. var _ = fmt.Errorf
  19. var _ = math.Inf
  20.  
  21. // This is a compile-time assertion to ensure that this generated file
  22. // is compatible with the proto package it is being compiled against.
  23. // A compilation error at this line likely means your copy of the
  24. // proto package needs to be updated.
  25. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
  26.  
  27. // Request message for creating a new customer
  28. type CustomerRequest struct {
  29. Id int32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
  30. Name string `protobuf:"bytes,2,name=name" json:"name,omitempty"`
  31. Email string `protobuf:"bytes,3,name=email" json:"email,omitempty"`
  32. Phone string `protobuf:"bytes,4,name=phone" json:"phone,omitempty"`
  33. Addresses []*CustomerRequest_Address `protobuf:"bytes,5,rep,name=addresses" json:"addresses,omitempty"`
  34. }
  35.  
  36. func (m *CustomerRequest) Reset() { *m = CustomerRequest{} }
  37. func (m *CustomerRequest) String() string { return proto.CompactTextString(m) }
  38. func (*CustomerRequest) ProtoMessage() {}
  39. func (*CustomerRequest) Descriptor() ([]byte,[]int) { return fileDescriptor0,[]int{0} }
  40.  
  41. func (m *CustomerRequest) GetId() int32 {
  42. if m != nil {
  43. return m.Id
  44. }
  45. return 0
  46. }
  47.  
  48. func (m *CustomerRequest) GetName() string {
  49. if m != nil {
  50. return m.Name
  51. }
  52. return ""
  53. }
  54.  
  55. func (m *CustomerRequest) GetEmail() string {
  56. if m != nil {
  57. return m.Email
  58. }
  59. return ""
  60. }
  61.  
  62. func (m *CustomerRequest) GetPhone() string {
  63. if m != nil {
  64. return m.Phone
  65. }
  66. return ""
  67. }
  68.  
  69. func (m *CustomerRequest) GetAddresses() []*CustomerRequest_Address {
  70. if m != nil {
  71. return m.Addresses
  72. }
  73. return nil
  74. }
  75.  
  76. type CustomerRequest_Address struct {
  77. Street string `protobuf:"bytes,name=street" json:"street,omitempty"`
  78. City string `protobuf:"bytes,name=city" json:"city,omitempty"`
  79. State string `protobuf:"bytes,name=state" json:"state,omitempty"`
  80. Zip string `protobuf:"bytes,name=zip" json:"zip,omitempty"`
  81. IsShippingAddress bool `protobuf:"varint,name=isShippingAddress" json:"isShippingAddress,omitempty"`
  82. }
  83.  
  84. func (m *CustomerRequest_Address) Reset() { *m = CustomerRequest_Address{} }
  85. func (m *CustomerRequest_Address) String() string { return proto.CompactTextString(m) }
  86. func (*CustomerRequest_Address) ProtoMessage() {}
  87. func (*CustomerRequest_Address) Descriptor() ([]byte,[]int{0, 0} }
  88.  
  89. func (m *CustomerRequest_Address) GetStreet() string {
  90. if m != nil {
  91. return m.Street
  92. }
  93. return ""
  94. }
  95.  
  96. func (m *CustomerRequest_Address) GetCity() string {
  97. if m != nil {
  98. return m.City
  99. }
  100. return ""
  101. }
  102.  
  103. func (m *CustomerRequest_Address) GetState() string {
  104. if m != nil {
  105. return m.State
  106. }
  107. return ""
  108. }
  109.  
  110. func (m *CustomerRequest_Address) GetZip() string {
  111. if m != nil {
  112. return m.Zip
  113. }
  114. return ""
  115. }
  116.  
  117. func (m *CustomerRequest_Address) GetIsShippingAddress() bool {
  118. if m != nil {
  119. return m.IsShippingAddress
  120. }
  121. return false
  122. }
  123.  
  124. type CustomerResponse struct {
  125. Id int32 `protobuf:"varint,omitempty"`
  126. Success bool `protobuf:"varint,name=success" json:"success,omitempty"`
  127. }
  128.  
  129. func (m *CustomerResponse) Reset() { *m = CustomerResponse{} }
  130. func (m *CustomerResponse) String() string { return proto.CompactTextString(m) }
  131. func (*CustomerResponse) ProtoMessage() {}
  132. func (*CustomerResponse) Descriptor() ([]byte,[]int{1} }
  133.  
  134. func (m *CustomerResponse) GetId() int32 {
  135. if m != nil {
  136. return m.Id
  137. }
  138. return 0
  139. }
  140.  
  141. func (m *CustomerResponse) GetSuccess() bool {
  142. if m != nil {
  143. return m.Success
  144. }
  145. return false
  146. }
  147.  
  148. type CustomerFilter struct {
  149. Keyword string `protobuf:"bytes,name=keyword" json:"keyword,omitempty"`
  150. }
  151.  
  152. func (m *CustomerFilter) Reset() { *m = CustomerFilter{} }
  153. func (m *CustomerFilter) String() string { return proto.CompactTextString(m) }
  154. func (*CustomerFilter) ProtoMessage() {}
  155. func (*CustomerFilter) Descriptor() ([]byte,[]int{2} }
  156.  
  157. func (m *CustomerFilter) GetKeyword() string {
  158. if m != nil {
  159. return m.Keyword
  160. }
  161. return ""
  162. }
  163.  
  164. func init() {
  165. proto.RegisterType((*CustomerRequest)(nil),"customer.CustomerRequest")
  166. proto.RegisterType((*CustomerRequest_Address)(nil),"customer.CustomerRequest.Address")
  167. proto.RegisterType((*CustomerResponse)(nil),"customer.CustomerResponse")
  168. proto.RegisterType((*CustomerFilter)(nil),"customer.CustomerFilter")
  169. }
  170.  
  171. // Reference imports to suppress errors if they are not otherwise used.
  172. var _ context.Context
  173. var _ grpc.ClientConn
  174.  
  175. // This is a compile-time assertion to ensure that this generated file
  176. // is compatible with the grpc package it is being compiled against.
  177. const _ = grpc.SupportPackageIsVersion4
  178.  
  179. // Client API for Customer service
  180.  
  181. type CustomerClient interface {
  182. // Get all Customers with filter - A server-to-client streaming RPC.
  183. GetCustomers(ctx context.Context,in *CustomerFilter,opts ...grpc.CallOption) (Customer_GetCustomersClient,error)
  184. // Create a new Customer - A simple RPC
  185. CreateCustomer(ctx context.Context,in *CustomerRequest,opts ...grpc.CallOption) (*CustomerResponse,error)
  186. }
  187.  
  188. type customerClient struct {
  189. cc *grpc.ClientConn
  190. }
  191.  
  192. func NewCustomerClient(cc *grpc.ClientConn) CustomerClient {
  193. return &customerClient{cc}
  194. }
  195.  
  196. func (c *customerClient) GetCustomers(ctx context.Context,error) {
  197. stream,err := grpc.NewClientStream(ctx,&_Customer_serviceDesc.Streams[0],c.cc,"/customer.Customer/GetCustomers",opts...)
  198. if err != nil {
  199. return nil,err
  200. }
  201. x := &customerGetCustomersClient{stream}
  202. if err := x.ClientStream.SendMsg(in); err != nil {
  203. return nil,err
  204. }
  205. if err := x.ClientStream.CloseSend(); err != nil {
  206. return nil,err
  207. }
  208. return x,nil
  209. }
  210.  
  211. type Customer_GetCustomersClient interface {
  212. Recv() (*CustomerRequest,error)
  213. grpc.ClientStream
  214. }
  215.  
  216. type customerGetCustomersClient struct {
  217. grpc.ClientStream
  218. }
  219.  
  220. func (x *customerGetCustomersClient) Recv() (*CustomerRequest,error) {
  221. m := new(CustomerRequest)
  222. if err := x.ClientStream.RecvMsg(m); err != nil {
  223. return nil,err
  224. }
  225. return m,nil
  226. }
  227.  
  228. func (c *customerClient) CreateCustomer(ctx context.Context,error) {
  229. out := new(CustomerResponse)
  230. err := grpc.Invoke(ctx,"/customer.Customer/CreateCustomer",in,err
  231. }
  232. return out,nil
  233. }
  234.  
  235. // Server API for Customer service
  236.  
  237. type CustomerServer interface {
  238. // Get all Customers with filter - A server-to-client streaming RPC.
  239. GetCustomers(*CustomerFilter,Customer_GetCustomeRSServer) error
  240. // Create a new Customer - A simple RPC
  241. CreateCustomer(context.Context,*CustomerRequest) (*CustomerResponse,error)
  242. }
  243.  
  244. func RegisterCustomerServer(s *grpc.Server,srv CustomerServer) {
  245. s.RegisterService(&_Customer_serviceDesc,srv)
  246. }
  247.  
  248. func _Customer_GetCustomers_Handler(srv interface{},stream grpc.ServerStream) error {
  249. m := new(CustomerFilter)
  250. if err := stream.RecvMsg(m); err != nil {
  251. return err
  252. }
  253. return srv.(CustomerServer).GetCustomers(m,&customerGetCustomeRSServer{stream})
  254. }
  255.  
  256. type Customer_GetCustomeRSServer interface {
  257. Send(*CustomerRequest) error
  258. grpc.ServerStream
  259. }
  260.  
  261. type customerGetCustomeRSServer struct {
  262. grpc.ServerStream
  263. }
  264.  
  265. func (x *customerGetCustomeRSServer) Send(m *CustomerRequest) error {
  266. return x.ServerStream.SendMsg(m)
  267. }
  268.  
  269. func _Customer_CreateCustomer_Handler(srv interface{},ctx context.Context,dec func(interface{}) error,interceptor grpc.UnaryServerInterceptor) (interface{},error) {
  270. in := new(CustomerRequest)
  271. if err := dec(in); err != nil {
  272. return nil,err
  273. }
  274. if interceptor == nil {
  275. return srv.(CustomerServer).CreateCustomer(ctx,in)
  276. }
  277. info := &grpc.UnaryServerInfo{
  278. Server: srv,FullMethod: "/customer.Customer/CreateCustomer",}
  279. handler := func(ctx context.Context,req interface{}) (interface{},error) {
  280. return srv.(CustomerServer).CreateCustomer(ctx,req.(*CustomerRequest))
  281. }
  282. return interceptor(ctx,info,handler)
  283. }
  284.  
  285. var _Customer_serviceDesc = grpc.ServiceDesc{
  286. ServiceName: "customer.Customer",HandlerType: (*CustomerServer)(nil),Methods: []grpc.MethodDesc{
  287. {
  288. MethodName: "CreateCustomer",Handler: _Customer_CreateCustomer_Handler,},Streams: []grpc.StreamDesc{
  289. {
  290. StreamName: "GetCustomers",Handler: _Customer_GetCustomers_Handler,ServerStreams: true,Metadata: "customer.proto",}
  291.  
  292. func init() { proto.RegisterFile("customer.proto",fileDescriptor0) }
  293.  
  294. var fileDescriptor0 = []byte{
  295. // 326 bytes of a gzipped FileDescriptorProto
  296. 0x1f, 0x8b, 0x08, 0x00, 0x02, 0xff, 0x74, 0x92, 0xef, 0x4a, 0xc3, 0x30, 0x10, 0xc0, 0x97, 0x6e, 0xdd, 0x9f, 0x53, 0xea, 0x0c, 0x22, 0xb1, 0x6a, 0x3f, 0x15, 0x91, 0x21, 0xf3, 0xab, 0x20, 0x32, 0x70, 0xf8, 0xb5, 0x3e, 0x41, 0x6d, 0x0f, 0x17, 0xdc, 0xda, 0x9a, 0xcb, 0x90, 0xf9, 0x0a, 0xbe, 0x83, 0xcf, 0xe0, 0x23, 0xd2, 0x66, 0x03, 0xe7, 0x72, 0x77, 0xe5, 0x04, 0x42, 0x35, 0xa9, 0x55, 0xa5, 0x2b, 0x1c, 0x78, 0x6b, 0xc5, 0xf7, 0xe6, 0x01, 0xb2, 0x2c, 0x62, 0x89, 0x7a, 0x65, 0xb6, 0xe1, 0x45, 0x19, 0x36, 0x67, 0xe3, 0x2a, 0x93, 0x4b, 0xd1, 0xc9, 0x06, 0x4c, 0x5e, 0x54, 0x25, 0x8a, 0x61, 0x94, 0x85, 0x24, 0x47, 0xe4, 0x68, 0x39, 0xd9, 0x1a, 0xfd, 0xb9, 0x7d, 0xf2, 0xd0, 0xa6, 0xbb, 0x9e, 0xf0, 0xc1, 0xa0, 0x4d, 0x73, 0xe8, 0x56, 0x88, 0x8e, 0x96, 0x8c, 0x64, 0x2e, 0xf5, 0xc6, 0x49, 0xd8, 0xce, 0x34, 0x3a, 0x0b, 0x7c, 0x4f, 0x59, 0xb7, 0x26, 0xd7, 0xe9, 0x79, 0xeb, 0x5a, 0xaf, 0xed, 0xc2, 0x8f, 0x58, 0xe2, 0x3b, 0x18, 0x9c, 0xae, 0xbd, 0x95, 0x09, 0xcd, 0x71, 0x5f, 0xba, 0x1f, 0x52, 0xa3, 0x6f, 0xb8, 0xa8, 0xf4, 0x9b, 0x51, 0xf6, 0x57, 0x2f, 0x0e, 0x37, 0xee, 0xfe, 0xfa, 0x43, 0xd4, 0x3c, 0xbc, 0x0d, 0xde, 0xd3, 0x60,}

server/main.go

  1. package main
  2.  
  3. import (
  4. "log"
  5. "net"
  6. "strings"
  7.  
  8. "golang.org/x/net/context"
  9. "google.golang.org/grpc"
  10.  
  11. pb "go_grpc_protobuf/customer"
  12. )
  13.  
  14. const (
  15. port = ":50051"
  16. )
  17.  
  18. // server is used to implement customer.CustomerServer.
  19. type server struct {
  20. savedCustomers []*pb.CustomerRequest
  21. }
  22.  
  23. // CreateCustomer creates a new Customer
  24. func (s *server) CreateCustomer(ctx context.Context,in *pb.CustomerRequest) (*pb.CustomerResponse,error) {
  25. s.savedCustomers = append(s.savedCustomers,in)
  26. return &pb.CustomerResponse{Id: in.Id,Success: true},nil
  27. }
  28.  
  29. // GetCustomers returns all customers by given filter
  30. func (s *server) GetCustomers(filter *pb.CustomerFilter,stream pb.Customer_GetCustomeRSServer) error {
  31. for _,customer := range s.savedCustomers {
  32. if filter.Keyword != "" {
  33. if !strings.Contains(customer.Name,filter.Keyword) {
  34. continue
  35. }
  36. }
  37. if err := stream.Send(customer); err != nil {
  38. return err
  39. }
  40. }
  41. return nil
  42. }
  43.  
  44. func main() {
  45. lis,err := net.Listen("tcp",port)
  46. if err != nil {
  47. log.Fatalf("Failed to listen: %v",err)
  48. }
  49. // Creates a new gRPC server
  50. s := grpc.NewServer()
  51. pb.RegisterCustomerServer(s,&server{})
  52. s.Serve(lis)
  53. }

client/main.go

  1. package main
  2.  
  3. import (
  4. "io"
  5. "log"
  6.  
  7. "golang.org/x/net/context"
  8. "google.golang.org/grpc"
  9.  
  10. pb "go_grpc_protobuf/customer"
  11. )
  12.  
  13. const (
  14. address = "localhost:50051"
  15. )
  16.  
  17. // createCustomer calls the RPC method CreateCustomer of CustomerServer
  18. func createCustomer(client pb.CustomerClient,customer *pb.CustomerRequest) {
  19. resp,err := client.CreateCustomer(context.Background(),customer)
  20. if err != nil {
  21. log.Fatalf("Could not create Customer: %v",err)
  22. }
  23. if resp.Success {
  24. log.Printf("A new Customer has been added with id: %d",resp.Id)
  25. }
  26. }
  27.  
  28. // getCustomers calls the RPC method GetCustomers of CustomerServer
  29. func getCustomers(client pb.CustomerClient,filter *pb.CustomerFilter) {
  30. // calling the streaming API
  31. stream,err := client.GetCustomers(context.Background(),filter)
  32. if err != nil {
  33. log.Fatalf("Error on get customers: %v",err)
  34. }
  35. for {
  36. customer,err := stream.Recv()
  37. if err == io.EOF {
  38. break
  39. }
  40. if err != nil {
  41. log.Fatalf("%v.GetCustomers(_) = _,%v",client,err)
  42. }
  43. log.Printf("Customer: %v",customer)
  44. }
  45. }
  46. func main() {
  47. // Set up a connection to the gRPC server.
  48. conn,err := grpc.Dial(address,grpc.WithInsecure())
  49. if err != nil {
  50. log.Fatalf("did not connect: %v",err)
  51. }
  52. defer conn.Close()
  53. // Creates a new CustomerClient
  54. client := pb.NewCustomerClient(conn)
  55.  
  56. customer := &pb.CustomerRequest{
  57. Id: 101,Name: "Shiju Varghese",Email: "shiju@xyz.com",Phone: "732-757-2923",Addresses: []*pb.CustomerRequest_Address{
  58. &pb.CustomerRequest_Address{
  59. Street: "1 Mission Street",City: "San Francisco",State: "CA",Zip: "94105",IsShippingAddress: false,&pb.CustomerRequest_Address{
  60. Street: "Greenfield",City: "Kochi",State: "KL",Zip: "68356",IsShippingAddress: true,}
  61.  
  62. // Create a new customer
  63. createCustomer(client,customer)
  64.  
  65. customer = &pb.CustomerRequest{
  66. Id: 102,Name: "Irene Rose",Email: "irene@xyz.com",Phone: "732-757-2924",customer)
  67. // Filter with an empty Keyword
  68. filter := &pb.CustomerFilter{Keyword: ""}
  69. getCustomers(client,filter)
  70. }

运行server,运行client,输出

  1. 2017/12/07 11:55:59 A new Customer has been added with id: 101
  2. 2017/12/07 11:55:59 A new Customer has been added with id: 102
  3. 2017/12/07 11:55:59 Customer: id:101 name:"Shiju Varghese" email:"shiju@xyz.com" phone:"732-757-2923" addresses:<street:"1 Mission Street" city:"San Francisco" state:"CA" zip:"94105" > addresses:<street:"Greenfield" city:"Kochi" state:"KL" zip:"68356" isShippingAddress:true >
  4. 2017/12/07 11:55:59 Customer: id:102 name:"Irene Rose" email:"irene@xyz.com" phone:"732-757-2924" addresses:<street:"1 Mission Street" city:"San Francisco" state:"CA" zip:"94105" isShippingAddress:true >

猜你在找的Go相关文章