| name | go-service-scaffold |
| description | Scaffold a layered Go backend service with api-handler, service, dao, model, dto, db, config, and util packages. Use when creating a new Go service that should follow this project's patterns: container-based dependency injection, interface-first service/dao design, Apollo remote config bootstrap, Gin + Swagger HTTP API setup, Gorm + gorm/gen model/query generation, cron startup wiring, and a clean project skeleton that can be extended for meeting systems or other business services. |
Go Service Scaffold
Use this skill when creating a new Go backend service that should look like this repository in structure and engineering style.
Goals
- Keep
api -> service -> dao -> model/query as the main dependency chain.
- Keep handlers thin and business logic inside
service/.
- Keep DB and remote-system access in
dao/.
- Use interface-first dependency seams where modules collaborate.
- Use a container as the single composition root.
- Use Apollo for config, Gin for HTTP, Swagger for API docs, and Gorm plus
gorm/gen for persistence code generation.
Default Project Structure
.
|-- api/
| |-- handler/
| | |-- container.go
| | `-- <module>.go
| |-- cron.go
| `-- http_router.go
|-- cmd/
| `-- generate/
| |-- mysql_model.go
| `-- mysql_query.go
|-- config/
| |-- remote.go
| `-- types.go
|-- dao/
| `-- <module>.go
|-- db/
| |-- mysql.go
| `-- redis.go
|-- docs/
|-- dto/
| `-- <module>.go
|-- enum/
|-- errs/
|-- model/
| `-- query/
|-- service/
| `-- <module>/
| `-- <module>.go
|-- util/
|-- go.mod
`-- main.go
Layer Rules
api/handler
- Define handler interfaces and service interfaces used by handlers.
- Bind params, validate input, extract context values, call service, return response.
- Do not call DAO directly.
service
- Hold business orchestration.
- Combine DAO calls, external SDK calls, and business rules.
- Define minimal interfaces for peer-service collaboration.
- Do not depend on Gin types except passing
context.Context.
dao
- Encapsulate MySQL, Redis, and remote-system access.
- Expose interface plus private implementation struct.
- Use generated
model/query helpers for standard DB operations.
model
- Keep generated table structs and a small amount of shared domain data structures.
- Do not put business orchestration here.
dto
- Keep request, response, and event payload structs here.
Interface Strategy
Follow the same style as this project:
- Handlers depend on service interfaces, not concrete structs.
- Services depend on DAO interfaces and minimal peer-service interfaces.
- DAOs expose interfaces and hide concrete implementations.
- Use interfaces when they help decouple modules, avoid circular dependencies, or make tests easier.
- Do not create interfaces for trivial local helpers that will never vary.
Container Strategy
Create one container type as the composition root.
The container should:
- initialize config-backed SDK clients
- initialize MySQL and Redis clients
- call
query.SetDefault(mysqlCli)
- create DAO instances
- create service instances
- expose only what handlers and cron need
Do not construct service graphs inside handlers or routers.
Typical shape:
type ApiContainer struct {
roomDao dao.RoomDao
roomService *room.Service
userService *user.Service
}
func NewApiContainer() *ApiContainer {
cfg := config.GetAll()
mysqlCli := db.InitMysqlClient(cfg.MySQL.Dsn)
query.SetDefault(mysqlCli)
roomDao := dao.NewRoomDao(mysqlCli, query.Q)
roomService := room.NewService(roomDao)
return &ApiContainer{
roomDao: roomDao,
roomService: roomService,
}
}
Apollo Config Pattern
Keep the config package API stable and simple.
Recommended shape:
var cfg *atomic.Value
func Init(env, url string) {
cfg, err = remotecfg.NewApollo(url, appID, env).InitAndWatchYamlCfg(namespace, &Config{})
if err != nil {
panic(...)
}
}
func GetAll() *Config { return cfg.Load().(*Config) }
func GetServer() Server { return GetAll().Server }
func GetMySQL() MySQL { return GetAll().MySQL }
Guidelines:
- group config by domain:
Server, App, MySQL, Redis, Client, and feature groups
- keep getters small and explicit
- if Apollo is replaced later, keep
config.Init/GetAll/GetXxx stable
Swagger Pattern
Use swaggo/gin-swagger.
Guidelines:
- annotate handler methods
- generate output into
docs/
- register Swagger route only outside release mode
Typical command:
swag init -g main.go -o docs
Typical router snippet:
if gin.Mode() != gin.ReleaseMode {
engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
Gorm And gorm/gen Pattern
Use Gorm for connection bootstrap and generated query helpers for DAO code.
Recommended split:
db/mysql.go: return *gorm.DB
model/*.gen.go: generated table structs
model/query/*.gen.go: generated query code
cmd/generate/mysql_model.go: model generation entrypoint
cmd/generate/mysql_query.go: query generation entrypoint
Startup:
mysqlCli := db.InitMysqlClient(cfg.MySQL.Dsn)
query.SetDefault(mysqlCli)
DAO shape:
type roomDao struct {
db *gorm.DB
q *query.Query
}
Use generated query builders for routine CRUD. Use raw SQL only when it is clearly simpler or required.
Startup Pattern
main.go should do only high-level bootstrap:
- parse
--env and --cfg
- call
config.Init
- initialize logger if the project uses one
- create container
- start HTTP API
- start cron jobs
Typical shape:
func main() {
flag.Parse()
config.Init(*env, *configURL)
container := handler.NewApiContainer()
api.InitAndRunHttpApi(container)
api.InitAndRunCron(container)
}
HTTP Router Pattern
Keep all route assembly in api/http_router.go.
Guidelines:
- create handlers from container
- register middleware once
- group routes by domain
- keep actual business logic out of the router
Cron Pattern
Keep api/cron.go separate so background jobs share the same container-managed services.
Use cron for:
- timeout scanning
- scheduled sync or trigger jobs
- delayed persistence or cleanup
First Module Template
When adding a new business module, create these files together:
dto/<module>.go
dao/<module>.go
service/<module>/<module>.go
api/handler/<module>.go
Then wire them in:
api/handler/container.go
api/http_router.go
Use this minimal shape:
package dto
type GetUserResp struct {
ID uint64 `json:"id"`
}
package dao
import (
"context"
"your/module/model"
"your/module/model/query"
"gorm.io/gorm"
)
type UserDao interface {
GetByID(ctx context.Context, id uint64) (*model.TblUser, bool, error)
}
type userDao struct {
db *gorm.DB
q *query.Query
}
func NewUserDao(db *gorm.DB, q *query.Query) UserDao {
return &userDao{db: db, q: q}
}
package user
import (
"context"
"your/module/dao"
"your/module/dto"
)
type Service struct {
userDao dao.UserDao
}
func NewService(userDao dao.UserDao) *Service {
return &Service{userDao: userDao}
}
func (s *Service) GetByID(ctx context.Context, id uint64) (*dto.GetUserResp, error) {
row, exists, err := s.userDao.GetByID(ctx, id)
if err != nil {
return nil, err
}
if !exists {
return nil, nil
}
return &dto.GetUserResp{ID: row.ID}, nil
}
package handler
import (
"context"
"github.com/gin-gonic/gin"
"your/module/dto"
)
type UserHandler interface {
GetByID(c *gin.Context)
}
type UserService interface {
GetByID(ctx context.Context, id uint64) (*dto.GetUserResp, error)
}
type userHandler struct {
svc UserService
}
Minimal go.mod Baseline
Start from dependencies like these and trim or add as needed:
require (
github.com/gin-gonic/gin v1.10.0
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.4
gorm.io/driver/mysql v1.5.7
gorm.io/gen v0.3.27
gorm.io/gorm v1.25.12
)
Add company-private logging, Apollo, auth, or SDK dependencies only after the structure is in place.
Useful Generation Commands
go mod tidy
go run ./cmd/generate/mysql_model.go
go run ./cmd/generate/mysql_query.go
swag init -g main.go -o docs
go test ./...
Quick Start Checklist
- Create the package skeleton above.
- Implement
config.Init with Apollo.
- Implement
db/mysql.go.
- Add generated
model and model/query flow.
- Add
ApiContainer.
- Add one sample business module end to end.
- Register routes and Swagger.
- Add cron only if needed.
- Run generation commands and tests.
Avoid These Mistakes
- Do not let handlers call DAOs directly.
- Do not let services depend on the full container.
- Do not let DAOs return HTTP-layer response wrappers.
- Do not hand-edit generated Gorm files.
- Do not initialize SDK clients in random packages.
- Do not put all logic in one huge
service package without domain folders.