Skip to content

Commit

Permalink
Add grpc samples
Browse files Browse the repository at this point in the history
Introduce grpc directory, containing a microservice architecture
consisting of an internal grpc server, and a public client/server app.

A run.sh script is provided, to easily deploy this architecture in
Code Engine, as well as a README, explaining the ideas behind.

Signed-off-by: Mahesh Kumawat <[email protected]>
  • Loading branch information
qu1queee authored and MaheshRKumawat committed Dec 4, 2023
1 parent 5ffd849 commit adfab6d
Show file tree
Hide file tree
Showing 11 changed files with 1,099 additions and 0 deletions.
21 changes: 21 additions & 0 deletions grpc/Dockerfile.client
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM golang:latest AS stage

WORKDIR /app/src

COPY client/ ./client/

COPY ecommerce/ ./ecommerce/

COPY go.mod .

COPY go.sum .

RUN CGO_ENABLED=0 GOOS=linux go build -o client ./client

FROM golang:latest

WORKDIR /app/src

COPY --from=stage /app/src/client/client .

CMD [ "./client" ]
21 changes: 21 additions & 0 deletions grpc/Dockerfile.server
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM golang:latest AS stage

WORKDIR /app/src

COPY server/ ./server/

COPY ecommerce/ ./ecommerce/

COPY go.mod .

COPY go.sum .

RUN CGO_ENABLED=0 GOOS=linux go build -o server ./server

FROM golang:latest

WORKDIR /app/src

COPY --from=stage /app/src/server/server .

CMD [ "./server" ]
39 changes: 39 additions & 0 deletions grpc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# gRPC Application

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.

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:


## Source code

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.
- `/client` directory defines an http server and calls the gRPC server via its different
handlers.

## Try it!

You can try to deploy this microservice architecture in your Code Engine project.
Once you have selected your Code Engine project, you only need to run:

```sh
./run.sh
```

## 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.
121 changes: 121 additions & 0 deletions grpc/client/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package main

import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strconv"
"time"

"github.com/gorilla/mux"
ec "github.com/qu1queee/CodeEngine/grpc/ecommerce"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UFT-8")
json.NewEncoder(w).Encode("server is running")
}

func GetGroceryHandler(groceryClient ec.GroceryClient) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
GetHandler(w, r, groceryClient)
}
}

func BuyGroceryHandler(groceryClient ec.GroceryClient) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
BuyHandler(w, r, groceryClient)
}
}

func GetHandler(w http.ResponseWriter, r *http.Request, groceryClient ec.GroceryClient) {

// Contact the server and print out its response.
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*1)
defer cancel()

vars := mux.Vars(r)

categoryName := vars["category"]

category := ec.Category{
Category: categoryName,
}

itemList, err := groceryClient.ListGrocery(ctx, &category)
if err != nil {
fmt.Printf("failed to list grocery: %v", err)
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("failed to list grocery"))
return
}

for _, item := range itemList.Item {
json.NewEncoder(w).Encode(fmt.Sprintf("Item Name: %v\n", item.Name))
}

w.WriteHeader(http.StatusOK)
}

func BuyHandler(w http.ResponseWriter, r *http.Request, groceryClient ec.GroceryClient) {
vars := mux.Vars(r)

itemName := vars["name"]
pAmount := vars["amount"]
categoryName := vars["category"]

amount, err := strconv.ParseFloat(pAmount, 64)
if err != nil {
http.Error(w, "invalid amount parameter", http.StatusBadRequest)
}

category := ec.Category{
Category: categoryName,
Itemname: itemName,
}

item, err := groceryClient.GetGrocery(context.Background(), &category)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
}

json.NewEncoder(w).Encode(fmt.Sprintf("Grocery: \n Item: %v \n Quantity: %v\n", item.GetName(), item.GetQuantity()))

paymentRequest := ec.PaymentRequest{
Amount: amount,
Item: item,
}

paymentResponse, _ := groceryClient.MakePayment(context.Background(), &paymentRequest)
json.NewEncoder(w).Encode(fmt.Sprintf("Payment Response: \n Purchase: %v \n Details: %v \n State: %v \n Change: %v\n", paymentResponse.PurchasedItem, paymentResponse.Details, paymentResponse.Success, paymentResponse.Change))
w.WriteHeader(http.StatusOK)
}

func main() {

serverLocalEndpoint := "LOCAL_ENDPOINT_WITH_PORT"

localEndpoint := os.Getenv(serverLocalEndpoint)

fmt.Printf("using local endpoint: %s\n", localEndpoint)
conn, err := grpc.Dial(localEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("failed to connect: %v", err)
}

defer conn.Close()

c := ec.NewGroceryClient(conn)

r := mux.NewRouter()
r.HandleFunc("/", indexHandler).Methods("GET")
r.HandleFunc("/listgroceries/{category}", GetGroceryHandler(c))
r.HandleFunc("/buygrocery/{category}/{name}/{amount:[0-9]+\\.[0-9]+}", BuyGroceryHandler(c))
fmt.Println("server app is running on :8080 .....")
http.ListenAndServe(":8080", r)
}
Loading

0 comments on commit adfab6d

Please sign in to comment.