25% off

Use code FUNCMAIN at checkout for 25% off all premium courses.

Get started →

Implementing our gRPC Server

May 22, 2021
Elliot Forbes

Elliot Forbes

Course Instructor

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.

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