Standard HTTP and GRPC Go Project Layout with Protobuf and GORM.
To upgrade all dependencies at once for a given module, just run the following from the root directory of your module
-
Proto file definition
service Demo {
rpc Demo(golang-project-layout.api.general.v1.DemoRequest) returns (golang-project-layout.api.general.v1.DemoResponse) {
option (google.api.http) = {
get: "/api/v1/demo"
};
}
}
message Demo {
string demo = 1;
}
message DemoRequest {
string demo = 1;
}
message DemoResponse {
Demo demo = 1;
}
-
Add RegisterDemoHandler generated by proto in internal/cmd/gateway.go file
func NewGateway(ctx context.Context, conn *grpc.ClientConn, opts []runtime.ServeMuxOption) (http.Handler, error) {
mux := runtime.NewServeMux(opts...)
for _, f := range []func(context.Context, *runtime.ServeMux, *grpc.ClientConn) error{
// Add follow line, it is generated by protoc
v1.RegisterDemoHandler,
} {
if err := f(ctx, mux, conn); err != nil {
return nil, err
}
}
return mux, nil
}
-
Create demo.go file in internal/service folder and implement the proto service apis
type DemoService struct {
// This is generated by protoc
projectv1.UnimplementedDemoServer
// Add follow line if you need database operate
// dao dao.DemoDao
}
func NewDemoService() *DemoService {
return &DemoService{}
}
// Replace to follow func if you need database operate
// func NewDemoService(dao dao.Interface) *DemoService {
// return &DemoService{dao: dao.DemoDao()}
// }
...
// Service implementation code
...
-
Add new demo func in internal/service/main.go file
func (s *Service) DemoService() DemoService {
return *NewDemoService()
// Replace to follow line if you need database operate
// return *NewDemoService(s.dao)
}
-
Add RegisterDemoServer generated by proto in internal/cmd/grpc/grpc.go file
// ...
var daoInterface dao.Interface
if daoInterface, err = initDao(); err != nil {
return err
}
// ...
// Other services register code
// ...
demoService := service.NewDemoService()
// This is generated by protoc
v1.RegisterDemoServer(s, demoService)
// ...
// Other code
// ...
go func() {
defer s.GracefulStop()
<-ctx.Done()
}()
// ...
-
Implemente Demo API in internal/service/demo.go file
func (s *DemoService) Demo(ctx context.Context, req *generalv1.DemoRequest) (*generalv1.DemoResponse, error) {
return &generalv1.DemoResponse{
Demo: &generalv1.Demo{
Demo: req.Demo,
},
}, nil
}
-
Proto file definition
service DemoDb {
rpc DemoDb(project.api.general.v1.DemoDbRequest) returns (project.api.general.v1.DemoDbResponse) {
option (google.api.http) = {
get: "/api/v1/demodb"
};
}
}
message DemoDb {
string demo_db = 1;
}
message DemoDbRequest {
string demo_db = 1;
}
message DemoDbResponse {
DemoDb demo_db = 1;
}
-
Add demodb.go in internal/model folder with model definition
type DemoDb struct {
Model
DemoDb string `json:"demo_db"`
}
-
Add demodb.go dao in internal/dao folder and define interface
type DemoDbDao interface {
DemoDb(ctx context.Context, demoDb string) (*model.DemoDb, error)
}
-
Implement dao interface in demodb.go in internal/dao/mysql with New func
type DemoDbDao struct {
Db *gorm.DB
}
// Implementation interface code
func (d DemoDbDao) DemoDb(ctx context.Context, demoDb string) (*model.DemoDb, error) {
// This is a demo code ignore it if you have real database logic
dDb := &model.DemoDb{
DemoDb: demoDb,
}
// Add GORM logic here
// if err := d.Db.Where("demo_db LIKE ?", "%"+demoDb+"%").Find(&dDb).Error; err != nil {
// return nil, err
// }
return dDb, nil
}
func NewDemoDbDao(d *gorm.DB) *DemoDbDao {
return &DemoDbDao{d}
}
-
Add gorm implement func in internal/dao/mysql/main.go file
func (d *Dao) DemoDbDao() dao.DemoDbDao {
// There could be added success after interface implement
return NewDemoDbDao(d.Db)
}
-
Add the new dao in interface of internal/dao/main.go file
type Interface interface {
// ...
// Other dao interfaces
// ...
DemoDbDao() DemoDbDao
}
-
Add RegisterDemoDbHandler generated by proto in internal/cmd/gateway.go file
func NewGateway(ctx context.Context, conn *grpc.ClientConn, opts []runtime.ServeMuxOption) (http.Handler, error) {
mux := runtime.NewServeMux(opts...)
for _, f := range []func(context.Context, *runtime.ServeMux, *grpc.ClientConn) error{
// ...
// Other code
// ...
// Add follow line, it is generated by protoc
v1.RegisterDemoDbHandler,
} {
if err := f(ctx, mux, conn); err != nil {
return nil, err
}
}
return mux, nil
}
-
Create demodb.go file in internal/service folder and implement the proto service apis
type DemoDbService struct {
// This is generated by protoc
projectv1.UnimplementedDemoDbServer
// Remove follow line if you don't need database operate
dao dao.DemoDbDao
}
func NewDemoDbService(dao dao.Interface) *DemoDbService {
return &DemoDbService{dao: dao.DemoDbDao()}
}
// Replace to follow func if you don't need database operate
// func NewDemoDbService() *DemoDbService {
// return &DemoDbService{}
// }
...
// Service implementation code
...
-
Add new demo func in internal/service/main.go file
func (s *Service) DemoDbService() DemoDbService {
return *NewDemoDbService(s.dao)
// Replace to follow line if you don't need database operate
// return *NewDemoService()
}
-
Add RegisterDemoDbServer generated by proto in internal/cmd/grpc/grpc.go file
// ...
var daoInterface dao.Interface
if daoInterface, err = initDao(); err != nil {
return err
}
// ...
// Other services register code
// ...
demoDbService := service.NewDemoDbService(daoInterface)
// This is generated by protoc
v1.RegisterDemoDbServer(s, demoDbService)
// ...
// Other code
// ...
go func() {
defer s.GracefulStop()
<-ctx.Done()
}()
// ...
-
Implemente Demo API in internal/service/demodb.go file
func (s *DemoDbService) DemoDb(ctx context.Context, req *generalv1.DemoDbRequest) (*generalv1.DemoDbResponse, error) {
demoDb, err := s.dao.DemoDb(ctx, req.DemoDb)
if err != nil {
result := status.Convert(err)
if result.Code() == codes.NotFound {
return nil, status.Errorf(codes.NotFound, "get err: %s not found", req.DemoDb)
}
return nil, status.Error(codes.Unknown, err.Error())
}
return &generalv1.DemoDbResponse{
DemoDb: &generalv1.DemoDb{
DemoDb: demoDb.DemoDb,
},
}, nil
}