Golang ORM类包Grimoire:基于变更集的实现

Golang ORM类包Grimoire:基于变更集的实现 大家好!

这个项目最初源于我在寻找一个能够识别结构体字段是否应该更新或忽略的替代ORM。这个问题特别出现在当结构体来自JSON,且只有部分字段需要更新时。一种解决方法是忽略所有零值,但这并不是很优雅的解决方案,因为我们将失去将值设置为零的能力。

这是什么?
Grimoire是一个受Ecto启发的数据库访问层。它具有灵活的查询API和内置验证功能。目前支持MySQL、PostgreSQL和SQLite3,但可以使用适配器接口轻松实现自定义适配器。

动机
常见的Go ORM接受结构体作为修改记录的值,这带来了无法区分空值、nil或未定义值的问题。当您需要支持部分更新的端点时,这尤其棘手。Grimoire通过集成受Elixir的Ecto启发的变更集系统来解决这个问题。变更集是一种类似表单的实体,它不仅帮助我们解决这个问题,还协助我们进行类型转换、验证和约束检查。

GitHub Fs02/grimoire

Database access layer for golang. Contribute to Fs02/grimoire development by creating an account on GitHub.

很希望能听到关于这个项目的反馈,谢谢!


更多关于Golang ORM类包Grimoire:基于变更集的实现的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang ORM类包Grimoire:基于变更集的实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Grimoire的变更集实现确实解决了Go中ORM在处理部分更新时的痛点。以下是一个使用变更集进行部分更新的示例:

package main

import (
    "github.com/Fs02/grimoire"
    "github.com/Fs02/grimoire/changeset"
)

type User struct {
    ID    int
    Name  string
    Email string
    Age   int
}

func main() {
    // 初始化连接
    db, _ := grimoire.Open("postgres", "user=postgres dbname=test sslmode=disable")
    
    // 从JSON解析的数据(模拟部分字段)
    jsonData := map[string]interface{}{
        "name": "John Doe",
        "age":  30,
        // email字段未提供,应该保持原值
    }
    
    // 创建变更集
    ch := changeset.Cast(User{}, jsonData, []string{"name", "email", "age"})
    
    // 添加验证规则
    changeset.ValidateRequired(ch, []string{"name"})
    changeset.ValidateLength(ch, "name", changeset.Length{Min: 2, Max: 100})
    
    // 检查变更集是否有效
    if changeset.Valid?(ch) {
        // 只更新变更的字段
        db.Where("id = ?", 1).UpdateChanges(ch)
    }
    
    // 也可以显式设置零值
    zeroUpdate := map[string]interface{}{
        "age": 0, // 明确设置age为0
    }
    
    zeroCh := changeset.Cast(User{}, zeroUpdate, []string{"age"})
    db.Where("id = ?", 2).UpdateChanges(zeroCh)
}

变更集的关键优势在于能够精确控制哪些字段应该被更新。changeset.Cast的第三个参数明确指定了允许更新的字段白名单,未包含在白名单中的字段即使存在于输入数据中也会被忽略。

对于验证,Grimoire提供了丰富的内置验证器:

// 更复杂的验证示例
func validateUser(ch *changeset.Changeset) {
    changeset.ValidateRequired(ch, []string{"name", "email"})
    changeset.ValidateFormat(ch, "email", `^[^\s@]+@[^\s@]+\.[^\s@]+$`)
    changeset.ValidateNumber(ch, "age", changeset.Number{Min: 0, Max: 150})
    changeset.ValidateInclusion(ch, "status", []interface{}{"active", "inactive", "pending"})
}

在处理数据库约束时,变更集还能与数据库约束集成:

// 处理唯一约束
user := User{Email: "existing@email.com"}
ch := changeset.Cast(user, map[string]interface{}{}, []string{"email"})

// 检查唯一性约束
if err := db.CheckUnique(ch, "email"); err != nil {
    changeset.AddError(ch, "email", "already taken")
}

这种基于变更集的方法确实比传统的结构体更新更加灵活,特别是在处理部分更新和明确设置零值的场景下。

回到顶部