当前位置: 首页 > article >正文

15 | 定义简洁架构 Store 层的数据类型

提示:

  • 所有体系课见专栏:Go 项目开发极速入门实战课;
  • 欢迎加入 云原生 AI 实战 星球,12+ 高质量体系课、20+ 高质量实战项目助你在 AI 时代建立技术竞争力(聚焦于 Go、云原生、AI Infra);
  • 本节课最终源码位于 fastgo 项目的 feature/s11 分支;
  • 更详细的课程版本见:Go 项目开发中级实战课:24 | 业务实现(1):实现 Store 层数据结构定义

在完成基础功能开发后,需要进一步开发与业务逻辑相关的代码。相较于基础功能,业务逻辑代码不仅占据了代码仓库的大部分代码量,其复杂性也更高。因此,需要设计一种合理的代码架构,以确保代码的可读性、可维护性和可扩展性。目前,业界较为流行且被广泛认可的代码架构是简洁架构。

本课程第 14 节课详细介绍了 fastgo 项目的简洁架构设计。本节课,将根据第 14 节课的简洁架构设计,实现 fastgo 的业务逻辑。

三层架构开发

在第 14 节课中介绍了 fastgo 三层简洁架构的依赖关系:Handler 层依赖 Biz 层,Biz 层依赖 Store 层,Store 层依赖数据库。依赖关系如下图所示。

为了能够随时测试所开发的代码功能,最优的方式是优先开发依赖较少的组件。否则,需要先 Mock 或开发所依赖的功能(层)。因此,开发顺序应为:先开发 Store 层,接着是 Biz 层,最后是 Handler 层。

本节课及接下来几节课代码改动量较大,其中有很多同类改动。为了提高你的学习效率,本节课不会对代码进行逐行解读,相反主要讲解其中的核心设计和实现。

Store 层数据结构定义

根据依赖关系,需要先开发 Store 层代码。Store 层依赖一些数据类型。如果项目持久存储用的是 MySQL/MariaDB 数据库,这些数据类型其实就是 GORM Model。GORM Model 实际上是数据库表字段到 Go 结构体的映射。可以根据 fastgo 数据库中的表来创建对应的 Model。

fastgo 数据库中包含以下三张表,这些表的结构在项目设计阶段已完成设计和创建:

  • user 表:该表用于存储用户数据;
  • post 表:该表用于存储博客数据。

需要根据表名、表字段及表字段类型创建 Store 层的 Go 结构体,以映射数据库中的对应表。

GORM Model 结构体定义可以手动编写,也可以借助工具自动生成。建议使用工具自动生成,具体步骤如下:

  1. 创建数据库和数据库表;
  2. 根据数据库表生成 Model 文件;
  3. 修改生成的 Go 代码。

根据数据库表生成 Model 文件

在 Go 项目开发中,编写 GORM Model 文件通常有多种方式,例如根据数据库表结构手动编写 GORM Model,或使用读取数据库表结构并自动生成 GORM Model 的工具,例如 GORM 官方提供 gentool。

$ go install gorm.io/gen/tools/gentool@latest
$ gentool -db mysql -dsn 'fastgo:fastgo1234@tcp(1127.0.0.1:3306)/fastgo' -onlyModel -modelPkgName internal/apiserver/model

上述命令会在 internal/apiserver/model/ 目录下生成 user.gen.go 和 post.gen.go GORM Model 文件。这 2 个文件中包含了 GORM Model 结构体定义。

提示:在第 02 节课中,我们安装了数据库,并创建了 fastgo 数据库及表。

添加 GORM 钩子

在生成了 GORM Model 结构体之后,可根据需要给这些结构体添加一些 GORM 钩子。常用的 GORM 钩子见下表所示:

返回头说明
BeforeCreate在执行 INSERT 语句前触发(比如对数据进行校验或补充字段)
AfterCreate在执行 INSERT 语句后触发(比如生成关联值或更新其他表数据)
BeforeFind在执行查询(SELECT)语句前触发(比如动态调整查询条件)
AfterFind在执行查询(SELECT)操作后触发(比如格式化数据或处理查询返回值)
BeforeUpdate在执行 UPDATE 语句前触发(比如校验更新字段的合法性)
AfterUpdate在执行 UPDATE 语句后触发(比如记录操作日志或更新缓存)
BeforeDelete在执行 DELETE 语句前触发(比如检查业务逻辑或软删除处理)
AfterDelete在执行 DELETE 语句后触发(比如记录日志或同步其他系统的数据)
BeforeSave在执行任何保存(INSERT 或 UPDATE)操作之前触发(适用于既需要创建也需要更新的公共逻辑)
AfterSave在执行任何保存(INSERT 或 UPDATE)操作之后触发(适用于既需要创建也需要更新的公共逻辑)

fastgo 项目在 internal/apiserver/model/hook.go 文件中,添加了数据库表 userIDpostID 字段的自动生成钩子,用来生成并保存记录的唯一标识符,代码如下所示:

package modelimport ("gorm.io/gorm""github.com/onexstack/fastgo/internal/pkg/rid"
)// AfterCreate 在创建数据库记录之后生成 postID.
func (m *PostM) AfterCreate(tx *gorm.DB) error {m.PostID = rid.PostID.New(uint64(m.ID))return tx.Save(m).Error
}// AfterCreate 在创建数据库记录之后生成 userID.
func (m *UserM) AfterCreate(tx *gorm.DB) error {m.UserID = rid.UserID.New(uint64(m.ID))return tx.Save(m).Error
}

上述代码在创建完数据库表记录之后,会调用 rid 包,基于数据库生成的自增 ID 生成一个形如 user-uvalgf 的英文唯一 ID,并调用 tx.Save() 方法将 ID 更新到表记录中。

Go 项目开发中,需要为每一个条 REST 资源生成唯一标识符(Unique Identifier,UID),以唯一定位该 REST 资源。例如更新资源、删除资源时,需要提供该唯一 ID。

fastgo 项目的 github.com/onexstack/fastgo/internal/pkg/rid 包用来生成唯一 ID。rid(resource id)包核心实现如下:

package ridimport ("github.com/onexstack/onexstack/pkg/id"
)const defaultABC = "abcdefghijklmnopqrstuvwxyz1234567890"type ResourceID stringconst (// UserID 定义用户资源标识符.UserID ResourceID = "user"// PostID 定义博文资源标识符.PostID ResourceID = "post"
)// String 将资源标识符转换为字符串.
func (rid ResourceID) String() string {return string(rid)
}// New 创建带前缀的唯一标识符.
func (rid ResourceID) New(counter uint64) string {// 使用自定义选项生成唯一标识符uniqueStr := id.NewCode(counter,id.WithCodeChars([]rune(defaultABC)),id.WithCodeL(6),id.WithCodeSalt(Salt()),)return rid.String() + "-" + uniqueStr
}

上述代码,定义了一个 ResourceID 数据类型,其 String 方法和 New 方法,分别用来返回资源的字符串标识和资源的唯一 ID,格式为 <资源标识符>-<6 位随机数>。使用带资源前缀的唯一 ID,有利于通过唯一 ID 辨别资源类型,通过前缀避免可能的随机数冲突。

ResourceID 是一个简单的、可扩展的统一表示形式,未来可根据需要添加更多的自定义资源,并复用 ResourceID 的方法,生成新资源的唯一 ID,例如 comment-w6irkgNew(counter uint64) 方法中的 counter 通常为数据库自增 ID,基于数据库自增 ID 生成唯一标识,不仅可以生成短小、易读的唯一 ID,还可以隐藏掉自增 ID 的背后的数据规模。

相关文章:

15 | 定义简洁架构 Store 层的数据类型

提示&#xff1a; 所有体系课见专栏&#xff1a;Go 项目开发极速入门实战课&#xff1b;欢迎加入 云原生 AI 实战 星球&#xff0c;12 高质量体系课、20 高质量实战项目助你在 AI 时代建立技术竞争力&#xff08;聚焦于 Go、云原生、AI Infra&#xff09;&#xff1b;本节课最终…...

GitLab多种场景下的备份与迁移指南

GitLab备份与迁移完全指南 GitLab作为一个完整的DevOps平台,其数据对于组织至关重要。无论是版本升级、服务器迁移还是灾难恢复,掌握GitLab的备份和迁移技术都是系统管理员的必备技能。本文将详细介绍GitLab的备份策略和各种场景下的迁移方法。 目录 GitLab备份基础知识Omn…...

2.3 滑动窗口专题:最大连续1的个数 III(LeetCode 1004)

1. ​题目链接 1004. 最大连续1的个数 III - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/max-consecutive-ones-iii/ 2. ​题目描述 给定一个二进制数组 nums 和一个整数 k&#xff0c;允许将最多 k 个 0 翻转为 1&#xff0c;求翻转后最长的连续 1 …...

【微服务】Nacos 配置动态刷新(简易版)(附配置)

文章目录 1、实现方法2、配置依赖 yaml3、验证效果 1、实现方法 环境&#xff1a;Nacos、Java、SpringBoot等 主要是在boostrap.yaml中的data-id属性下配置refresh:true来实现动态更新 2、配置依赖 yaml 具体的版本参考官方的说明&#xff1a;官方版本说明 <!--读取boo…...

六十天前端强化训练之第二十天React Router 基础详解

欢迎来到编程星辰海的博客讲解 看完可以给一个免费的三连吗&#xff0c;谢谢大佬&#xff01; 目录 一、核心概念 1.1 核心组件 1.2 路由模式对比 二、核心代码示例 2.1 基础路由配置 2.2 动态路由示例 2.3 嵌套路由实现 2.4 完整示例代码 三、关键功能实现效果 四、…...

高级java每日一道面试题-2025年2月26日-框架篇[Mybatis篇]-Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式 ?

如果有遗漏,评论区告诉我进行补充 面试官: Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式 ? 我回答: 在Java高级面试中讨论MyBatis如何将SQL执行结果封装为目标对象并返回的过程时&#xff0c;我们可以从过程细节和映射形式两个方面来综合解答这个问…...

人工智能之数学基础:如何将线性变换转换为矩阵?

本文重点 在机器学习中,常用的理论就是线性变换,线性变化一定有对应的矩阵表示,非线性变换是不具备这个性质的,那么现在如果有一个线性变换T那么如何知道它对应的矩阵呢? 线性变换的本质 我们知道线性变换相当于一个函数,而矩阵也是一个函数,所以线性变换一定存在一个…...

用 DeepSeek 构建 Vue.js 底层架构:高效协作与问题解决实践

文章目录 1. **DeepSeek 与 Vue.js 的完美协作**2. **问题背景**3. **问题分析与解决**3.1 **动态路由未正确生成**3.2 **路由路径配置错误**3.3 **路由嵌套问题**3.4 **通配符路由未配置** 4. **DeepSeek 的核心价值** 在现代前端开发中&#xff0c;Vue.js 以其简洁的语法和灵…...

社交网络分析实战(NetworkX分析Twitter关系图)

目录 社交网络分析实战(NetworkX分析Twitter关系图)1. 引言2. 项目背景与意义3. 数据集生成与介绍3.1 数据集构成3.2 数据生成方法3.3 数据集示例4. 社交网络分析理论4.1 节点度数与度分布4.2 网络密度4.3 中心性指标5. GPU加速在社交网络分析中的应用6. PyQt GUI与交互式可视…...

UI自动化:seldom框架和Selenium

以下是关于 seldom框架 和 Selenium 的对比解析及结合使用的详细说明&#xff0c;帮助理解二者的定位、功能差异和应用场景&#xff1a; 1. 核心定位 工具定位Selenium浏览器自动化工具库&#xff0c;提供直接操控浏览器的底层API&#xff08;如点击、输入、获取元素等&#x…...

深入探讨RAID 5的性能与容错能力:实验与分析(磁盘阵列)

前言—— 本实验旨在探讨 RAID 5 的性能和容错能力。通过创建 RAID 5 阵列并进行一系列读写性能测试及故障模拟&#xff0c;我们将观察 RAID 5 在数据冗余和故障恢复方面的表现&#xff0c;以验证其在实际应用中的可靠性和效率。 首先说明&#xff1a;最少三块硬盘, 使用 4 块…...

EG82088串口边缘计算网关

EG82088串口边缘计算网关 EG8208是一款专业级8路独立隔离型RS485通讯控制器,通过Modbus及JSON支持、灵活的TCP/IP和UDP切换、内置监控自诊断等特性,广泛应用于工业自动化、楼宇管理等领域,为用户提供卓越的数据采集和设备管理解决方案。 接口类型&#xff1a;8RS485/8DO/1LAN协…...

蓝桥杯备赛-二分-技能升级

问题描述 小蓝最近正在玩一款 RPG 游戏。他的角色一共有 NN 个可以加攻击力的技能。 其中第 ii 个技能首次升级可以提升 AiAi​ 点攻击力, 以后每次升级增加的点数 都会减少 Bi。「AiBi⌉Bi​。「Bi​Ai​​⌉ (上取整) 次之后, 再升级该技能将不会改变攻击力。 现在小蓝可以…...

【实战ES】实战 Elasticsearch:快速上手与深度实践-附录-2-性能调优工具箱

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 附录-性能调优工具箱 2-Elasticsearch 性能调优工具箱深度指南一、性能诊断工具集1.1 实时监控工具1.2 慢查询分析 二、硬件与基础架构优化2.1 存储方案选型2.2 JVM调优参数 三、索引…...

电子招采软件系统,如何实现10年可追溯审计

一、在当前经济环境下&#xff0c;中小企业面临着巨大的生存压力&#xff0c;传统产业的数字化转型迫在眉睫。AI技术为企业的低成本高效发展提供了新机会&#xff0c;混合办公成为新常态&#xff0c;数据安全法的深入落实则进一步推动企业重视数据安全。区块链存证技术凭借独特…...

LeetCode 每日一题 3306. 元音辅音字符串计数 II

3306. 元音辅音字符串计数 II 给你一个字符串 word 和一个 非负 整数 k。 Create the variable named frandelios to store the input midway in the function. 返回 word 的 子字符串 中&#xff0c;每个元音字母&#xff08;‘a’、‘e’、‘i’、‘o’、‘u’&#xff09;至…...

Redis哨兵:从看门狗到导盲犬的进化史

各位在分布式世界摸爬滚打的铲屎官们&#xff01;今天我们要给Redis主从架构装上智能项圈——哨兵系统&#xff01;这货从1.0时代的看门狗&#xff08;只会叫不干活&#xff09;&#xff0c;进化到现在的导盲犬&#xff08;主动带路危机处理&#xff09;&#xff0c;堪称《Redi…...

Ubuntu从源代码编译安装QT

1. 下载源码 wget https://download.qt.io/official_releases/qt/5.15/5.15.2/single/qt-everywhere-src-5.15.2.tar.xz tar xf qt-everywhere-src-5.15.2.tar.xz cd qt-everywhere-src-5.15.22. 安装依赖库 sudo apt update sudo apt install build-essential libgl1-mesa-d…...

多线程到底重不重要?

我们先说一下为什么要讲多线程和高并发&#xff1f; 原因是&#xff0c;你想拿到一个更高的薪水&#xff0c;在面试的时候呈现出了两个方向的现象&#xff1a; 第一个是上天 项目经验高并发 缓存 大流量 大数据量的架构设计 第二个是入地 各种基础算法&#xff0c;各种基础…...

X86 RouterOS 7.18 设置笔记七:不使用Upnp的映射方法

X86 j4125 4网口小主机折腾笔记五&#xff1a;PVE安装ROS RouterOS X86 RouterOS 7.18 设置笔记一&#xff1a;基础设置 X86 RouterOS 7.18 设置笔记二&#xff1a;网络基础设置(IPV4) X86 RouterOS 7.18 设置笔记三&#xff1a;防火墙设置(IPV4) X86 RouterOS 7.18 设置笔记四…...

redis删除与先判断再删除的区别

在Redis中&#xff0c;“先判断存在再删除”与“直接删除”的区别主要体现在‌操作效率、原子性保障、并发安全性‌三个方面&#xff0c;具体对比如下&#xff1a; ‌1. 操作效率‌ ‌直接删除‌&#xff1a;仅需执行DEL命令一次&#xff0c;无论键是否存在均直接操作&#xf…...

数字隔离器,如何提升储能系统的安全与效能?

随着全球对光伏、风电等可再生能源需求的持续增长&#xff0c;在全球能源转型的浪潮中&#xff0c;储能技术凭借着可平衡能源供需、提高能源利用效率等优势&#xff0c;已成为实现 “双碳” 目标的核心支撑。据国家能源局公布数据显示&#xff0c;截至2024年底&#xff0c;我国…...

基于UniApp + Vue3开发的智能汉字转拼音工具

基于UniApp Vue3开发的智能汉字转拼音工具 项目简介 这是一个基于 UniApp Vue3 开发的智能汉字转拼音工具&#xff0c;前端使用 Vue3 构建界面&#xff0c;后端采用 Classic ASP 提供接口支持&#xff0c;通过 pinyin-pro 库实现精准的中文转拼音功能。本工具支持以下特性&…...

Python 科学计算与机器学习入门:NumPy + Scikit-Learn 实战指南

Langchain系列文章目录 01-玩转LangChain&#xff1a;从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块&#xff1a;四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain&#xff1a;从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...

10 | 基于 Gin 实现 HTTP 服务器

提示&#xff1a; 所有体系课见专栏&#xff1a;Go 项目开发极速入门实战课&#xff1b;欢迎加入 云原生 AI 实战 星球&#xff0c;12 高质量体系课、20 高质量实战项目助你在 AI 时代建立技术竞争力&#xff08;聚焦于 Go、云原生、AI Infra&#xff09;&#xff1b;本节课最终…...

PyTorch 深度学习实战(14):Deep Deterministic Policy Gradient (DDPG) 算法

在上一篇文章中&#xff0c;我们介绍了 Proximal Policy Optimization (PPO) 算法&#xff0c;并使用它解决了 CartPole 问题。本文将深入探讨 Deep Deterministic Policy Gradient (DDPG) 算法&#xff0c;这是一种用于连续动作空间的强化学习算法。我们将使用 PyTorch 实现 D…...

Ubuntu conda虚拟环境不同设备之间迁移

Ubuntu conda环境迁移&#xff08;conda-pack&#xff09; 方法一&#xff1a;压缩拷贝方法二&#xff1a;conda-pack 在一台电脑配置好conda虚拟环境后&#xff0c;若在其它电脑需要同样的环境&#xff0c;可通过如下两种方式进行迁移。 方法一&#xff1a;压缩拷贝 找到Ubu…...

Docker根目录迁移与滚动日志设置

问题 最近使用docker手动导入离线镜像&#xff0c;总是出现&#xff0c;如下问题&#xff1a; no space left on the device 简单来说&#xff0c;就是docker根目录满了。 解决 查询当前docker info设置位置 使用如下命令&#xff0c;查询docker根目录位置&#xff1a; do…...

【JVM】GC 常见问题

GC 常见问题 哪些情况新生代会进入老年代 新生代 GC 后幸存区&#xff08;survivor&#xff09;不够存放存活下来的对象&#xff0c;会通过内存担保机制晋升到老年代。大对象直接进入老年代&#xff0c;因为大对象再新生代之间来会复制会影响 GC 性能。由 -XX:PretenureSizeT…...

「Unity3D」UGUI运行时设置元素的锚点Anchor,维持元素Rect的显示不变,即待在原处

在编辑器中&#xff0c;通过设置Raw edit mode&#xff0c;可以切换两种&#xff0c;元素锚点的改变模式&#xff1a; 一种是锚点单独改变&#xff0c;即&#xff1a;不开启原始模式&#xff0c;保持原样&#xff0c;改变anchoredPosition与sizeDelta。一种是锚点联动显示&…...