In this repo we are demonstrating on how to use the Grpc or GraphQL communication between application using the gogen framework that also apply the Clean Architecture
For the Gogen Framework Structure, you can refer to here link
The application consist of two parts
- Client : Has a restapi interface to invoke the gRPC or GraphQL client
- Server : Has a gRPC or GraphQL server to receive the request, process it and then return back to client (gRPC or GraphQL)
gogen_grpc_graphql
├── application
│ ├── app_client.go
│ └── app_server.go
├── domain_demo
│ ├── controller
│ │ ├── graphqlserver
│ │ ├── grpcserver
│ │ └── restapi
│ ├── gateway
│ │ ├── emptyimpl
│ │ ├── graphqlclient
│ │ └── grpcclient
│ └── usecase
│ ├── runmessagereverse
│ └── runmessagesend
├── main.go
└── shared
└── pb
├── grpcstub
│ ├── message.pb.go
│ └── message_grpc.pb.go
└── message.proto
-
After you git clone it, make sure to run the
go mod tidy
to download the dependency -
Run the server application by
go run main.go server
-
Run the client application by
go run main.go client
-
invoke this api with curl, postman or use the file
http_runmessagesend.http
underdomain_demogrpc/controller/restapi
POST http://localhost:8000/api/v1/runmessagesend { "message": "hello" }
Then you will get the message reversed in response payload
{ "success": true, "errorCode": "", "errorMessage": "", "data": { "return_message": "olleh" }, "traceId": "Z1RCGNXYTR2QCNVK" }
For the server you may comment / uncomment this part (application/app_server.go
)
//primaryDriver := grpcserver.NewController(log, cfg)
primaryDriver := graphqlserver.NewController(log, cfg)
For the client you may comment / uncomment this part (application/app_client.go
)
//datasource := grpcclient.NewGateway(log, appData, cfg)
datasource := graphqlclient.NewGateway(log, appData, cfg)
This is the proto file shared/pb/message.proto
used in this project
syntax = "proto3";
package mypackage;
option go_package = "./grpcstub";
message MessageReverseRequest {
string content = 1;
}
message MessageReverseResponse {
string content = 1;
}
service MyService {
rpc SendMessage(MessageReverseRequest) returns (MessageReverseResponse);
}
For GRPC code generation you need to do
$ cd shared/pb
$ protoc --go_out=. --go-grpc_out=. message.proto
Make sure you already have the protoc executable first.
type controller struct {
grpcstub.UnimplementedMyServiceServer
gogen.UsecaseRegisterer
server *grpc.Server
log logger.Logger
cfg *config.Config
}
func NewController(log logger.Logger, cfg *config.Config) gogen.ControllerRegisterer {
server := grpc.NewServer()
return &controller{
UsecaseRegisterer: gogen.NewBaseController(),
server: server,
log: log,
cfg: cfg,
}
}
func (r *controller) Start() {
listen, err := net.Listen("tcp", ":50051")
if err != nil {
panic(err)
}
err = r.server.Serve(listen)
if err != nil {
panic(err)
}
}
func (r *controller) RegisterRouter() {
grpcstub.RegisterMyServiceServer(r.server, r)
}
func (r *controller) SendMessage(ctx context.Context, stubReq *grpcstub.MessageReverseRequest) (*grpcstub.MessageReverseResponse, error) {
return &grpcstub.MessageReverseResponse{
Content: "...",
}, nil
}
type gateway struct {
appData gogen.ApplicationData
config *config.Config
log logger.Logger
client grpcstub.MyServiceClient
}
// NewGateway ...
func NewGateway(log logger.Logger, appData gogen.ApplicationData, cfg *config.Config) *gateway {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
panic(err)
}
//defer conn.Close()
client := grpcstub.NewMyServiceClient(conn)
return &gateway{
log: log,
appData: appData,
config: cfg,
client: client,
}
}
func (r *gateway) SendMessage(ctx context.Context, message string) (string, error) {
r.log.Info(ctx, "called")
response, err := r.client.SendMessage(context.Background(), &grpcstub.MessageReverseRequest{
Content: message,
})
if err != nil {
return "", err
}
return response.Content, nil
}
type controller struct {
gogen.UsecaseRegisterer // collect all the inports
Router *gin.Engine // the router from preference web framework
log logger.Logger
cfg *config.Config
fields graphql.Fields
}
func NewController(log logger.Logger, cfg *config.Config) gogen.ControllerRegisterer {
return &controller{
UsecaseRegisterer: gogen.NewBaseController(),
log: log,
cfg: cfg,
fields: map[string]*graphql.Field{},
}
}
func (r *controller) RegisterRouter() {
r.fields["reverseMessage"] = r.sendMessageHandler()
}
func (r *controller) Start() {
rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: r.fields}
schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
schema, err := graphql.NewSchema(schemaConfig)
if err != nil {
fmt.Println("Error creating schema: ", err)
return
}
// Create a new GraphQL HTTP handler with the schema
graphqlHandler := handler.New(&handler.Config{
Schema: &schema,
Pretty: true,
})
// Serve the GraphQL endpoint
http.Handle("/graphql", graphqlHandler)
fmt.Println("GraphQL Server running on http://localhost:8080/graphql")
http.ListenAndServe(":8080", nil)
}
func (r *controller) sendMessageHandler() *graphql.Field {
return &graphql.Field{
Type: graphql.String,
Description: "Reverses a given message",
Args: graphql.FieldConfigArgument{
"message": &graphql.ArgumentConfig{
Type: graphql.String,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
type InportRequest = runmessagereverse.InportRequest
type InportResponse = runmessagereverse.InportResponse
inport := gogen.GetInport[InportRequest, InportResponse](r.GetUsecase(InportRequest{}))
traceID := util.GenerateID(16)
ctx := logger.SetTraceID(context.Background(), traceID)
var req InportRequest
message, ok := p.Args["message"].(string)
if !ok {
return nil, fmt.Errorf("Invalid message type")
}
req.Message = message
res, err := inport.Execute(ctx, req)
if err != nil {
return nil, err
}
return res.ReturnMessage, nil
},
}
}
type GraphQLRequest struct {
Query string `json:"query"`
Variables map[string]interface{} `json:"variables"`
}
type GraphQLResponse struct {
Data interface{} `json:"data"`
Errors []struct {
Message string `json:"message"`
} `json:"errors"`
}
func (r *gateway) SendMessage(ctx context.Context, message string) (string, error) {
r.log.Info(ctx, "called in GraphQL Gateway")
// Define the GraphQL query
query := `
query ReverseMessage($message: String!) {
reverseMessage(message: $message)
}
`
// Define the query variables
variables := map[string]interface{}{
"message": message,
}
// Create a GraphQL request
request := GraphQLRequest{
Query: query,
Variables: variables,
}
// Convert the request to JSON
requestJSON, err := json.Marshal(request)
if err != nil {
return "", err
}
// Send a POST request to the GraphQL server
resp, err := http.Post("http://localhost:8080/graphql", "application/json", bytes.NewBuffer(requestJSON))
if err != nil {
return "", err
}
defer resp.Body.Close()
// Read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
// Parse the GraphQL response
var response GraphQLResponse
err = json.Unmarshal(body, &response)
if err != nil {
return "", err
}
// Check for errors in the response
if len(response.Errors) > 0 {
errs := ""
for _, err := range response.Errors {
errs += err.Message + ", "
}
return "", fmt.Errorf(errs)
}
// Extract the reversed message from the response
reversedMessage := response.Data.(map[string]interface{})["reverseMessage"].(string)
return reversedMessage, nil
}