Now that we have a basic appreciation for why we should use gRPC for our app to app communications, let’s now take a look at how we can set about implementing our gRPC server.
Implementing our gRPC Server
Let’s create our transport/grpc
package that is going to house the implementation of our gRPC endpoints as well as the logic to listen and serve on a given port:
package grpc
import (
"log"
"net"
"google.golang.org/grpc"
)
// RocketService - defines the rocket service interface
// which any service passed in to our Handler will need to
// conform to
type RocketService interface{}
// Handler -
type Handler struct {
RocketService RocketService
}
// New - returns a new gRPC handler
func New(rktService RocketService) Handler {
return Handler{
RocketService: rktService,
}
}
// Serve - starts out gRPC listeners
func (h Handler) Serve() error {
lis, err := net.Listen("tcp", ":9000")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %s", err)
return err
}
return nil
}
Updating our App Setup Code
package main
import (
"log"
"github.com/TutorialEdge/go-grpc-services-course/internal/db"
"github.com/TutorialEdge/go-grpc-services-course/internal/rocket"
"github.com/TutorialEdge/go-grpc-services-course/internal/transport/grpc"
)
// Run - handles the setup and starting of our application
// using this approach makes testing easier and we can more
// gracefully handle errors
func Run() error {
log.Println("Starting up Rocket gRPC Service")
// rktStore - the store responsible for holding
// our rocket inventory
rktStore, err := db.New()
if err != nil {
return err
}
// trigger our migration so that the database we are connecting
// to has the latest database schema changes
if err := rktStore.Migrate(); err != nil {
return err
}
// rktService the service responsible for updating our
// rocket inventory
rktService := rocket.New(rktStore)
// rktHandler instantiates a new gRPC handler
// which we pass our rktService into
rktHandler := grpc.New(rktService)
// Start our gRPC listener, this is a blocking
// function call so it should be the last thing
// we run in this function
if err := rktHandler.Serve(); err != nil {
return err
}
return nil
}
func main() {
// our main function is super small, only responsible
// for calling Run and then handling the error
if err := Run(); err != nil {
log.Fatal(err.Error())
}
}
Implementing the Stub Methods
Now that we have the Serve function up and running, let’s have a look at implementing the stub gRPC handler functions for our rocket microservice:
func (h Handler) GetRocket(ctx context.Context, req *rkt.GetRocketRequest) (*rkt.GetRocketResponse, error) {
return &rkt.GetRocketResponse{}, nil
}
func (h Handler) AddRocket(ctx context.Context, req *rkt.AddRocketRequest) (*rkt.AddRocketResponse, error) {
return &rkt.AddRocketResponse{}, nil
}
func (h Handler) DeleteRocket(ctx context.Context, req *rkt.DeleteRocketRequest) (*rkt.DeleteRocketResponse, error) {
return &rkt.DeleteRocketResponse{}, nil
}
With these in place, let’s attempt to run our application now using:
$ go mod vendor
$ docker-compose up --build
Our gRPC microservice should be up and running and ready to accept requests!
Conclusion
Awesome, we now have a fully functioning gRPC server that listens on port 9000 for incoming requests. In the next video, we’ll be looking at how we can setup our Protobuf monorepo so that