Go语言微服务框架 - 8.Gormer迭代-定制专属的ORM代码生成工具

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 我们对比一下GORM库提供的`gorm.Model`,它在新增、修改时,会自动修改对应的时间,这个可以帮我们减少很多重复性的代码编写。这里,我就针对现有的gormer工具做一个示例性的迭代。

在上一篇,我们写一个gormer工具库,支持了简单的CRUD。但是,在实际的开发场景中,这部分的功能仍显得非常单薄。

例如,我们对比一下GORM库提供的gorm.Model,它在新增、修改时,会自动修改对应的时间,这个可以帮我们减少很多重复性的代码编写。这里,我就针对现有的gormer工具做一个示例性的迭代。

v0.5.2:Gormer迭代-定制更智能的代码生成工具

项目链接 https://github.com/Junedayday/micro_web_service/tree/v0.5.2

目标

生成一套智能化的Dao层代码,兼容软删除和硬删除。

这里提一下软删除的概念,就是指在数据库中用某个字段标记为删除,但这行数据仍存在;而硬删除就是直接删除整条数据。

软删除虽然增加了一定的复杂度,但带来的收益很大。最直接的好处就是能保留记录,方便查原始记录。

关键技术点

  1. gormer.yaml的文件
  2. 模板文件的修改
  3. 核心结构体梳理
  4. API调用示例

目录构造

为了方便理解,我简化对应的目录结构

--- micro_web_service            项目目录
    |-- gen                            从idl文件夹中生成的文件,不可手动修改
       |-- idl                             对应idl文件夹
          |-- demo                             对应idl/demo服务,包括基础结构、HTTP接口、gRPC接口
            |-- order                            对应idl/order服务,同上
    |-- idl                            原始的idl定义
       |-- demo                            业务package定义,protobuffer的原始定义
       |-- order                           业务order定义,同时干
    |-- internal                       项目的内部代码,不对外暴露
       |-- config                          配置相关的文件夹,viper的相关加载逻辑
       |-- dao                             Data Access Object层,是model层的实现
       |-- gormer                          从pkg/gormer中生成的相关代码,不允许更改
       |-- model                           model层,定义对象的接口方法,具体实现在dao层
       |-- mysql                           MySQL连接
       |-- server                          服务器的实现,对idl中定义服务的具体实现
       |-- service                         service层,作为领域实现的核心部分
     |-- zlog                            封装zap日志的代码实现
  |-- pkg                            开放给第三方的工具库
     |-- gormer                          修改:gormer二进制工具,用于生成Gorm相关Dao层代码
    |-- buf.gen.yaml                   buf生成代码的定义,从v1beta升到v1
    |-- buf.yaml                       buf工具安装所需的工具,从v1beta升到v1
    |-- gen.sh                         生成代码的脚本:buf+gormer
    |-- go.mod                         Go Module文件
    |-- gormer.yaml                    新增:将gormer中的参数移动到这里
    |-- main.go                        项目启动的main函数

1.gormer.yaml的文件

这里先给出具体的建表语句,可以清晰地看到orders表6个字段的具体含义:

CREATE TABLE orders
(
id bigint PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
name varchar(255) COMMENT '名称,建议唯一',
price decimal(15,3) COMMENT '订单价格',
create_time timestamp NULL DEFAULT NULL COMMENT '创建时间',
update_time timestamp NULL DEFAULT NULL COMMENT '更新时间',
delete_status tinyint(3) COMMENT '删除状态,1表示软删除'
) COMMENT='订单信息表';

我们回顾一下之前的gormer程序,它采用了flag参数解析的方式。但随着复杂度提升,命令行参数包含了大量的内容,很难维护。这时,就建议采用配置文件的方式,保证可读性、可维护性。

# 数据库相关的信息
database:
  # 数据库连接
  dsn: "root:123456@tcp(127.0.0.1:3306)/demo"
  # 所有要生成到Go结构体中的表
  tables:
    # name-表名
    # goStruct-Go中结构体名
    # createTime-创建时间的数据库字段,必须为时间格式
    # updateTime-更新时间的数据库字段,必须为时间格式
    # softDeleteKey-软删除的数据库字段,必须为整数型,不填则为硬删除
    # softDeleteValue-表示为软删除的对应值
    - name: "orders"
      goStruct: "Order"
      createTime: "create_time"
      updateTime: "update_time"
      softDeleteKey: "delete_status"
      softDeleteValue: 1

# 项目相关的信息
project:
  # 项目的路径
  base: "./"
  # gorm相关核心结构的代码路径
  gorm: "internal/gormer/"
  # dao层CRUD核心结构的代码路径
  dao: "internal/dao/"
  # 项目的go module信息
  go_mod: "github.com/Junedayday/micro_web_service"

2.模板文件的修改

这里以两个具有代表性的操作为例,一起来看看具体代码。

新增

利用了go template的特性,填充了create_time和update_time字段。这里包含两层if语句:

  • 第一层:在gormer.yaml里必须指定了createTime代码,否则不要生成这段代码
  • 第二层:如果外部传进来的字段里没有指定时间,才填充最新的时间;否则以外部传入为准
daoTmplAdd = `func (repo *{
   {.StructName}}Repo) Add{
   {.StructName}}({
   {.StructSmallCamelName}} *gormer.{
   {.StructName}}) (err error) {
{
   {if ne .FieldCreateTime "" }}
    if {
   {.StructSmallCamelName}}.{
   {.FieldCreateTime}}.IsZero() {
        {
   {.StructSmallCamelName}}.{
   {.FieldCreateTime}} = time.Now()
    }
{
   {end}}
{
   {if ne .FieldUpdateTime "" }}
    if {
   {.StructSmallCamelName}}.{
   {.FieldUpdateTime}}.IsZero() {
        {
   {.StructSmallCamelName}}.{
   {.FieldUpdateTime}} = time.Now()
    }
{
   {end}}
    err = repo.db.
        Table(gormer.{
   {.StructName}}TableName).
        Create({
   {.StructSmallCamelName}}).
        Error
    return
}
`

// 生成后
func (repo *OrderRepo) AddOrder(order *gormer.Order) (err error) {
   

    if order.CreateTime.IsZero() {
   
        order.CreateTime = time.Now()
    }

    if order.UpdateTime.IsZero() {
   
        order.UpdateTime = time.Now()
    }

    err = repo.db.
        Table(gormer.OrderTableName).
        Create(order).
        Error
    return
}

删除

删除的逻辑主要区分了一个字段,即是否在gormer.yaml里指定了软删除的字段。

  • 指定了软删除的字段,则将这个字段更新为设定的值、并且更新updateTime字段;
  • 未指定软删除的字段,则直接硬删除对应的记录;
daoTmplDelete = `func (repo *{
   {.StructName}}Repo) Delete{
   {.StructName}}(condition *gormer.{
   {.StructName}}Options) (err error) {
    if condition == nil {
        return errors.New("delete must include where condition")
    }

    err = repo.db.
        Table(gormer.{
   {.StructName}}TableName).
        Where(condition.{
   {.StructName}}, condition.Fields).
{
   {if eq .FieldSoftDeleteKey "" }} Delete(&gormer.{
   {.StructName}}{}).
{
   { else }}  {
   {if eq .FieldUpdateTime "" }}
                Select("{
   {.TableSoftDeleteKey}}").
                Updates(&gormer.{
   {.StructName}}{
                    {
   {.FieldSoftDeleteKey}}:{
   {.TableSoftDeleteValue}},
                }).
            {
   { else }}
                Select("{
   {.TableSoftDeleteKey}}","{
   {.TableUpdateTime}}").
                Updates(&gormer.{
   {.StructName}}{
                    {
   {.FieldSoftDeleteKey}}:{
   {.TableSoftDeleteValue}},
                    {
   {.FieldUpdateTime}} : time.Now(),
                }).
            {
   { end }}
{
   { end }}
        Error
    return
}
`

// 生成后
func (repo *OrderRepo) DeleteOrder(condition *gormer.OrderOptions) (err error) {
   
    if condition == nil {
   
        return errors.New("delete must include where condition")
    }

    err = repo.db.
        Table(gormer.OrderTableName).
        Where(condition.Order, condition.Fields).
        Select("delete_status", "update_time").
        Updates(&gormer.Order{
   
            DeleteStatus: 1,
            UpdateTime:   time.Now(),
        }).
        Error
    return
}

3.核心结构体梳理

我们再一起看看表结构对应到Go结构体的一个关键结构体,这里分成了4个重要的部分:

  1. 表名、结构体名
  2. 表中的列信息、结构体中的Field字段信息
  3. 创建时间、更新时间
  4. 软删除的字段

这个数据结构体,其实是将两个数据源进行了关联映射:

  • 原始信息:从MySQL表中查询
  • 自定义字段信息:从gormer.yaml查询
type StructLevel struct {
   
    // table -> struct
    TableName            string
    StructName           string
    StructSmallCamelName string

    // table column -> struct field
    Columns []FieldLevel

    // create time
    TableCreateTime string
    FieldCreateTime string
    // update time
    TableUpdateTime string
    FieldUpdateTime string

    // soft delete
    TableSoftDeleteKey   string
    TableSoftDeleteValue int
    FieldSoftDeleteKey   string
}

type FieldLevel struct {
   
    FieldName string
    FieldType string
    // gorm tag for field
    GormName string
    // comment from create table sql
    Comment string
}

4.API调用示例

从API调用的角度来看,程序对外接口如下。有兴趣的可以体验下:

// List
curl --location --request GET 'http://127.0.0.1:8081/v1/orders'

// Create
curl --location --request POST 'http://127.0.0.1:8081/v1/orders' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name": "order4",
    "price": 100.3
}'

// Update
curl --location --request PATCH 'http://127.0.0.1:8081/v1/orders' \
--header 'Content-Type: application/json' \
--data-raw '{
    "order": {
        "id": "1",
        "name": "order1",
        "price": 110.8
    },
    "update_mask": "price"
}'

// Get
curl --location --request GET 'http://127.0.0.1:8081/v1/orders/order1'

// Delete
curl --location --request DELETE 'http://127.0.0.1:8081/v1/orders/order1'

延伸思考

修改到这个版本,gormer工具已经达到了基本可用的阶段。我们回顾一下重点功能:根据数据库表结构,自动化生成dao层的CRUD代码,并扩展了两特性:

  1. 支持创建时间、修改时间的字段,自动填充
  2. 支持软删除与硬删除

从更远的角度来看,还有许多MySQL的特性可以添加,尤其是对事务的支持,有兴趣的可以自行探索。限于篇幅与复杂度,目前就迭代到这个版本。

总结

Gormer是一个我们根据日常CRUD需求自行实现的工具,是框架实现高度自动化的重要环节。它的核心思想是 - 在重复的日常开发过程中找到可自动化的环节,实现Generate Code

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
9天前
|
JavaScript Java Go
探索Go语言在微服务架构中的优势
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出。本文将深入探讨Go语言在构建微服务时的性能优势,包括其在内存管理、网络编程、并发模型以及工具链支持方面的特点。通过对比其他流行语言,我们将揭示Go语言如何成为微服务架构中的一股清流。
|
1月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
100 3
|
6天前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
7天前
|
Go 数据处理 API
Go语言在微服务架构中的应用与优势
本文摘要采用问答形式,以期提供更直接的信息获取方式。 Q1: 为什么选择Go语言进行微服务开发? A1: Go语言的并发模型、简洁的语法和高效的编译速度使其成为微服务架构的理想选择。 Q2: Go语言在微服务架构中有哪些优势? A2: 主要优势包括高性能、高并发处理能力、简洁的代码和强大的标准库。 Q3: 文章将如何展示Go语言在微服务中的应用? A3: 通过对比其他语言和展示Go语言在实际项目中的应用案例,来说明其在微服务架构中的优势。
|
26天前
|
Cloud Native Go API
Go语言在微服务架构中的创新应用与实践
本文深入探讨了Go语言在构建高效、可扩展的微服务架构中的应用。Go语言以其轻量级协程(goroutine)和强大的并发处理能力,成为微服务开发的首选语言之一。通过实际案例分析,本文展示了如何利用Go语言的特性优化微服务的设计与实现,提高系统的响应速度和稳定性。文章还讨论了Go语言在微服务生态中的角色,以及面临的挑战和未来发展趋势。
|
27天前
|
运维 Go 开发者
Go语言在微服务架构中的应用与优势
本文深入探讨了Go语言在构建微服务架构中的独特优势和实际应用。通过分析Go语言的核心特性,如简洁的语法、高效的并发处理能力以及强大的标准库支持,我们揭示了为何Go成为开发高性能微服务的首选语言。文章还详细介绍了Go语言在微服务架构中的几个关键应用场景,包括服务间通信、容器化部署和自动化运维等,旨在为读者提供实用的技术指导和启发。
|
30天前
|
Dubbo Java 应用服务中间件
Dubbo学习圣经:从入门到精通 Dubbo3.0 + SpringCloud Alibaba 微服务基础框架
尼恩团队的15大技术圣经,旨在帮助开发者系统化、体系化地掌握核心技术,提升技术实力,从而在面试和工作中脱颖而出。本文介绍了如何使用Dubbo3.0与Spring Cloud Gateway进行整合,解决传统Dubbo架构缺乏HTTP入口的问题,实现高性能的微服务网关。
|
1月前
|
负载均衡 Go API
探索Go语言在微服务架构中的应用与优势
在这篇技术性文章中,我们将深入探讨Go语言(又称为Golang)在构建微服务架构时的独特优势。文章将通过对比分析Go语言与其他主流编程语言,展示Go在并发处理、性能优化、以及开发效率上的优势。同时,我们将通过一个实际的微服务案例,详细说明如何利用Go语言构建高效、可扩展的微服务系统。
|
1月前
|
安全 Go 云计算
探索Go语言在微服务架构中的应用与优势
在本文中,我们将深入探讨Go语言(又称为Golang)在构建微服务架构中的独特优势。文章将分析Go语言的并发模型、简洁的语法以及高效的编译速度,以及这些特性如何使其成为微服务架构的理想选择。我们将通过一个简单的微服务示例,展示Go语言在实际开发中的表现,并讨论其在性能和可维护性方面的优势。
|
1月前
|
消息中间件 监控 Go
Go语言在微服务架构中的优势与实践
【10月更文挑战第10天】Go语言在微服务架构中的优势与实践