本周,我的工作模式正式从远程办公回到了现场办公。恰逢过年,整体工作节奏放缓,切换的过程很顺畅。
虽然我十分期待远程办公成为常态,但不得不承认,这种模式在中国落地,还有一段路要走。
ORM是一个非常高频使用的开发工具。以下图为例,Go程序内与MySQL中,数据存储是异构的 ,这就导致传统开发方式会分成两步:
SQL
语句这部分的开发有大量重复性的代码,如拼接SQL、数据解析,所以就有了ORM这个概念 - 将内存中的数据结构(对象)与数据库中的表对应起来。一旦映射关系建立,那就可以调用ORM里的CRUD完成日常开发。在Go语言程序中,最常见的就是gorm。
我们以Book作为对象为例,它在Go程序中的定义是:
type Book struct {
Id int64 `gorm:"column:id"`
BookName string `gorm:"column:book_name"`
UpdateTime time.Time `gorm:"column:update_time"`
Status int `gorm:"column:status"`
}
对应MySQL中的建表语句为:
CREATE TABLE `books`
(
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`book_name` varchar(255) NOT NULL DEFAULT '' COMMENT '书名',
`update_time` datetime NOT NULL DEFAULT NOW() COMMENT '更新时间',
`status` tinyint(3) NOT NULL DEFAULT 0 COMMENT '状态',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
CHARSET = utf8mb4
COLLATE = utf8mb4_bin COMMENT '书';
完成定义后,我们可以使用gorm
库实现CRUD了。但基于ORM库,开发中还是会高频出现一些奇怪的问题:明明程序没有bug,ORM的操作结果却没有达到预期。例如插入时status
字段是0,没有报错,但查询时缺变成了100。
这类问题,往往是开发者在设计时没有注重 用户认知 导致的,也就是说 现象反直觉、所见非所得。我们今天的话题,将基于此展开:
ORM往往扩展了很多能力,但大幅度地增加了用户的学习成本与排查问题时的成本。以GORM字段权限控制为例:
type User struct {
Name string `gorm:"<-:create"` // 允许读和创建
Name string `gorm:"<-:update"` // 允许读和更新
Name string `gorm:"<-"` // 允许读和写(创建和更新)
Name string `gorm:"<-:false"` // 允许读,禁止写
Name string `gorm:"->"` // 只读(除非有自定义配置,否则禁止写)
Name string `gorm:"->;<-:create"` // 允许读和写
Name string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读)
Name string `gorm:"-"` // 通过 struct 读写会忽略该字段
Name string `gorm:"-:all"` // 通过 struct 读写、迁移会忽略该字段
Name string `gorm:"-:migration"` // 通过 struct 迁移会忽略该字段
}
看起来特性很酷,但如果你作为读代码的人,你愿意去读一个结构体中每个Field的tag
详情吗?而且,这种限制藏得很隐蔽,发生问题后排查起来很累。
因此,程序侧的ORM定义,最重要的是能保证程序数据结构与数据库存储结构一一映射,其余特性需要慎用。
慎用不代表不用。 如果能在团队内部形成规范,一方面这个规范能落地到代码里,另一方面也能宣传到各个成员、让大家形成共识,那就能用这些特性提升开发效率。
程序侧的代码对开发者可见,排查问题相对清晰。而如果问题最终是在数据库侧导致的,那么就变得复杂了:
所以,我们在前期设计数据库侧的内容时,要尽可能地保证简单。我个人的评判标准是:让Go结构体的数据,和MySQL表中的一行数据完全对应,不做额外的工作。
我举两个反例:
status
的默认值设置为100status
字段修改为某个值后,自动触发另一个字段的修改第三个要点最为复杂,它需要结合ORM库的具体能力以及数据库的自身特性来综合考量:ORM的有些特性并不完善,具体在哪实现?
依旧以gorm为例,在用Book
结构体进行多列更新时,无法更新其中的默认值,如
// 官方示例
// 代码原理:Active字段是默认值false,所以不会更新
// 用户认知:因为惯性思维,往往认为这个值会被设置为false
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
我们先不考虑具体解决方案,而是希望大家能认识到ORM的局限性 - 想用一个结构体完全覆盖所有的增删改查场景,是不现实的。选择方案,其实是trade-off
,选择一个团队更能快速理解的策略。
想了解方案的同学,可以参考我之前的博客。
第三点是进阶性质的能力,需要大量ORM与数据库侧的开发经验,今天不作展开。
ORM的使用体验会大幅提升CRUD的开发与维护效率。我比较提倡 在设计时,最简化ORM与数据库侧的特性,只采用其核心的映射能力。
而当简化到一定程度后,我们可以打通两侧的数据结构,如示例中的Book
结构体与books
建表语句。由于MySQL中的数据类型更为复杂,可以维护一个从 解析建表语句,自动生成Go中ORM结构体 的代码生成工具。
实现可以参考博客
如今的应届生在校或实习时就具备了颇为深厚的编程经验,参加工作后能快速地胜任日常需求,这就引起了老一批工程师的焦虑,不禁怀疑:我们的coding经验究竟有什么价值?
下面,我分享一下个人的思考,会从低到高三个维度进行讲述:
以上三点维度不同,但很难从价值维度区分高低。从这三点来看,一个资深coder对团队的价值非常重要。
这几年,我的焦虑感与日俱增,尤其是近两年的行业低谷。面对焦虑,专家们有很多思路,这里分享三个对我帮助最大的方法:
Github: https://github.com/Junedayday/code_reading Blog: http://junes.tech/ Bilibili: https://space.bilibili.com/293775192 公众号: golangcoding