DEV Community

Jufianto Henri
Jufianto Henri

Posted on

Init your golang gRPC Services

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  
Enter fullscreen mode Exit fullscreen mode

how if I use macos? run this, but I'm not sure if its work because I dont have macos

brew install protobuf
Enter fullscreen mode Exit fullscreen mode

if already installed, please verify by run

protoc --version
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Tree Folder

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){}
}
Enter fullscreen mode Exit fullscreen mode

what happen on that code?

  • option go_package will be result as your package grpc after .go files generated
  • enum is the type enum of TaskStatus will be message will be like the type struct on golang
  • string task_id = 1; is explain the type data of task_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

Enter fullscreen mode Exit fullscreen mode

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)
}


Enter fullscreen mode Exit fullscreen mode

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")
}

Enter fullscreen mode Exit fullscreen mode

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){}
Enter fullscreen mode Exit fullscreen mode

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)

}


Enter fullscreen mode Exit fullscreen mode

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

Running Services

and you need to test it by postman

Test 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)