README ¶
grpc-scaffolding
grpc-scaffolding aims to scaffold grpc project quickly by generating code via the proto definition and go template.
The following is Go project layout scaffold generated:
├── Dockerfile
├── Makefile
├── .gitignore
├── .env.example
├── README.md
├── cmd
│ └── main.go
├── config
│ ├── config.go
│
├── internal/ should put business logic here.
├── pkg/
└── server
│ ├── handler
│ │ ├── health.go
│ │ ...
│ ├── grpc
│ │ ├── server.go
│ ├── server.go
if you define service proto at repo rpc-proto
like:
...
// HealthService ...
service HealthService {
// Ready ...
rpc Ready (ReadyRequest) returns (ReadyResponse) {
option (google.api.http) = {
post: "/api/v1/ready"
body: "*"
};
}
}
// ReadyRequest ...
message ReadyRequest{
}
// ReadyResponse ...
message ReadyResponse {
}
...
// ExampleService ...
service ExampleService {
// Hello ...
rpc Hello (HelloRequest) returns (HelloResponse) {
option (google.api.http) = {
post: "/api/v1/hello"
body: "*"
};
}
}
// HelloRequest defines input to hello.
message HelloRequest{
string msg = 1 [(validate.rules).string.min_len = 1];
}
// HelloResponse ...
message HelloResponse {
string msg = 1;
}
project generated will has files like:
project-name/server/grpc/server.go
// Code generated by go generate; DO NOT EDIT.
// This file was generated by @generated
// source: codegen/grpcserver/tmpl/server.go.tmpl
package server
import (
"context"
"fmt"
"net"
http "net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"os"
"os/signal"
"syscall"
"time"
promhttp "github.com/prometheus/client_golang/prometheus/promhttp"
examplev1 "github.com/linhbkhn95/grpc-service/go/example/v1"
healthv1 "github.com/linhbkhn95/grpc-service/go/health/v1"
grpc "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/protobuf/encoding/protojson"
)
type (
// Server is server struct which contains both grpc and http server.
Server struct {
gRPC *grpc.Server
mux *runtime.ServeMux
cfg Config
isRunningDevelopment bool
}
// Config hold http/grpc server config
Config struct {
GRPC ServerListen `yaml:"grpc" mapstructure:"grpc"`
HTTP ServerListen `yaml:"http" mapstructure:"http"`
}
// ServerListen config for host/port socket listener
// nolint:revive
ServerListen struct {
Host string `yaml:"host" mapstructure:"host"`
Port int `yaml:"port" mapstructure:"port"`
}
)
// DefaultConfig return a default server config
func DefaultConfig() Config {
return NewConfig(10443, 10080)
}
// NewConfig return a optional config with grpc port and http port.
func NewConfig(grpcPort, httpPort int) Config {
return Config{
GRPC: ServerListen{
Host: "0.0.0.0",
Port: grpcPort,
},
HTTP: ServerListen{
Host: "0.0.0.0",
Port: httpPort,
},
}
}
// String return socket listen DSN
func (l ServerListen) String() string {
return fmt.Sprintf("%s:%d", l.Host, l.Port)
}
func NewServer(cfg Config, isRunningDevelopment bool, opt ...grpc.ServerOption) *Server {
return &Server{
gRPC: grpc.NewServer(opt...),
mux: runtime.NewServeMux(
runtime.WithMarshalerOption(runtime.MIMEWildcard,
&runtime.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: false,
UseEnumNumbers: false,
EmitUnpopulated: true,
},
UnmarshalOptions: protojson.UnmarshalOptions{
DiscardUnknown: true,
},
})),
cfg: cfg,
isRunningDevelopment: isRunningDevelopment,
}
}
func (s *Server) Register(grpcServer ...interface{}) error {
for _, srv := range grpcServer {
switch _srv := srv.(type) {
case healthv1.HealthServiceServer:
healthv1.RegisterHealthServiceServer(s.gRPC, _srv)
if err := healthv1.RegisterHealthServiceHandlerFromEndpoint(context.Background(), s.mux, s.cfg.GRPC.String(), []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}); err != nil {
return err
}
case examplev1.ExampleServiceServer:
examplev1.RegisterExampleServiceServer(s.gRPC, _srv)
if err := examplev1.RegisterExampleServiceHandlerFromEndpoint(context.Background(), s.mux, s.cfg.GRPC.String(), []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}); err != nil {
return err
}
default:
return fmt.Errorf("Unknown GRPC Service to register %#v", srv)
}
}
return nil
}
// Serve server listen for HTTP and GRPC
func (s *Server) Serve() error {
stop := make(chan os.Signal, 1)
errch := make(chan error)
signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
httpMux := http.NewServeMux()
httpMux.Handle("/metrics", promhttp.Handler())
httpMux.Handle("/", s.mux)
httpServer := http.Server{
Addr: s.cfg.HTTP.String(),
Handler: httpMux,
}
go func() {
if err := httpServer.ListenAndServe(); err != nil {
errch <- err
}
}()
go func() {
listener, err := net.Listen("tcp", s.cfg.GRPC.String())
if err != nil {
errch <- err
return
}
if err := s.gRPC.Serve(listener); err != nil {
errch <- err
}
}()
for {
select {
case <-stop:
ctx, cancelFn := context.WithTimeout(context.Background(), 30*time.Second)
defer cancelFn()
s.gRPC.GracefulStop()
if err := httpServer.Shutdown(ctx); err != nil {
fmt.Println("failed to stop server: %w", err)
}
if !s.isRunningDevelopment {
fmt.Println("Shutting down. Wait for 15 seconds")
time.Sleep(15 * time.Second)
}
return nil
case err := <-errch:
return err
}
}
}
project-name/server/handler/health.go
package handler
import (
"context"
healthv1 "github.com/linhbkhn95/grpc-service/go/health/v1"
)
type HealthServer struct {
healthv1.UnimplementedHealthServiceServer
}
func NewHealthServer() healthv1.HealthServiceServer {
return &HealthServer{}
}
//TODO: implement methods of this service.
// Ready ...
func (s HealthServer) Ready(ctx context.Context, req *healthv1.ReadyRequest) (*healthv1.ReadyResponse, error) {
return &healthv1.ReadyResponse{}, nil
}
project-name/server/handler/example.go
package handler
import (
"context"
examplev1 "github.com/linhbkhn95/grpc-service/go/example/v1"
)
type ExampleServer struct {
examplev1.UnimplementedExampleServiceServer
}
func NewExampleServer() examplev1.ExampleServiceServer {
return &ExampleServer{}
}
//TODO: implement methods of this service.
// SayHello will send hello term to server.
func (s ExampleServer) SayHello(ctx context.Context, req *examplev1.SayHelloRequest) (*examplev1.SayHelloResponse, error) {
return &examplev1.SayHelloResponse{}, nil
}
// SayGoodbye will send goodbye term to server.
func (s ExampleServer) SayGoodbye(ctx context.Context, req *examplev1.SayGoodbyeRequest) (*examplev1.SayGoodbyeResponse, error) {
return &examplev1.SayGoodbyeResponse{}, nil
}
Features
- Easy generate project from proto definition
- Provide layout standard and
rpc server
instance for implementation. Separate layertransport
andservice
Modules
- layout
- grpc_server
Usage
- install:
go get github.com/linhbkhn95/grpc-scaffolding
orgo install github.com/linhbkhn95/grpc-scaffolding@latest
if your terminal throw error like
go: downloading github.com/linhbkhn95/grpc-scaffolding v0.0.0-20220728042906-33c17ac9cdfd
go: github.com/linhbkhn95/grpc-scaffolding@latest: github.com/linhbkhn95/grpc-scaffolding@v0.0.0-20220728042906-33c17ac9cdfd: verifying module: github.com/linhbkhn95/grpc-scaffolding@v0.0.0-20220728042906-33c17ac9cdfd: reading https://sum.golang.org/lookup/github.com/!kyber!network/rpc-framework@v0.0.0-20220728042906-33c17ac9cdfd: 410 Gone
server response:
not found: github.com/linhbkhn95/grpc-scaffolding@v0.0.0-20220728042906-33c17ac9cdfd: invalid version: git ls-remote -q origin in /tmp/gopath/pkg/mod/cache/vcs/12330e8f12b843c19322388d9f726ba65c92be1dda46cd6a348f373be8edd50d: exit status 128:
fatal: could not read Username for 'https://github.com': terminal prompts disabled
Confirm the import path was entered correctly.
If this is a private repository, see https://golang.org/doc/faq#git_https for additional information.
please setup private repo via command: export GOPRIVATE=github.com/linhbkhn95/*
and git config --global url.https://${{ PERSONAL_ACCESS_TOKEN }}@github.com/.insteadOf https://github.com/
-
install grpc folder: run
git clone git@github.com:linhbkhn95/rpc-proto.git
. -
cd module need to use then run
go generate
. -
you can edit flag at header of file main.go before generate:
go:generate go run gen.go grpc_server --rpc_protos=health/v1/health.proto
.
Layout module
It is really rpc-framework
which will init project and make layout for your project. It will read proto
file from proto
path which defined. It include all of GRPC
interface. So, It can generate something which you need like (RPC
instance, handler implementation ...)
Example: when run init project example
which combine by two services health
and example
.
You should run command: (should run in folder which contains rpc-proto
dir)
rpc-framework --project_name=example --go_module_name=github.com/linhbkhn95/example --rpc_proto_dir=rpc-proto/proto --rpc_protos=health/v1/service.proto,example/v1/service.proto
When you run command above, you should see like something below.
2022/07/27 00:25:26 File was generate at example/config/config.go
2022/07/27 00:25:26 File was generate at example/server/server.go
2022/07/27 00:25:27 File was generate at example/server/grpc/server.go
2022/07/27 00:25:27 File was generate at example/server/handler/health.go
2022/07/27 00:25:27 File was generate at example/cmd/main.go
2022/07/27 00:25:27 File was generate at example/.gitignore
2022/07/27 00:25:27 File was generate at example/go.mod
2022/07/27 00:25:27 File was generate at example/Makefile
2022/07/27 00:25:27 File was generate at example/.gitignore
2022/07/27 00:25:27 Generated completed!
2022/07/27 00:25:27 installing dependencies ...
2022/07/27 00:25:27 installing grpc ecosystem ...
2022/07/27 00:25:27 installing viper...
2022/07/27 00:25:27 installing github.com/linhbkhn95/golang-british ...
2022/07/27 00:25:35 installing github.com/linhbkhn95/grpc-service ...
2022/07/27 00:25:40 installing google.golang.org/grpc ...
2022/07/27 00:25:41 installing github.com/grpc-ecosystem/go-grpc-middleware ...
2022/07/27 00:25:42 installing github.com/grpc-ecosystem/grpc-gateway/v2/runtime ...
2022/07/27 00:25:42 installing github.com/prometheus/client_golang/prometheus/promhttp ...
2022/07/27 00:25:43 installing google.golang.org/grpc ...
2022/07/27 00:25:43 installing google.golang.org/grpc/credentials/insecure ...
2022/07/27 00:25:44 installing google.golang.org/protobuf/encoding/protojson ...
2022/07/27 00:25:44 installing github.com/grpc-ecosystem/go-grpc-prometheus ...
2022/07/27 00:25:45 run go mod tidy ...
2022/07/27 00:25:45 install successfully ..
the result like here. https://github.com/linhbkhn95/grpc-scaffolding/tree/main/example
Module flags
-
Grpc server
rpc_protos
: list service from project grpc, default value:health/v1/service.proto
.enable_gateway
: flag use enable gateway for server, default value:false
.output_path
: goal path then generate, default value:grpc_server/output/z_server_grpc.go
.rpc_proto_dir
: Folder contain services of project, default value :../rpc-proto/proto
.enable_metric
: flag to enable metric by prometheus module. default value:false
.enable_http
: fag to enable port http, default value:true
.
-
Layout It will contain all of flags of
grpc_server
and add 2 flags flag isproject_name
andgo_module_name
. So, You should run command
rpc-framework --project_name=example --go_module_name=github.com/linhbkhn95/example --rpc_proto_dir=rpc-proto/proto --rpc_protos=health/v1/service.proto,example/v1/service.proto