The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
aababc

业务系统开发语言从 PHP / Python 转向 Go 如何实现数据的存储

  •  
  •   aababc · 1 day ago · 3067 views

    之前主要是使用 PHP/Python 做业务系统开发, 比较习惯 Doctrine, Eloquent, SQLAlchemy 这类 ORM 的模式

    Load Entity
        ↓
    修改对象属性
        ↓
    ORM 检测变更
        ↓
    只 UPDATE 发生变化的字段
    

    比如:

    $order = Order::find(1);
    $order->paid($time);
    $order->save();
    
    $order = $entityManager->find(Order::class, 1);
    $order->paid($time);
    $entityManager->flush();
    

    业务代码只需要表达业务数据变成了什么样, 至于那些字段有变化, 最终那些数据被保存到数据库中, 是由 ORM 的 dirty tracking/Unit of Work 负责.

    但是最近再写 Go 的时候, 感觉主流的方式更偏向于显式更新

    db.Update(ctx, id, map[string]any{
        "paid_at": "xxxx",
        "paid": 1,
    })
    

    或者

    db.Model(&order).Update(ctx, id, Order{
        PaidAt: "xxxx",
        Paid: 1,
    })
    

    简单的场景来说, 还比较清晰, 那么对一个比较复杂的场景来说, 变化的字段比较多的时候, 而且想实现类型安全的时候是不是不太容易.

    目前我使用的几种方案:

    1. 每次都全量更新, 不管更新了几个字段, 每次都全部刷新到数据库里
    2. 业务层面整理好需要更新的字段,通过 map[string]any 方式更新

    我想听听大家的意见, 跟大家学习学习

    Supplement 1  ·  1 day ago

    感谢大家的回复, 我描述一下我们之前的常规的用户, 大部分逻辑都是这样的

    // PayUsecase.php
    readonly class PayUsecase
    {
        public function __construct(
            private UserRepository $orderRepository,
        ) {
        }
    
        public function handle(int $id)
        {
            $order = $this->orderRepository->findByID($id)        
            $order->paid($at);
            $this-orderRepository->save($order)
        }
    }
    
    // Repository.php
    class OrderRepository
    {
        public function findByID(int $id): ?Order
        {
            return Order::find($id)
        }
    
        public function save(Order $order): bool
        {
            return $order->save();
        }
    }
    

    可能迁移到 golang 之后就需要变成, 这样是不是更清晰, 在 Repository 中定义不同行为的方法

    
    // payusecase.go
    type Payusecase struct {
        OrderRepository *OrderRepository
    }
    
    func (p *Payusecase) Pay() {
        order := p.OrderRepository.FindById()
    	
        order.Pay()
        p.OrderRepository.Paid(order)
        
    }
    
    // repository.go
    type SQLExecutor interface {
        ExecContext(context.Context, string, ...any) (sql.Result, error)
        QueryContext(context.Context, string, ...any) (*sql.Rows, error)
        QueryRowContext(context.Context, string, ...any) *sql.Row
    }
    
    type OrderRepository struct{
        db DBTX
    }
    
    func (r *OrderRepository) FindById() (Order, error) {
        // 执行相关的 sql
    }
    
    func (r *OrderRepository) Create() error {
        // 执行相关的 sql
    }
    
    func (r *OrderRepository) Update() error {
        // 执行相关的 sql
    }
    
    func (r *OrderRepository) Paid(order Order) error {
        // 执行相关的 sql, 这里可以从 order 中取出需要更新的字段
    }
    

    还有就是, 我们现在也大量使用了 AI, 但是有时候自己私人的项目还是会手写很大一部分代码!

    45 replies    2026-08-31 15:23:33 +08:00
    maigebaoer
        1
    maigebaoer  
       1 day ago via Android
    golang 也有 gorm 这类 ORM ,只是文档之类的资料不完整,远不如 PHP 和 Python 的 ORM 好用。我这边是用官方 sql 包😅
    NotLongNil
        2
    NotLongNil  
       1 day ago
    go 没有那么重的 orm 库。无论哪种方案,你都必须自己处理好事务。你这第一种方案,不是不行,就是没人这么用
    zengxs
        3
    zengxs  
       1 day ago
    全量更新对数据库压力很大,并发稍高就得寄,除非你们并发是个位数
    liqinliqin
        4
    liqinliqin  
    PRO
       1 day ago
    bitmin
        5
    bitmin  
       1 day ago
    让 agent 写就得了,不用考虑写的麻烦代码量多这种问题,现在还有完全不用 AI 写代码的吗
    caola
        6
    caola  
       1 day ago
    user.LastIp = lastIp

    if err := db.Pgsql().Select("last_ip").Save(&user); err != nil {
    return response.HttpFail(c, "系统错误,请稍后重试")
    }

    Gorm 可以用 Select() 来指定更新其中某些字段
    mooyo
        7
    mooyo  
       1 day ago
    写裸 SQL 更好,反正都是 agent 写了,agent 写的 SQL 比你写得好
    mooyo
        8
    mooyo  
       1 day ago
    然后自己稍微封装一些常用的 CRUD SQL 层操作
    loading
        9
    loading  
       1 day ago via Android
    go ,数据库记得处理好零值,挺烦人的。
    singer
        10
    singer  
    PRO
       1 day ago
    https://gorm.io/zh_CN/

    golang 用这个简单一点
    kran
        11
    kran  
       1 day ago via iPhone
    https://github.com/kran/dba

    按照自己的经验封装了这个库,除了全量和 map ,还可以定义小结构体来做特定字段的更新
    SethShi
        12
    SethShi  
       1 day ago
    别纠结了, 用 gorm gen, 生成范式, 然后 AI 夸夸写
    yougg
        13
    yougg  
       1 day ago via Android
    让 AI 帮你写 raw SQL
    然后 sqlc 转 go 代码
    panlatent
        14
    panlatent  
       1 day ago via Android
    迁移的话,是不是可以先选一个类似的 orm ,然后再去 orm
    lmmlwen
        15
    lmmlwen  
       1 day ago
    这种 curd 不就是 AI 最擅长的
    aababc
        16
    aababc  
    OP
       22h 58m ago
    @liqinliqin #4
    我们原来的技术栈都是 laravel/symfony 基于 FPM 优先的 PHP 技术栈, typephp 刚发布没多久稳定性啥的还没有经过验证, 而且和现有生态的融合咋样好像也说不清楚, 至少在我来看短时间不会选择! 感觉 trueasync 进入了 PHP 内核可能更有利好吧! 不过 swoole 团队的能力确实强, 能做这么多东西出来确实也是对 PHP 的利好!
    liqinliqin
        17
    liqinliqin  
    PRO
       22h 7m ago
    @aababc 没问题的,既然用过 swoole,就花 3 分钟试下 typephp
    jowan
        18
    jowan  
       5h 40m ago
    你这是刻意在把 Doctrine/Eloquent 那套 Entity 的思维迁移到 Go
    Go 的优势跟你的习惯相反 不强迫你把 DB 和领域实体、持久化生命周期绑在一起
    Go 设计思路应该更多地在表达业务,而你更多思维是在表达 ORM
    这会更接近 Go 业务系统里比较舒服的设计 要把编程思维转换过来
    chaoshui
        19
    chaoshui  
       5h 37m ago
    在前司,我们有一个基于快照的 Entity Change Tracking 包,可以用于生成 gorm 的 update 参数,所以处理起来就很方便。
    在目前的公司,就没有这么一个玩意,所以是默认全量更新需要更新的所有字段。
    qW7bo2FbzbC0
        20
    qW7bo2FbzbC0  
       5h 0m ago
    用 sqlx 就行,全能的 orm 太重了,而且会生成很多奇怪的 sql
    pandamen
        21
    pandamen  
       4h 55m ago
    还是 PHP 的 ORM 好用。
    coderzhangsan
        22
    coderzhangsan  
       4h 46m ago
    合适的工具做合适的事,好端端业务项目为何要迁 go, 闲的蛋疼吗?
    ryan961
        23
    ryan961  
       4h 43m ago
    都用 go 了就别再整 php 的 mvc 那一套了......
    btw, ent ( https://entgo.io/zh/docs/getting-started )可能会适合你
    8355
        24
    8355  
       4h 30m ago
    转 go 的目的是啥? 解决性能问题?
    aababc
        25
    aababc  
    OP
       4h 24m ago
    @jowan #18
    确实不绑定在一起, 但问题是我需要把相关的数据持久化的时候这一步少不了吧?
    aababc
        26
    aababc  
    OP
       4h 22m ago
    @8355 #24
    我能说随大流吗, 就是某些项目用了之后感觉还行就越用越多, 而且部署什么的也比较方便, 100M 内存就能跑起来一个业务.
    aababc
        27
    aababc  
    OP
       4h 20m ago
    @ryan961 #23
    说实在的之前看过这个, 感觉太复杂了. 而且这个和 MVC 好像也没啥关系, 不是 PHP 就要使用 MVC 吧.
    jowan
        28
    jowan  
       3h 19m ago
    @aababc 持久化当然少不了,但业务变更并不等于必须使用 Dirty Tracking
    正常的持久化方式本来就是修改什么字段就更新什么字段
    你需要的是 Dirty Tracking 能力 用 Gorm 也可以实现
    另外从你的代码中能看出 你的职责边界设计比较混乱
    order.Pay()已经完成了业务行为,repo 的 Paid(order)实际上又在表达一次
    你的 repo 耦合度非常高 Paid 接受一个完整的 Order 然后自己去判断需要的字段
    你这不又把领域状态和持久化逻辑耦合在一起了吗?
    repo 更合理的职责应该是负责把要持久化的数据存取出来
    而不是重新解释业务 Usecase 也有重复的表达
    使用 Go 来开发项目都有默认的潜规则 就像共有方法前缀一样
    Go 经常在不同层故意使用相似的 struct ,是为了解耦
    推荐多看看 Go 的项目最佳实践
    masterclock
        29
    masterclock  
       3h 18m ago
    entgo 是最“正确”的 go orm
    但是 ai 时代,真的还需要这个吗?
    aababc
        30
    aababc  
    OP
       3h 14m ago
    @jowan #28
    正常的持久化方式本来就是修改什么字段就更新什么字段
    那如何实现这个?
    jowan
        31
    jowan  
       3h 1m ago
    如果业务已经明确知道哪些字段发生变化,直接通过 Repo/ORM 的 Updates 、Select 更新对应字段就行,不需要 Dirty Tracking ,让业务层明确告诉 Repo 要更新什么:比如 repo.MarkPaid(ctx, id, paidAt)
    aababc
        32
    aababc  
    OP
       2h 53m ago
    @jowan #31
    那是不是还可以更通用一点
    ```go
    Update(context.Context, int, map[string]any)
    ```
    jowan
        33
    jowan  
       2h 30m ago
    @aababc 当然可以 但是 Go 强调的显式、类型安全、清晰
    你这样做 本质上又是在自己造一个弱类型的 ORM 更新接口
    做法不是错误的 但是不符合最佳实践 失去类型安全
    很容易变成万能的 Repo 从你发帖到回复 你一直都在往这个方向靠齐
    说明你受原来的开发思维影响太严重了
    jowan
        34
    jowan  
       2h 20m ago
    这么跟你解释 你可能清晰一点 不是每个更新组合都要去写个方法
    那就是为了抽象而抽象 简单的 repo 可以这样
    如果特别复杂,更新组合比较多那 repo 方法也会爆表
    当然也不符合 Go 的项目开发最佳实践
    这时候,你甚至可以定义个 update 结构体,然后去传想要的字段调用它

    type OrderUpdate struct {
    Status *Status
    Paid *bool
    PaidAt *time.Time
    Amount *int64
    Remark *string
    }

    repo.Update(ctx, id, OrderUpdate{
    Paid: ptr(true),
    PaidAt: ptr(at),
    })

    这样做的话类型安全 只更新想要的目标字段
    但零值需要谨慎考虑 终归到底你需要的是一个类型安全的 DTO
    没有谁对谁错 但既然从 PHP 转到 Go 我觉得也应该遵循这个生态的实践经验
    要不然 直接用 Hyperf 不好嘛
    无缝对接 Eloquent 以前怎么爽写现在就怎么爽写
    aababc
        35
    aababc  
    OP
       2h 10m ago
    @jowan #34
    我能说我们现在就是这么干的吗, 但是这个过程我能说非常脏吗, 从我的观点出发, 认为比较易用的就是, 我在业务中操作业务对象, 业务结束之后, 如果需要持久化, 那么我不需要关注那些字段变化, 这应该是一个无感知的过程.
    8355
        36
    8355  
       1h 44m ago
    @aababc #26 那其实就是收益并没有那么大,也没有明确的要求和目的,应该就是硬找点活吧.
    如果是为降本,机器数量并不大的情况下转换这个过程其实是亏的,人员薪资+ai 开销完全覆盖不了未来 1 年的降本收益,还可能带来额外的测试成本和潜在 bug.对于开发来说能多一个技术栈肯定是好事,如果是对负责人来说其实风险是更大的.
    nettest
        37
    nettest  
       1h 42m ago
    问 v2 不如问 ai
    han3sui
        38
    han3sui  
       1h 40m ago
    golang 用 orm ,零值更新是需要一个好好考虑设计的地方
    WaldenHorizon
        39
    WaldenHorizon  
       1h 31m ago
    好久没手写代码,也没认真看过这种细节技术了
    我的建议是给业务处理定义一个单独的返回结构体+err ,而不是直接修改入参的 order
    下一步你想要保存这些变动,就用 gconv 包把返回结构体转成 map[string]any 然后 db update
    jowan
        40
    jowan  
       1h 29m ago
    @aababc 你喜欢改完实体,最后统一 Save ,
    再让 ORM 自动追踪哪些字段变了 你配合 Gorm 一样可以做到
    我要表达的是 业务层说清楚要干什么,Repo 负责怎么落库
    不一定要让 ORM 接管整个 Entity 的生命周期
    vcbal
        41
    vcbal  
       1h 27m ago
    说实话 上面说问 AI 的,有一个算一个 全 block 了,难得在 v2 再次看到技术讨论 而不是嚷嚷着又重置了的帖子
    liuliuliuliu
        42
    liuliuliuliu  
       1h 26m ago
    我个人还是更喜欢用 ORM ,屏弃底层的 Sql 细节,这和 ai 时代不冲突啊,用 ai 写 ORM 代码其实更清晰,维护性更好
    aababc
        43
    aababc  
    OP
       1h 21m ago
    @jowan #40
    是的, 你说的这个没有问题, 但是想实现这个能力在 Go 还是挺麻烦的, 就是使用 GORM 也感觉做起来比较吃力, 我尝试过在 GORM 做这个, 但是说实在的感觉不咋滴, 也有可能是我的路子没走对
    aababc
        44
    aababc  
    OP
       1h 19m ago
    @liuliuliuliu #42
    不同语言的社区对 ORM 的态度是截然不同的, Go 对 ORM 的反对声量特别高, 而且说实在的 Go 现在社区的 ORM 都不咋地, 但是我自己也搞不出来更好用的.
    liuliuliuliu
        45
    liuliuliuliu  
       1h 10m ago
    @aababc #44 go 语言的表达力低下,也受限于语言特性,可能写不出太好用的 orm ,要说 orm ,自强的还是 .net 的 entity framework
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   5519 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 131ms · UTC 08:34 · PVG 16:34 · LAX 01:34 · JFK 04:34
    ♥ Do have faith in what you're doing.