gRPC in Go - Part 2: Building the Server
Andre
Andre
In Part 1, we set up our project and defined our Protocol Buffers. Now, let's implement the server side of our e-commerce application. We'll create a fully functional gRPC server that handles product-related operations.
Implementing the Product Service
First, let's create a struct that implements our ProductServiceServer
interface. Create a new file called server/product_service.go
:
package server
import (
"context"
"fmt"
pb "github.com/andredeloliveira/ecommerce-grpc/ecommerce-proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type ProductService struct {
pb.UnimplementedProductServiceServer
products map[string]*pb.Product // Simple in-memory storage
}
func NewProductService() *ProductService {
// Initialize with some sample data
products := map[string]*pb.Product{
"1": {
Id: "1",
Name: "Gaming Laptop",
Description: "High-performance gaming laptop",
Price: 1299.99,
Stock: 10,
},
"2": {
Id: "2",
Name: "Wireless Mouse",
Description: "Ergonomic wireless mouse",
Price: 49.99,
Stock: 50,
},
}
return &ProductService{products: products}
}
Implementing Service Methods
Now, let's implement the required methods:
func (s *ProductService) GetProduct(ctx context.Context, req *pb.ProductID) (*pb.Product, error) {
// Add basic validation
if req.Id == "" {
return nil, status.Error(codes.InvalidArgument, "product ID cannot be empty")
}
// Look up the product
product, exists := s.products[req.Id]
if !exists {
return nil, status.Errorf(codes.NotFound, "product with ID %s not found", req.Id)
}
return product, nil
}
func (s *ProductService) ListProducts(ctx context.Context, req *pb.ProductListRequest) (*pb.ProductList, error) {
// Add basic validation
if req.Page < 1 {
return nil, status.Error(codes.InvalidArgument, "page must be greater than 0")
}
if req.Limit < 1 {
return nil, status.Error(codes.InvalidArgument, "limit must be greater than 0")
}
// Convert map to slice for pagination
products := make([]*pb.Product, 0, len(s.products))
for _, product := range s.products {
products = append(products, product)
}
// Calculate pagination
startIndex := (int(req.Page) - 1) * int(req.Limit)
endIndex := startIndex + int(req.Limit)
if startIndex >= len(products) {
return &pb.ProductList{Products: []*pb.Product{}, Total: int32(len(products))}, nil
}
if endIndex > len(products) {
endIndex = len(products)
}
return &pb.ProductList{
Products: products[startIndex:endIndex],
Total: int32(len(products)),
}, nil
}
Setting Up the gRPC Server
Create a new file called main.go
to set up and run the server:
package main
import (
"log"
"net"
pb "github.com/andredeloliveira/ecommerce-grpc/ecommerce-proto"
"github.com/andredeloliveira/ecommerce-grpc/server"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// Create a new gRPC server
s := grpc.NewServer()
// Register our service
productService := server.NewProductService()
pb.RegisterProductServiceServer(s, productService)
// Register reflection service on gRPC server
reflection.Register(s)
log.Printf("Server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
Adding Middleware for Logging
Let's add some middleware to log incoming requests:
func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
// Execute the handler
h, err := handler(ctx, req)
// Log the request
log.Printf(
"Request - Method:%s\tDuration:%s\tError:%v",
info.FullMethod,
time.Since(start),
err,
)
return h, err
}
// Update the main function to use the interceptor
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// Create a new gRPC server with the logging interceptor
s := grpc.NewServer(
grpc.UnaryInterceptor(LoggingInterceptor),
)
// ... rest of the main function
}
Testing the Server
You can test your server using tools like grpcurl
. First, install it:
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
Then test your endpoints:
# List all available services
grpcurl -plaintext localhost:50051 list
# Get a specific product
grpcurl -plaintext -d '{"id": "1"}' localhost:50051 ecommerce.ProductService/GetProduct
# List products
grpcurl -plaintext -d '{"page": 1, "limit": 10}' localhost:50051 ecommerce.ProductService/ListProducts
Next Steps
In Part 3, we'll build the client side of our application, including:
- Creating a gRPC client
- Implementing retry logic
- Handling timeouts and cancellation
- Best practices for client-side implementation
Stay tuned for the final part of our series!