Skip to content

Commit

Permalink
Merge pull request #65 from qu1queee/qu1queee/grpc
Browse files Browse the repository at this point in the history
Add grpc samples
  • Loading branch information
reggeenr authored Dec 6, 2023
2 parents 5ffd849 + 6add6f4 commit e41e889
Show file tree
Hide file tree
Showing 14 changed files with 1,216 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 icr.io/codeengine/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 icr.io/codeengine/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 icr.io/codeengine/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 icr.io/codeengine/golang:latest

WORKDIR /app/src

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

CMD [ "./server" ]
42 changes: 42 additions & 0 deletions grpc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 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 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 architecture diagram:

![Alt text](images/grpc-architecture.png)

## 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. 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.

## 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
```

If you want to clean-up resources from this sample app, do not forget to run:

```sh
./run clean
```
21 changes: 21 additions & 0 deletions grpc/build
Original file line number Diff line number Diff line change
@@ -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
119 changes: 119 additions & 0 deletions grpc/client/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
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 Fail(w http.ResponseWriter, msg string, err error) {
fmt.Printf("%s: %v\n", msg, err)
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(fmt.Sprintf("%s\n", msg)))
}

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 {
Fail(w, "failed to list groceries", err)
return
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(itemList.Item)
}

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 {
Fail(w, "invalid amount parameter", err)
}

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

item, err := groceryClient.GetGrocery(context.Background(), &category)
if err != nil {
Fail(w, fmt.Sprintf("failed to get grocery item by name: %v", category.Itemname), err)
return
}

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

paymentResponse, _ := groceryClient.BuyGrocery(context.Background(), &paymentRequest)

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(paymentResponse)
}

func main() {
localEndpoint := os.Getenv("LOCAL_ENDPOINT_WITH_PORT")

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 e41e889

Please sign in to comment.