首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Gin 模板自动生成 DDD 代码结构

Gin 模板自动生成 DDD 代码结构

原创
作者头像
浩瀚星河
发布2025-04-06 14:45:20
发布2025-04-06 14:45:20
2280
举报
文章被收录于专栏:golanggolang

该文章的灵感来自陈明勇大佬的fnote项目,该项目内置了自动生成DDD代码结构的脚本,避免构建新模块的时候重复的书写相同的逻辑繁琐的代码,非常实用

该脚本可以通过指定-domain自动生成类似如下的代码结构

代码语言:txt
复制
posts/
├── internal/                # 内部实现,限制外部访问
│   ├── domain/              # 领域层
│   │   └── posts.go         # 领域模型 (如实体定义)
│   ├── repository/          # 仓储层
│   │   ├── dao/             # 数据访问对象 (DAO)
│   │   │   └── posts.go     # PostsDao 实现 (dao.IPostsDao)
│   │   └── repository.go    # PostsRepo 实现 (repository.IPostsRepo)
│   ├── service/             # 领域服务层
│   │   └── service.go       # PostsService 实现 (service.IPostsService)
│   └── web/                 # 接口层
│       ├── handler.go       # PostHandler 实现 (Gin 处理器)
│       ├── vo.go            # 值对象 (如响应结构体)
│       └── request.go       # 请求对象 (如请求参数结构体)
├── module.go                # 模块入口,包含 InitPostsModule
└── wire.go                  # Wire 依赖注入生成文件

前提

将tmpl和tpl与go模板文件关联

image-20250405142413209
image-20250405142413209

构建DDD架构所需结构体

代码语言:go
复制
// GenDomain 生成DDD结构目录所需参数
type GenDomain struct {
	DomainName    string
	UnderlineName string
	TableName     string
	OutputDir     string
}

参数剖析

DomainName指的是实体名

UnderlineName指的是包名(一般是domain的蛇形命名法)

TableName指的是表名

OutputDir指的是生成代码结构的输出目录

定义变量

flag参数

代码语言:go
复制
domain    = flag.String("domain", "", "the name of domain;eg: User")
table     = flag.String("table", "", "the name of table;eg: user")
output    = flag.String("output", "", "the output directory;eg: internal/user")
underline = new(string)

通过flag库接受命令行传递的参数,为后续的模板解析传参做铺垫

embed嵌入变量

代码语言:go
复制
//go:embed templates/domain.tmpl
domainTpl embed.FS

//go:embed templates/dao.tmpl
daoTpl embed.FS

//go:embed templates/repository.tmpl
repositoryTpl embed.FS

//go:embed templates/service.tmpl
serviceTpl embed.FS

//go:embed templates/web.tmpl
HandlerTpl embed.FS

//go:embed templates/request.tmpl
RequestTpl embed.FS

//go:embed templates/vo.tmpl
VOTpl embed.FS

//go:embed templates/module.tmpl
ModuleTpl embed.FS

//go:embed templates/wire.tmpl
WireTpl embed.FS

利用embedtmpl嵌入到go变量当中,供后续解析

校验参数

代码语言:go
复制
// 校验命令行参数并设置默认值
func validateFlags(domain, table, output, underline *string) error {
    if *domain == "" {
       return errors.New("domain参数不能为空;eg: -domain=User")
    }
    if !regexp.MustCompile(`^[a-zA-Z0-9_]+$`).MatchString(*domain) {
       return errors.New("domain参数包含非法字符,只能包含字母、数字和下划线")
    }

    snakeCaseName := stringx.CamelToSnake(*domain)
    if *output == "" {
       *output = fmt.Sprintf("internal/%s", snakeCaseName)
    }

    if *table == "" {
       *table = snakeCaseName
    }

    *underline = snakeCaseName
    return nil
}

对命令行参数进行校验,不合法则报错,domain参数是必填的,其他变量是选填的,不填会为其提供默认值。

如:

  • output为空,默认值为internal/(domain转蛇形命名)
  • table为空,默认值为(domain转蛇形命名)

生成模板

代码语言:go
复制
// 生成模板代码
func genTemplate(fs embed.FS, templatePath string, outputDir string, output string, gen GenDomain) {
    tpl, err := template.ParseFS(fs, templatePath)
    if err != nil {
       panic(fmt.Sprintf("解析模板失败: %s", err.Error()))
    }

    if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
       panic(fmt.Sprintf("解析目标文件夹失败: %s", err.Error()))
    }

    outputPath := outputDir + output
    dst, err := os.Create(outputPath)
    defer dst.Close()
    if err != nil {
       panic(fmt.Sprintf("创建目标文件失败: %s", err.Error()))
    }

    if tpl.Execute(dst, gen) != nil {
       panic("生成模板文件失败")
    }

    log.Printf("生成模板文件成功: %s", outputPath)
}

完整代码

本教程基于mongodb数据库,使用的是mongox数据库ORM工具,直接上代码

gen脚本代码结构如下:

image-20250406141430713
image-20250406141430713

在项目根目录的cmd/gen下编写脚本gen.go

代码语言:go
复制
package main

import (
	"embed"
	"flag"
	"fmt"
	"github.com/chenmingyong0423/gkit/stringx"
	"github.com/pkg/errors"
	"log"
	"os"
	"regexp"
	"text/template"
)

// GenDomain 生成DDD结构目录所需参数
type GenDomain struct {
	DomainName    string
	UnderlineName string
	TableName     string
	OutputDir     string
}

var (
	domain    = flag.String("domain", "", "the name of domain;eg: User")
	table     = flag.String("table", "", "the name of table;eg: user")
	output    = flag.String("output", "", "the output directory;eg: internal/user")
	underline = new(string)

	//go:embed templates/domain.tmpl
	domainTpl embed.FS

	//go:embed templates/dao.tmpl
	daoTpl embed.FS

	//go:embed templates/repository.tmpl
	repositoryTpl embed.FS

	//go:embed templates/service.tmpl
	serviceTpl embed.FS

	//go:embed templates/web.tmpl
	HandlerTpl embed.FS

	//go:embed templates/request.tmpl
	RequestTpl embed.FS

	//go:embed templates/vo.tmpl
	VOTpl embed.FS

	//go:embed templates/module.tmpl
	ModuleTpl embed.FS

	//go:embed templates/wire.tmpl
	WireTpl embed.FS
)

func main() {
	flag.Parse()
	if err := validateFlags(domain, output, table, underline); err != nil {
		panic(err.Error())
	}

	gen := GenDomain{
		DomainName:    *domain,
		OutputDir:     *output,
		TableName:     *table,
		UnderlineName: *underline,
	}

	genTemplate(domainTpl, "templates/domain.tmpl", *output+"/internal/domain", fmt.Sprintf("/%s.go", gen.UnderlineName), gen)
	genTemplate(daoTpl, "templates/dao.tmpl", *output+"/internal/repository/dao", fmt.Sprintf("/%s.go", gen.UnderlineName), gen)
	genTemplate(repositoryTpl, "templates/repository.tmpl", *output+"/internal/repository", fmt.Sprintf("/%s.go", gen.UnderlineName), gen)
	genTemplate(serviceTpl, "templates/service.tmpl", *output+"/internal/service", fmt.Sprintf("/%s.go", gen.UnderlineName), gen)
	genTemplate(HandlerTpl, "templates/web.tmpl", *output+"/internal/web", fmt.Sprintf("/%s.go", gen.UnderlineName), gen)
	genTemplate(RequestTpl, "templates/request.tmpl", *output+"/internal/web", "/request.go", gen)
	genTemplate(VOTpl, "templates/vo.tmpl", *output+"/internal/web", "/vo.go", gen)
	genTemplate(ModuleTpl, "templates/module.tmpl", *output, "/module.go", gen)
	genTemplate(WireTpl, "templates/wire.tmpl", *output, "/wire.go", gen)
}

// 校验命令行参数并设置默认值
func validateFlags(domain, table, output, underline *string) error {
	if *domain == "" {
		return errors.New("domain参数不能为空;eg: -domain=User")
	}
	if !regexp.MustCompile(`^[a-zA-Z0-9_]+$`).MatchString(*domain) {
		return errors.New("domain参数包含非法字符,只能包含字母、数字和下划线")
	}

	snakeCaseName := stringx.CamelToSnake(*domain)
	if *output == "" {
		*output = fmt.Sprintf("internal/%s", snakeCaseName)
	}

	if *table == "" {
		*table = snakeCaseName
	}

	*underline = snakeCaseName
	return nil
}

// 生成模板代码
func genTemplate(fs embed.FS, templatePath string, outputDir string, output string, gen GenDomain) {
	tpl, err := template.ParseFS(fs, templatePath)
	if err != nil {
		panic(fmt.Sprintf("解析模板失败: %s", err.Error()))
	}

	if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
		panic(fmt.Sprintf("解析目标文件夹失败: %s", err.Error()))
	}

	outputPath := outputDir + output
	dst, err := os.Create(outputPath)
	defer dst.Close()
	if err != nil {
		panic(fmt.Sprintf("创建目标文件失败: %s", err.Error()))
	}

	if tpl.Execute(dst, gen) != nil {
		panic("生成模板文件失败")
	}

	log.Printf("生成模板文件成功: %s", outputPath)
}

templates/domain.tmpl

代码语言:go
复制
package domain

type {{.DomainName}} struct{
}

templates/dao.tmpl

代码语言:go
复制
package dao

import (
	"github.com/chenmingyong0423/go-mongox/v2"
)

type {{.DomainName}} struct {
}

type I{{.DomainName}}Dao interface {
}

var _ I{{.DomainName}}Dao = (*{{.DomainName}}Dao)(nil)

func New{{.DomainName}}Dao(db *mongox.Database) *{{.DomainName}}Dao {
	return &{{.DomainName}}Dao{coll: mongox.NewCollection[{{.DomainName}}](db, "{{.TableName}}")}
}

type {{.DomainName}}Dao struct {
	coll *mongox.Collection[{{.DomainName}}]
}

templates/repository.tmpl

代码语言:go
复制
package repository

import (
	"github.com/codepzj/Stellux/server/{{.OutputDir}}/internal/repository/dao"
)

type I{{.DomainName}}Repo interface {
}

var _ I{{.DomainName}}Repo = (*{{.DomainName}}Repo)(nil)

func New{{.DomainName}}Repo(dao dao.I{{.DomainName}}Dao) *{{.DomainName}}Repo {
	return &{{.DomainName}}Repo{dao: dao}
}

type {{.DomainName}}Repo struct {
	dao dao.I{{.DomainName}}Dao
}

templates/service.tmpl

代码语言:go
复制
package service

import (
    "github.com/codepzj/Stellux/server/{{.OutputDir}}/internal/repository"
)

type I{{.DomainName}}Service interface {
}

var _ I{{.DomainName}}Service = (*{{.DomainName}}Service)(nil)

func New{{.DomainName}}Service(repo repository.I{{.DomainName}}Repo) *{{.DomainName}}Service {
    return &{{.DomainName}}Service{repo: repo}
}

type {{.DomainName}}Service struct {
    repo repository.I{{.DomainName}}Repo
}

templates/web.tmpl

代码语言:go
复制
package web

import (
    "github.com/codepzj/Stellux/server/{{.OutputDir}}/internal/service"
    "github.com/gin-gonic/gin"
)

type I{{.DomainName}}Handler interface {
}

var _ I{{.DomainName}}Handler = (*{{.DomainName}}Handler)(nil)

func New{{.DomainName}}Handler(serv service.I{{.DomainName}}Service) *{{.DomainName}}Handler {
    return &{{.DomainName}}Handler{serv: serv}
}

type {{.DomainName}}Handler struct {
    serv service.I{{.DomainName}}Service
}

func (h *{{.DomainName}}Handler) RegisterGinRoutes(router *gin.Engine) {
}

templates/request.tmpl

代码语言:go
复制
package web

type {{.DomainName}}Request struct {}

templates/vo.tmpl

代码语言:go
复制
package web

type {{.DomainName}}VO struct{
}

templates/module.tmpl

代码语言:go
复制
package {{.UnderlineName}}

import (
    "github.com/codepzj/Stellux/server/{{.OutputDir}}/internal/service"
    "github.com/codepzj/Stellux/server/{{.OutputDir}}/internal/web"
)

type (
    Handler = web.{{.DomainName}}Handler
    Service = service.I{{.DomainName}}Service
    Module  struct {
       Hdl *Handler
       Svc Service
    }
)

templates/wire.tmpl

代码语言:go
复制
//go:build wireinject
// +build wireinject

package {{.UnderlineName}}

import (
    "github.com/chenmingyong0423/go-mongox/v2"
    "github.com/codepzj/Stellux/server/{{.OutputDir}}/internal/repository"
    "github.com/codepzj/Stellux/server/{{.OutputDir}}/internal/repository/dao"
    "github.com/codepzj/Stellux/server/{{.OutputDir}}/internal/service"
    "github.com/codepzj/Stellux/server/{{.OutputDir}}/internal/web"

    "github.com/google/wire"
)

var Provider = wire.NewSet(
    web.New{{.DomainName}}Handler,service.New{{.DomainName}}Service,repository.New{{.DomainName}}Repo,dao.New{{.DomainName}}Dao,
    wire.Bind(new(service.I{{.DomainName}}Service), new(*service.{{.DomainName}}Service)),wire.Bind(new(repository.I{{.DomainName}}Repo), new(*repository.{{.DomainName}}Repo)),wire.Bind(new(dao.I{{.DomainName}}Dao), new(*dao.{{.DomainName}}Dao)),
)

func Init{{.DomainName}}Module(db *mongox.Database) *Module {
    wire.Build(
       Provider,
       wire.Struct(new(Module), "Hdl", "Svc"),
    )
    return nil
}

效果演示

代码语言:bash
复制
go run cmd/gen/gen.go -domain=Payment -output=internal/payment -table=payment
image-20250406141739649
image-20250406141739649

展示部分生成的代码

repository/dao/payment.go

image-20250406142045322
image-20250406142045322

web/payment.go

image-20250406142118015
image-20250406142118015

module.go

image-20250406142158327
image-20250406142158327

wire.go

image-20250406142228897
image-20250406142228897

成功生成对应的代码结构和目录,只需专注于相应的业务逻辑即可,不需要重新定义和书写重复的构建逻辑,相当于一个脚手架👍👍👍

参考资料

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前提
  • 构建DDD架构所需结构体
    • 参数剖析
  • 定义变量
    • flag参数
    • embed嵌入变量
  • 校验参数
  • 生成模板
  • 完整代码
    • templates/domain.tmpl
    • templates/dao.tmpl
    • templates/repository.tmpl
    • templates/service.tmpl
    • templates/web.tmpl
    • templates/request.tmpl
    • templates/vo.tmpl
    • templates/module.tmpl
    • templates/wire.tmpl
  • 效果演示
    • 展示部分生成的代码
    • repository/dao/payment.go
    • web/payment.go
    • module.go
    • wire.go
  • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档