diff --git a/grpc/README.md b/grpc/README.md index 929d538d..22d94ade 100644 --- a/grpc/README.md +++ b/grpc/README.md @@ -4,14 +4,15 @@ A basic gRPC microservice architecture, involving a gRPC server and a client, that allow users to buy items from an online store. The ecommerce interface provided by the gRPC server, allows users to -list and buy grocery via the public client/server application. +list and buy groceries via the public client/server application. The client/server application is deployed as a Code Engine application, with a public endpoint. While the gRPC server is deployed as a Code Engine application only exposed to the Code Engine project. -See image: +See architecture diagram: +![Alt text](images/grpc-architecture.png) ## Source code @@ -19,8 +20,9 @@ Check the source code if you want to understand how this works. In general, we provided three directories: - `/ecommerce` directory hosts the protobuf files and declares the `grocery` - interface for a set of remote procedures that can be called by clients. -- `/server` directory hosts the `grocery` interface implementation and creates a server. + interface for a set of remote procedures that can be called by clients. This directory + can be regenerated upon changes to the `.proto` file, by calling the `./regenerate-grpc-code.sh`. +- `/server` directory hosts the `grocery` interface implementation and creates a gRPC server. - `/client` directory defines an http server and calls the gRPC server via its different handlers. @@ -33,7 +35,8 @@ Once you have selected your Code Engine project, you only need to run: ./run ``` -## Todo: -3. Extend initGroceryServer() content, in order to have more groceries. -4. Json Encoding on the client side, needs improvement -5. Error handling for queries validations, e.g. grocery not found, or category not found. \ No newline at end of file +If you want to clean-up resources from this sample app, do not forget to run: + +```sh +./run clean +``` \ No newline at end of file diff --git a/grpc/build b/grpc/build new file mode 100755 index 00000000..cdbc03ef --- /dev/null +++ b/grpc/build @@ -0,0 +1,21 @@ +#!/bin/bash + +set -euo pipefail + +# Env Vars: +# REGISTRY: name of the image registry/namespace to store the images +# +# NOTE: to run this you MUST set the REGISTRY environment variable to +# your own image registry/namespace otherwise the `docker push` commands +# will fail due to an auth failure. Which means, you also need to be logged +# into that registry before you run it. + +export REGISTRY=${REGISTRY:-icr.io/codeengine} + +# Build the images +docker build -f Dockerfile.client -t "${REGISTRY}"/grpc-client . --platform linux/amd64 +docker build -f Dockerfile.server -t "${REGISTRY}"/grpc-server . --platform linux/amd64 + +# And push it +docker push "${REGISTRY}"/grpc-client +docker push "${REGISTRY}"/grpc-server diff --git a/grpc/client/main.go b/grpc/client/main.go index 47389403..34a250ed 100644 --- a/grpc/client/main.go +++ b/grpc/client/main.go @@ -91,12 +91,7 @@ func BuyHandler(w http.ResponseWriter, r *http.Request, groceryClient ec.Grocery Item: item, } - paymentResponse, _ := groceryClient.MakePayment(context.Background(), &paymentRequest) - - // if !paymentResponse.Success { - // Fail(w, "failed to buy grocery, not enough money", errors.New("not enough money")) - // return - // } + paymentResponse, _ := groceryClient.BuyGrocery(context.Background(), &paymentRequest) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(paymentResponse) diff --git a/grpc/ecommerce/ecommerce.pb.go b/grpc/ecommerce/ecommerce.pb.go index 7ec492f9..e33e052e 100644 --- a/grpc/ecommerce/ecommerce.pb.go +++ b/grpc/ecommerce/ecommerce.pb.go @@ -343,22 +343,22 @@ var file_ecommerce_ecommerce_proto_rawDesc = []byte{ 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x32, - 0xc2, 0x01, 0x0a, 0x07, 0x67, 0x72, 0x6f, 0x63, 0x65, 0x72, 0x79, 0x12, 0x34, 0x0a, 0x0a, 0x47, + 0xc1, 0x01, 0x0a, 0x07, 0x67, 0x72, 0x6f, 0x63, 0x65, 0x72, 0x79, 0x12, 0x34, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x47, 0x72, 0x6f, 0x63, 0x65, 0x72, 0x79, 0x12, 0x13, 0x2e, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x1a, 0x0f, 0x2e, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x72, 0x63, 0x65, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x63, 0x65, 0x72, 0x79, 0x12, 0x13, 0x2e, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x1a, 0x13, 0x2e, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x72, 0x63, - 0x65, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0b, - 0x4d, 0x61, 0x6b, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x65, 0x63, - 0x6f, 0x6d, 0x6d, 0x65, 0x72, 0x63, 0x65, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x72, - 0x63, 0x65, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x71, 0x75, 0x31, 0x71, 0x75, 0x65, 0x65, 0x65, 0x2f, 0x43, 0x6f, 0x64, 0x65, - 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x63, 0x6f, 0x6d, - 0x6d, 0x65, 0x72, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x0a, + 0x42, 0x75, 0x79, 0x47, 0x72, 0x6f, 0x63, 0x65, 0x72, 0x79, 0x12, 0x19, 0x2e, 0x65, 0x63, 0x6f, + 0x6d, 0x6d, 0x65, 0x72, 0x63, 0x65, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x72, 0x63, + 0x65, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x71, 0x75, 0x31, 0x71, 0x75, 0x65, 0x65, 0x65, 0x2f, 0x43, 0x6f, 0x64, 0x65, 0x45, + 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x63, 0x6f, 0x6d, 0x6d, + 0x65, 0x72, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -387,10 +387,10 @@ var file_ecommerce_ecommerce_proto_depIdxs = []int32{ 2, // 2: ecommerce.PaymentResponse.purchasedItem:type_name -> ecommerce.Item 0, // 3: ecommerce.grocery.GetGrocery:input_type -> ecommerce.Category 0, // 4: ecommerce.grocery.ListGrocery:input_type -> ecommerce.Category - 3, // 5: ecommerce.grocery.MakePayment:input_type -> ecommerce.PaymentRequest + 3, // 5: ecommerce.grocery.BuyGrocery:input_type -> ecommerce.PaymentRequest 2, // 6: ecommerce.grocery.GetGrocery:output_type -> ecommerce.Item 1, // 7: ecommerce.grocery.ListGrocery:output_type -> ecommerce.ItemList - 4, // 8: ecommerce.grocery.MakePayment:output_type -> ecommerce.PaymentResponse + 4, // 8: ecommerce.grocery.BuyGrocery:output_type -> ecommerce.PaymentResponse 6, // [6:9] is the sub-list for method output_type 3, // [3:6] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name diff --git a/grpc/ecommerce/ecommerce.proto b/grpc/ecommerce/ecommerce.proto index 8ba2af72..6c518b41 100644 --- a/grpc/ecommerce/ecommerce.proto +++ b/grpc/ecommerce/ecommerce.proto @@ -6,7 +6,7 @@ option go_package="github.com/qu1queee/CodeEngine/grpc/ecommerce"; service grocery { rpc GetGrocery(Category) returns (Item) {}; rpc ListGrocery(Category) returns (ItemList) {}; - rpc MakePayment(PaymentRequest) returns (PaymentResponse) {}; + rpc BuyGrocery(PaymentRequest) returns (PaymentResponse) {}; } message Category{ diff --git a/grpc/ecommerce/ecommerce_grpc.pb.go b/grpc/ecommerce/ecommerce_grpc.pb.go index c52a2ad9..c2729e99 100644 --- a/grpc/ecommerce/ecommerce_grpc.pb.go +++ b/grpc/ecommerce/ecommerce_grpc.pb.go @@ -24,7 +24,7 @@ const _ = grpc.SupportPackageIsVersion7 type GroceryClient interface { GetGrocery(ctx context.Context, in *Category, opts ...grpc.CallOption) (*Item, error) ListGrocery(ctx context.Context, in *Category, opts ...grpc.CallOption) (*ItemList, error) - MakePayment(ctx context.Context, in *PaymentRequest, opts ...grpc.CallOption) (*PaymentResponse, error) + BuyGrocery(ctx context.Context, in *PaymentRequest, opts ...grpc.CallOption) (*PaymentResponse, error) } type groceryClient struct { @@ -53,9 +53,9 @@ func (c *groceryClient) ListGrocery(ctx context.Context, in *Category, opts ...g return out, nil } -func (c *groceryClient) MakePayment(ctx context.Context, in *PaymentRequest, opts ...grpc.CallOption) (*PaymentResponse, error) { +func (c *groceryClient) BuyGrocery(ctx context.Context, in *PaymentRequest, opts ...grpc.CallOption) (*PaymentResponse, error) { out := new(PaymentResponse) - err := c.cc.Invoke(ctx, "/ecommerce.grocery/MakePayment", in, out, opts...) + err := c.cc.Invoke(ctx, "/ecommerce.grocery/BuyGrocery", in, out, opts...) if err != nil { return nil, err } @@ -68,7 +68,7 @@ func (c *groceryClient) MakePayment(ctx context.Context, in *PaymentRequest, opt type GroceryServer interface { GetGrocery(context.Context, *Category) (*Item, error) ListGrocery(context.Context, *Category) (*ItemList, error) - MakePayment(context.Context, *PaymentRequest) (*PaymentResponse, error) + BuyGrocery(context.Context, *PaymentRequest) (*PaymentResponse, error) mustEmbedUnimplementedGroceryServer() } @@ -82,8 +82,8 @@ func (UnimplementedGroceryServer) GetGrocery(context.Context, *Category) (*Item, func (UnimplementedGroceryServer) ListGrocery(context.Context, *Category) (*ItemList, error) { return nil, status.Errorf(codes.Unimplemented, "method ListGrocery not implemented") } -func (UnimplementedGroceryServer) MakePayment(context.Context, *PaymentRequest) (*PaymentResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method MakePayment not implemented") +func (UnimplementedGroceryServer) BuyGrocery(context.Context, *PaymentRequest) (*PaymentResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BuyGrocery not implemented") } func (UnimplementedGroceryServer) mustEmbedUnimplementedGroceryServer() {} @@ -134,20 +134,20 @@ func _Grocery_ListGrocery_Handler(srv interface{}, ctx context.Context, dec func return interceptor(ctx, in, info, handler) } -func _Grocery_MakePayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { +func _Grocery_BuyGrocery_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(PaymentRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(GroceryServer).MakePayment(ctx, in) + return srv.(GroceryServer).BuyGrocery(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/ecommerce.grocery/MakePayment", + FullMethod: "/ecommerce.grocery/BuyGrocery", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(GroceryServer).MakePayment(ctx, req.(*PaymentRequest)) + return srv.(GroceryServer).BuyGrocery(ctx, req.(*PaymentRequest)) } return interceptor(ctx, in, info, handler) } @@ -168,8 +168,8 @@ var Grocery_ServiceDesc = grpc.ServiceDesc{ Handler: _Grocery_ListGrocery_Handler, }, { - MethodName: "MakePayment", - Handler: _Grocery_MakePayment_Handler, + MethodName: "BuyGrocery", + Handler: _Grocery_BuyGrocery_Handler, }, }, Streams: []grpc.StreamDesc{}, diff --git a/grpc/images/grpc-architecture.png b/grpc/images/grpc-architecture.png new file mode 100644 index 00000000..f9885780 Binary files /dev/null and b/grpc/images/grpc-architecture.png differ diff --git a/grpc/regenerate-grpc-code.sh b/grpc/regenerate-grpc-code.sh index 8661e72f..fb3ade58 100755 --- a/grpc/regenerate-grpc-code.sh +++ b/grpc/regenerate-grpc-code.sh @@ -6,14 +6,14 @@ BASEDIR="$(dirname "$0")" if ! hash protoc >/dev/null 2>&1; then echo "[ERROR] protoc compiler plugin is not installed, bailing out." - echo "[INFO] refer to https://grpc.io/docs/languages/go/quickstart/#prerequisites for instalation guidelines." + echo "[INFO] refer to https://grpc.io/docs/languages/go/quickstart/#prerequisites for installation guidelines." echo exit 1 fi if ! hash protoc-gen-go >/dev/null 2>&1; then echo "[ERROR] protoc-gen-go plugin is not installed, bailing out." - echo "[INFO] refer to https://grpc.io/docs/languages/go/quickstart/#prerequisites for instalation guidelines." + echo "[INFO] refer to https://grpc.io/docs/languages/go/quickstart/#prerequisites for installation guidelines." echo "[INFO] ensure your GOPATH is in your PATH". exit 1 fi diff --git a/grpc/run b/grpc/run index 6b7813fc..ab8082b8 100755 --- a/grpc/run +++ b/grpc/run @@ -1,6 +1,6 @@ #!/bin/bash -set -euo pipefail +set -exuo pipefail SERVER_APP_NAME="a-grpc-server" CLIENT_APP_NAME="a-grpc-client" @@ -37,18 +37,18 @@ echo "[INFO] Retrieving client/server public endpoint" URL=$(ibmcloud ce app get -n "${CLIENT_APP_NAME}" -o url) echo "[INFO] Endpoint is: ${URL}" -# Query the list of groceries by electronics category -echo "[INFO] Retrieving available electronic items" -curl -q "${URL}"/listgroceries/electronics +# Query the list of groceries by food category +echo "[INFO] Retrieving available food items" +curl -q "${URL}"/listgroceries/food # Buy an item from food and pay with 5.0 dollars echo "[INFO] Going to buy an apple, paying with 5.0 dollars" -JSON_REPONSE=$(curl -s "${URL}"/buygrocery/vegetables/apple/5.0 | jq '.success') -echo $JSON_REPONSE | jq . +JSON_RESPONSE=$(curl -s "${URL}"/buygrocery/food/apple/5.0 | jq '.success') +echo "${JSON_RESPONSE}" | jq . # Validate payment operation echo "[INFO] Validating payment operation state" -OPERATION_SUCCEEDED=$(echo $JSON_REPONSE | jq '.success') +OPERATION_SUCCEEDED=$(echo "${JSON_RESPONSE}" | jq '.success') if [ "${OPERATION_SUCCEEDED}" == "null" ] then echo "[ERROR] Payment failed, bailing out." diff --git a/grpc/server/main.go b/grpc/server/main.go index 472328cf..adbe34a0 100644 --- a/grpc/server/main.go +++ b/grpc/server/main.go @@ -124,7 +124,7 @@ func (gs *GroceryServer) ListGrocery(ctx context.Context, in *ec.Category) (*ec. return &itemList, errors.New("category not found") } -func (gs *GroceryServer) MakePayment(ctx context.Context, in *ec.PaymentRequest) (*ec.PaymentResponse, error) { +func (gs *GroceryServer) BuyGrocery(ctx context.Context, in *ec.PaymentRequest) (*ec.PaymentResponse, error) { amount := in.GetAmount() purchasedItem := in.GetItem()