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 的背后的数据规模。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/72017.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSDN统计个人创作总字数

前言 不是很懂爬虫&#xff0c;所以就叫deepseek写了一个 用起来很简单&#xff0c;但是有一个小问题&#xff0c;就是统计的是总字符数。代码片会被统计进去&#xff0c;Markdown语法也会被统计进去。 不过我没有太多需求&#xff0c;能大概统计一下满足以下小小的好奇心和成…

React.js 基础与进阶教程

React.js 基础与进阶教程 React.js 是由 Facebook 开发的流行前端 JavaScript 库&#xff0c;专为构建用户界面&#xff08;UI&#xff09;设计&#xff0c;尤其适用于单页面应用&#xff08;SPA&#xff09;。它采用组件化开发模式&#xff0c;使 UI 结构更加清晰、可维护性更…

msf(Metasploit)中Session与Channel的区别与关系解析

在 Metasploit Framework&#xff08;MSF&#xff09;中&#xff0c;Session 和 Channel 都是与目标主机的交互方式&#xff0c;但它们的作用和概念有所不同。本文将解析这两个术语的区别。 一、Session&#xff08;会话&#xff09; Session 是指通过 Metasploit 成功利用目标…

设计模式-结构型模式-装饰器模式

概述 装饰器模式 : Decorator Pattern : 是一种结构型设计模式. 作用 &#xff1a; 允许你动态地给对象添加功能或职责&#xff0c;而无需修改其原始类的代码,非常的符合 开闭原则。 实现思路 &#xff1a;通过创建一个包装对象&#xff08;即装饰器&#xff09;&#xff0c;来…

Qt/C++音视频开发82-系统音量值获取和设置/音量大小/静音

一、前言 在音视频开发中&#xff0c;音量的控制分两块&#xff0c;一个是控制播放器本身的音量&#xff0c;绝大部分场景都是需要控制这个&#xff0c;这个不会影响系统音量的设置。还有一种场景是需要控制系统的音量&#xff0c;因为播放器本身的音量是在系统音量的基础上控…

基于深度学习的医学CT图像肺结节智能检测与语音提示系统【python源码+Pyqt5界面+数据集+训练代码】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

前端小食堂 | Day14 - Vue 3 の传送门与悬念

&#x1f300; 今日秘技&#xff1a;Teleport 与 Suspense の时空魔法 1. Teleport 任意门 <template> <!-- &#x1f6aa; 将组件传送到 body 末尾 --> <Teleport to"body"> <div class"modal"> <h2>重要通知&#x…

emacs使用mongosh的方便工具发布

github项目地址: GitHub - csfreebird/emacs_mongosh: 在emacs中使用mongosh快速登录mongodb数据库 * 用途 在emacs中使用mongosh快速登录mongodb数据库&#xff0c; 操作方法: M-x mongosh, 输入数据库名称&#xff0c;然后就可以自动登录&#xff0c;前提是你已经配置好了…

Linux:Ubuntu server 24.02 上搭建 ollama + dify

一、安装Ubuntu 具体的安装过程可以参见此链接&#xff1a;链接&#xff1a;Ubuntu Server 20.04详细安装教程&#xff0c;这里主要记录一下过程中遇到的问题。 安装时subnet如何填写 在Ubuntu中subnet填写255.255.255.0是错误的&#xff0c;其格式为 xx.xx.xx.xx/yy &#…

unordered_set 的常用函数

在 C 的标准库中&#xff0c;std::unordered_set 是基于哈希表实现的哈希集合。下面介绍这种语言里哈希集合的常用函数。 C std::unordered_set 1. 元素操作 insert 功能&#xff1a;向哈希集合中插入元素。如果元素已经存在&#xff0c;则不会重复插入。示例代码&#xff1a…

starrocks批量启停脚本

#!/bin/bash # 定义 StarRocks 安装目录 STARROCKS_HOME"/path/to/starrocks" # 定义 FE 和 BE 节点列表 FE_NODES("fe_node1_ip" "fe_node2_ip" "fe_node3_ip") BE_NODES("be_node1_ip" "be_node2_ip" "be_…

python 提取视频中的音频

在Python中提取视频中的音频&#xff0c;你可以使用moviepy库&#xff0c;这是一个非常强大且易于使用的库&#xff0c;专门用于视频编辑。以下是如何使用moviepy来提取视频中的音频的步骤&#xff1a; 安装moviepy 首先&#xff0c;你需要安装moviepy。你可以通过pip安装它&a…

大语言模型打卡学习DAY1

学习目标&#xff1a; 语言模型的发展历程 大模型的技术基础 学习内容&#xff1a; 1. 语言模型的发展历程 语言模型通常是指能够建模自然语言文本生成概率的模型&#xff0c;从语言建模到任务求解&#xff0c;这是科学思维的一次重要跃升。2. 大语言模型技术基础 定义&#…

boarding_passes(登机牌)表的作用

boarding_passes&#xff08;登机牌&#xff09;表的作用 boarding_passes 这张表的主要作用是记录旅客的登机信息&#xff0c;包括&#xff1a; 票号 (ticket_no) - 关联到 tickets 表&#xff0c;表示这张票属于哪个旅客。航班 ID (flight_id) - 关联到 flights 表&#xf…

Go语言为什么运行比Java快

文章目录 前言一、核心区别二、Go Vs Java1.Go 的启动比 Java 快&#xff1f;2.选 Go Or Java&#xff1f; 总结 前言 Go 和 Java 是两种广泛应用的编程语言&#xff0c;它们在语言特性、性能、生态、应用场景等方面存在显著区别。以下是它们的核心区别&#xff0c;以及在实际…

java生成一个24位的字符串,要求这个字符串由大写的英文字母和数字组成,长度固定位24位

import java.security.SecureRandom;public class RandomStringGenerator {// 定义允许的字符集&#xff08;大写字母和数字&#xff09;private static final String ALLOWED_CHARACTERS "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";private static final SecureRando…

基于python的升级队列加速决策

a-f大等级是3级 a-c建筑每升1级分别需要8天 d-f建筑每升1级分别需要10天 目前以下建筑队列正在从0级升至1级 建筑A升级需要7天05&#xff1a;16&#xff1a;20 建筑b升级需要06&#xff1a;06&#xff1a;54 建筑c升级需要00&#xff1a;37&#xff1a;00 建筑d升级需要…

【经验】Ubuntu|VMware 新建虚拟机后打开 SSH 服务、在主机上安装vscode并连接、配置 git 的 ssh

常常有人问VMware-Tools装了也复制粘贴不了怎么办&#xff0c;这个东西影响因素太多了&#xff0c;我总是建议直接用SSH连接虚拟机。但是之前一直都没有出教程&#xff0c;现在出一个简单的教程。 文章目录 在 Ubuntu 虚拟机&#xff08;VMware&#xff09;中开启 SSH 服务、配…

C++多线程编程 3.互斥量、互斥锁

目录 1. 线程安全与互斥锁&#xff08;std::mutex&#xff09; 2. 互斥量死锁 3. std::lock_guard 4. std::unique_lock (1)示例 (2)详细知识点 5. std::this_thread (1)sleep_for (2)sleep_until (3)yield (4)get_id 直接通过示例讲解&#xff1a; 1. 线程安全与互…

【redis】hash基本命令和内部编码

文章目录 表示形式命令HSET 和 HGET HEXISTSHDELHKEYSHVALSHGETALLHMGETHLENHSETNXHINCRBYHINCRBYFLOAT命令小结内部编码 表示形式 Redis 自身已经是键值对结构了 Redis 自身的键值对就是通过哈希的方式来组织的 把 key 这一层组织完成之后&#xff0c;到了 value 这一层&…