Video:

Implementing our gRPC Server

May 22, 2021

Course Instructor: Elliot Forbes

Hey Gophers! My name is Elliot and I'm the creator of TutorialEdge and I've been working with Go systems for roughly 5 years now.

Twitter: @Elliot_f

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