Hello, After so long time I'm not writing again (last time write on medium), finally I do gather the intention to make an article again. today I want to share how do I creating my grpc services from stracth.
for the context I want to create continues article, I will try to build complex system application that will mock like real world problem. but lets see later if I can complete that.
for now lets do focus on how to creating the golang grpc services, I will create really simple application and how to test it.
Install the prerequisites tools
you need to install protoc first, I used linux so to install that need to run
sudo apt install -y protobuf-compiler
how if I use macos? run this, but I'm not sure if its work because I dont have macos
brew install protobuf
if already installed, please verify by run
protoc --version
it will show the version of protoc, for me I have protoc version libprotoc 3.6.1
.
after do that, install the requirement plugin protoc for golang
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
and make sure bin for protoc-gen-go
and protoc-gen-go-grpc
already on your path.
Path Structure
so this is my structure my project, btw this path structure will look like complicated, because I want to make continues app later in this project, so just bear with it
little breakdawn what inside the folder.
agent
-> it will the base applications. basically I want to make app using state-machine pattern
agent/client
-> it will hold the client type and the handler for GRPC
proto
-> this will based proto files for our grpc. the files *.pb.go
and *_grpc.pb.go
will generated automaticall, we just need to write .proto
files for service definition of rpc
Make your syntax proto
Lets make your syntax proto
proto/agent.proto
syntax = "proto3";
option go_package = "github.com/jufianto/state-agent/agentsrv-proto";
enum TaskStatus{
UNKNOWN = 0;
PROCESSING = 1;
FAILED = 2;
SUCCESS = 3;
}
message TaskRequest{
string task_id = 1;
string task_name = 2;
string task_url = 3;
}
message TaskResponse{
string task_id = 1;
string task_result = 2;
TaskStatus task_status = 3;
}
message TaskListResponse{
repeated TaskResponse tasks = 1;
}
message TaskListRequest{
repeated string tasks_id = 1;
}
message TaskNotify{
string task_id = 1;
TaskStatus task_status = 2;
}
message TaskStatusRequest {
string task_id = 1;
}
message TasksStatusResponse {
string task_id = 1;
TaskStatus task_status = 2;
}
service TaskService{
rpc CreateTask(TaskRequest) returns (TaskResponse) {}
rpc ListTask(TaskListRequest) returns (TaskListResponse) {}
rpc StatusTask(TaskStatusRequest) returns (TasksStatusResponse){}
}
what happen on that code?
-
option go_package
will be result as your package grpc after.go
files generated -
enum
is the type enum ofTaskStatus
will bemessage
will be like thetype struct
on golang -
string task_id = 1;
is explain the type data oftask_id
is string and the number should be consistent on the field. the number is need to identify the binary serialize later. so if you want later to add new field, you must not to change the the number on the field, just add it. it can break the backward compability when deserialize later. -
service TaskService
is the defined of service grpc
after you coded that, lets go to folder proto
and execute this command to generate the grpc code go
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
*.proto
and you will see the result on proto
folder have 2 files
agent.pb.go
-> the Go struct definitions for messages proto, It also includes methods to serialize/deserialize (marshal/unmarshal) the messages to/from binary or JSON formats.
agent_grpc.ob.go
. -> contains the grpc specific code, like the server / client definitions and also list of method / services grpc
Lets do the real code
after generated the grpc code, create files client.go
on folder client and fill with this code.
client.go
package client
import (
agentsrv_proto "github.com/jufianto/state-agent/proto"
"google.golang.org/grpc"
)
type AgentService struct {
Key string
agentsrv_proto.UnimplementedTaskServiceServer
}
type TaskResult struct {
TaskID string
TaskResult string
TaskStatus string
}
func NewAgentClient(key string) *AgentService {
return &AgentService{
Key: key,
}
}
func (a *AgentService) RegisterGW(srvGrpc *grpc.Server) {
agentsrv_proto.RegisterTaskServiceServer(srvGrpc, a)
}
and also code the handler.go
handler.go
func (a *AgentService) CreateTask(ctx context.Context, req *agentsrv_proto.TaskRequest) (*agentsrv_proto.TaskResponse, error) {
fmt.Printf("got request %+v \n", req)
return &agentsrv_proto.TaskResponse{
TaskId: req.TaskId,
TaskResult: "waiting for processing",
TaskStatus: agentsrv_proto.TaskStatus_PROCESSING,
}, nil
}
func (a *AgentService) ListTask(ctx context.Context, req *agentsrv_proto.TaskListRequest) (*agentsrv_proto.TaskListResponse, error) {
log.Println("list tasks", req.GetTasksId())
return nil, fmt.Errorf("not yet implemented")
}
func (a *AgentService) StatusTask(ctx context.Context, req *agentsrv_proto.TaskStatusRequest) (*agentsrv_proto.TasksStatusResponse, error) {
log.Println("status tasks", req.GetTaskId())
return nil, fmt.Errorf("not yet implemented")
}
what happen on that code?
so, client.go it the base client for our primary services, it will hold the agentsrv_proto.UnimplementedTaskServiceServer
interface to make sure the handler will implemented the services grpc.
the real thing gRPC is coming from this code agentsrv_proto.RegisterTaskServiceServer(srvGrpc, a)
it will make the grpc server registered on this handler and can be used later as grpc services.
and for the handler.go
it will impelented from grpc generated code from the rpc we defined on protofiles. on the proto files, you can see you have rpc services look like this
rpc CreateTask(TaskRequest) returns (TaskResponse) {}
rpc ListTask(TaskListRequest) returns (TaskListResponse) {}
rpc StatusTask(TaskStatusRequest) returns (TasksStatusResponse){}
and all of this method grpc need to be implemented as you can see from the handler.go
Finally, lets write the main.go
main.go
package main
import (
"fmt"
"log"
"net"
"github.com/jufianto/state-agent/agent/client"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
func main() {
srvGrpc := grpc.NewServer()
srv := client.NewAgentClient("key123")
srv.RegisterGW(srvGrpc)
fmt.Println("service listening....")
net, err := net.Listen("tcp", ":3333")
if err != nil {
log.Fatalf("failed to register grpc: %v", err)
}
reflection.Register(srvGrpc)
srvGrpc.Serve(net)
}
srvGrpc := grpc.NewServer()
will be init the grpc server, and srv.RegisterGW
will be registered the handler to server grpc and finally do serve on srvGrpc.Serve(net)
with port 3333
reflection.Register(srvGrpc)
it just for helper to check what all the services on grpc services.
Run your services
to run your services, please do run
go run agent/main.go
and it will looks like
and you need to test it by postman
you can see from the list down on postman, there have option list Create Task, ListTask
, thats the use of reflection package. it will help us to check what all the method on the gRPC services.
FAQ
Q: usually on Rest API services, I see there have routing that help route the services based on the path we called. but Why I dont see on here?
A: that's the magic of grpc, basically its not based on routing but based on the method implemented. the method handler it should be same with the method we defined on the grpc generated code or can be simplified based on the proto rpc services defined.
The Github Repo for this Projects
[!NOTE]
if you have anything to ask please reach me on twitter and if you confuse with my bad writing you also can comment
and all of this explanation based my experience, so feel free to correct me if I share the miss information
twitter: https://x.com/jufiantohenri
linkedin: https://www.linkedin.com/in/jufianto/
Top comments (0)