After designing your gRPC service with Goa’s DSL, it’s time to bring it to life! This guide will walk you through implementing your service step by step. You’ll learn how to:
First, let’s generate all the necessary gRPC code. From your project root (e.g., grpcgreeter/
), run:
goa gen grpcgreeter/design
go mod tidy
This command analyzes your gRPC design (greeter.go
) and generates the required code in the gen/
directory. Here’s what gets created:
gen/
├── grpc/
│ └── greeter/
│ ├── pb/ # Protocol Buffers definitions
│ ├── server/ # Server-side gRPC code
│ └── client/ # Client-side gRPC code
└── greeter/ # Service interfaces and types
goa gen
whenever you modify your design to keep the generated code in sync with your service definition.Let’s explore what Goa generated for us:
greeter.proto
: The Protocol Buffers service definitionservice Greeter {
rpc SayHello (SayHelloRequest) returns (SayHelloResponse);
}
greeter.pb.go
: The compiled Go code from the .proto
fileserver.go
: Maps your service methods to gRPC handlersencode_decode.go
: Converts between your service types and gRPC messagestypes.go
: Contains server-specific type definitionsclient.go
: gRPC client implementationencode_decode.go
: Client-side serialization logictypes.go
: Client-specific type definitionsNow for the fun part - implementing your service logic! Create a new file called greeter.go
in your service package:
package greeter
import (
"context"
"fmt"
// Use a descriptive alias for the generated package
gengreeter "grpcgreeter/gen/greeter"
)
// GreeterService implements the Service interface
type GreeterService struct{}
// NewGreeterService creates a new service instance
func NewGreeterService() *GreeterService {
return &GreeterService{}
}
// SayHello implements the greeting logic
func (s *GreeterService) SayHello(ctx context.Context, p *gengreeter.SayHelloPayload) (*gengreeter.SayHelloResult, error) {
// Add input validation if needed
if p.Name == "" {
return nil, fmt.Errorf("name cannot be empty")
}
// Build the greeting
greeting := fmt.Sprintf("Hello, %s!", p.Name)
// Return the result
return &gengreeter.SayHelloResult{
Greeting: greeting,
}, nil
}
Create your server entry point in cmd/greeter/main.go
:
package main
import (
"context"
"log"
"net"
"os"
"os/signal"
"syscall"
"grpcgreeter"
gengreeter "grpcgreeter/gen/greeter"
genpb "grpcgreeter/gen/grpc/greeter/pb"
genserver "grpcgreeter/gen/grpc/greeter/server"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
func main() {
// Create a TCP listener
lis, err := net.Listen("tcp", ":8090")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// Create a new gRPC server with options
srv := grpc.NewServer(
grpc.UnaryInterceptor(loggingInterceptor),
)
// Initialize your service
svc := greeter.NewGreeterService()
// Create endpoints
endpoints := gengreeter.NewEndpoints(svc)
// Register service with gRPC server
genpb.RegisterGreeterServer(srv, genserver.New(endpoints, nil))
// Enable server reflection for debugging tools
reflection.Register(srv)
// Handle graceful shutdown
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
log.Println("Shutting down gRPC server...")
srv.GracefulStop()
}()
// Start serving
log.Printf("gRPC server listening on :8090")
if err := srv.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
// Example logging interceptor
func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
log.Printf("Handling %s", info.FullMethod)
return handler(ctx, req)
}
Let’s break down the key components of our gRPC server:
TCP Listener Setup:
lis, err := net.Listen("tcp", ":8090")
Opens port 8090 for incoming gRPC connections. This is where your service will listen for client requests.
Server Creation:
srv := grpc.NewServer(
grpc.UnaryInterceptor(loggingInterceptor),
)
Creates a new gRPC server with middleware (interceptor) support. The logging interceptor will log every incoming request.
Service Registration:
svc := greeter.NewGreeterService()
endpoints := gengreeter.NewEndpoints(svc)
genpb.RegisterGreeterServer(srv, genserver.New(endpoints, nil))
Server Reflection:
reflection.Register(srv)
Enables gRPC reflection, allowing tools like grpcurl
to discover your service methods dynamically.
Graceful Shutdown:
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
srv.GracefulStop()
}()
Request Logging:
func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
log.Printf("Handling %s", info.FullMethod)
return handler(ctx, req)
}
grpcurl
to discover servicesBuild the service:
go build -o greeter cmd/greeter/main.go
Run the server:
./greeter
Your gRPC service is now running and ready to accept connections on port 8090!
Now that your service is implemented and running, you can:
Remember to check out the gRPC Concepts section for advanced topics like streaming, middleware, and error handling.