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

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

  •  
  •   aababc · 1 day ago · 2470 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  ·  23h 22m 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, 但是有时候自己私人的项目还是会手写很大一部分代码!

    30 replies    2026-08-31 13:19:24 +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
       19h 46m ago
    @liqinliqin #4
    我们原来的技术栈都是 laravel/symfony 基于 FPM 优先的 PHP 技术栈, typephp 刚发布没多久稳定性啥的还没有经过验证, 而且和现有生态的融合咋样好像也说不清楚, 至少在我来看短时间不会选择! 感觉 trueasync 进入了 PHP 内核可能更有利好吧! 不过 swoole 团队的能力确实强, 能做这么多东西出来确实也是对 PHP 的利好!
    liqinliqin
        17
    liqinliqin  
    PRO
       18h 55m ago
    @aababc 没问题的,既然用过 swoole,就花 3 分钟试下 typephp
    jowan
        18
    jowan  
       2h 28m ago
    你这是刻意在把 Doctrine/Eloquent 那套 Entity 的思维迁移到 Go
    Go 的优势跟你的习惯相反 不强迫你把 DB 和领域实体、持久化生命周期绑在一起
    Go 设计思路应该更多地在表达业务,而你更多思维是在表达 ORM
    这会更接近 Go 业务系统里比较舒服的设计 要把编程思维转换过来
    chaoshui
        19
    chaoshui  
       2h 25m ago
    在前司,我们有一个基于快照的 Entity Change Tracking 包,可以用于生成 gorm 的 update 参数,所以处理起来就很方便。
    在目前的公司,就没有这么一个玩意,所以是默认全量更新需要更新的所有字段。
    qW7bo2FbzbC0
        20
    qW7bo2FbzbC0  
       1h 48m ago
    用 sqlx 就行,全能的 orm 太重了,而且会生成很多奇怪的 sql
    pandamen
        21
    pandamen  
       1h 43m ago
    还是 PHP 的 ORM 好用。
    coderzhangsan
        22
    coderzhangsan  
       1h 34m ago
    合适的工具做合适的事,好端端业务项目为何要迁 go, 闲的蛋疼吗?
    ryan961
        23
    ryan961  
       1h 31m ago
    都用 go 了就别再整 php 的 mvc 那一套了......
    btw, ent ( https://entgo.io/zh/docs/getting-started )可能会适合你
    8355
        24
    8355  
       1h 18m ago
    转 go 的目的是啥? 解决性能问题?
    aababc
        25
    aababc  
    OP
       1h 12m ago
    @jowan #18
    确实不绑定在一起, 但问题是我需要把相关的数据持久化的时候这一步少不了吧?
    aababc
        26
    aababc  
    OP
       1h 10m ago
    @8355 #24
    我能说随大流吗, 就是某些项目用了之后感觉还行就越用越多, 而且部署什么的也比较方便, 100M 内存就能跑起来一个业务.
    aababc
        27
    aababc  
    OP
       1h 8m ago
    @ryan961 #23
    说实在的之前看过这个, 感觉太复杂了. 而且这个和 MVC 好像也没啥关系, 不是 PHP 就要使用 MVC 吧.
    jowan
        28
    jowan  
       7 mins ago
    @aababc 持久化当然少不了,但业务变更并不等于必须使用 Dirty Tracking
    正常的持久化方式本来就是修改什么字段就更新什么字段
    你需要的是 Dirty Tracking 能力 用 Gorm 也可以实现
    另外从你的代码中能看出 你的职责边界设计比较混乱
    order.Pay()已经完成了业务行为,repo 的 Paid(order)实际上又在表达一次
    你的 repo 耦合度非常高 Paid 接受一个完整的 Order 然后自己去判断需要的字段
    你这不又把领域状态和持久化逻辑耦合在一起了吗?
    repo 更合理的职责应该是负责把要持久化的数据存取出来
    而不是重新解释业务 Usecase 也有重复的表达
    使用 Go 来开发项目都有默认的潜规则 就像共有方法前缀一样
    Go 经常在不同层故意使用相似的 struct ,是为了解耦
    推荐多看看 Go 的项目最佳实践
    masterclock
        29
    masterclock  
       6 mins ago
    entgo 是最“正确”的 go orm
    但是 ai 时代,真的还需要这个吗?
    aababc
        30
    aababc  
    OP
       2 mins ago
    @jowan #28
    正常的持久化方式本来就是修改什么字段就更新什么字段
    那如何实现这个?
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   4012 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 80ms · UTC 05:22 · PVG 13:22 · LAX 22:22 · JFK 01:22
    ♥ Do have faith in what you're doing.