Golang中如何合理管理CRUD操作

Golang中如何合理管理CRUD操作 我不理解为什么像 Rails API 模式或 Django REST 这样的框架在 Go 语言中不是一个流行的选择。我能理解这样的框架可能不会被每个项目使用,但任何现实的 CRUD 应用都会有几十个实体,需要 JSON 解码、数据库序列化和各种 REST 路由。这感觉像是需要手动维护大量简单且非 DRY 的代码。使用像 GORM 这样的工具可能解决其中一部分问题,但并非全部。我的结论是,人们没有将 Go 用于大型 CRUD 项目,而是将这部分工作交给 Python Django 或 Rails,而将 Go 用于专门的微服务?

10 回复

这是一个问题,因为我不知道我的结论是否有效(:

更多关于Golang中如何合理管理CRUD操作的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我的结论是,人们没有将Go语言用于大型CRUD项目,而是将这部分工作交给Python Django或Rails,转而使用Go语言开发专门的微服务?

这是一个陈述句,不是疑问句?我可以确认,我尝试用Go创建API确实有些挑战。主要是因为我没有找到关于如何创建REST API的共识。而且也很难实现DRY(不要重复自己)的方法。但最终,我至少成功创建了一个动态REST API的草案。我还没有收到太多反馈,所以不知道我的方向是否正确。https://crud.go4webdev.org

而且随着项目规模的增大,这一点变得更加重要,而非相反。性能方面也是如此。

我完全同意你的这个观点。我正努力将我的职业发展更多地推向处于种子阶段、仍在探索产品市场契合度的公司,因此目前我想利用约定偏好来优化变更速度。我知道这不可持续,最终我会希望重写代码,停止使用以约定为中心的方法,朝着你所暗示的方向前进。

听起来总的来说,如果我在早期阶段使用 Go 语言,基于约定自己构建一个框架可能会相当快。

每个路由都需要执行查询。我只是想弄清楚,在用 Go 构建 Web CRUD 应用时,人们是否重视 DRY 原则。

我的主要目标是让 API 尽可能符合 DRY 原则,但 Go 使得编写通用代码变得有点困难。在我使用查询查找数据库的方法中,路由和端点甚至可以更加 DRY。一个主路由和几个通用的子路由。它按预期工作,只是代码行数更少……

我猜使用 GORM,它仍然是 DRY 的,因为你的类型……

在我看来,任何类型的 ORM 都是 SQL 之上的一层,它应该简化查询。ORM 就像 jQuery。一个额外的层,最终只会让事情变得更复杂。我有限的 ORM 经验与 DRY 无关。查询数量相同,只是用另一种语言编写。

我主要想了解,那些拥有5到10名开发人员、致力于解决各种软件问题的初创公司,在后端的不同部分都使用了哪些技术。他们可能正在构建一个单体应用,也可能是几个微服务。根据初创公司的性质,Web CRUD可能只是他们正在构建的整体软件中的一小部分。

我的帖子并非意在攻击任何事物。如果给人这种感觉,我表示歉意。我认为Go语言在高吞吐量、高可用性以及各种复杂的后端需求方面看起来非常出色。我只是想弄清楚,人们在使用Go构建Web CRUD时是否重视DRY(不要重复自己)原则,或者他们虽然重视DRY,但只是不在系统的这一部分使用Go。

我并非声称自己曾参与过许多大型或小型系统的工作。我曾参与过一些不同规模的系统,但通常没有负责架构或构建每个系统的大部分内容。有时我的贡献是有价值且重大的,有时则只是微小的贡献。

我不确定我们是否应该在 Go 生态系统中效仿 DRF。 它极其复杂,并且存在严重的性能问题。 在我之前的工作中,我们使用 Device42 来管理我们的数据中心。Device42 是基于 DRF 构建的,每秒无法处理超过 10 个 REST API 请求!他们的技术支持告诉我们,这是他们架构的一个限制,是他们无法解决的问题。

(编写性能不那么糟糕的 DRF 应用程序是可能的。但这不必要地困难。优化缓慢的 Django REST 框架性能

如果你想在 Go 中使用 Rest 框架,可以看看 Gin 教程

Tutorial: Developing a RESTful API with Go and Gin - The Go Programming Language

Go 是一种开源编程语言,可以轻松构建简单、可靠且高效的软件。

Dean_Davidson:

你还担心哪些其他类型的重复?

我会试着边想边说,看看会得出什么结论。让我们从一个非常刻意设计的单一实体系统开始。假设我们正在存储“工作区”,它具有ID、分支名称、创建时间和更新时间。

type Workspace struct {
   Id int
   BranchName string
   CreatedAt time.Time
   UpdatedAt time.Time
}

我们将开始定义一个可预测的路由模式:

rtr.POST("/workspaces", ...)
rtr.GET("/workspaces/:id", ...)
rtr.PATCH("/workspaces/:id", ...)
rtr.DELETE("/workspaces/:id, ...)

GET 和 PATCH 路由将需要解码 JSON 请求体。这仍然是 DRY(不重复自己)的,因为我们只需用 JSON 属性来装饰类型。

每个路由都需要执行查询。我想,使用 GORM 的话,这仍然是 DRY 的,因为你的类型、你的 JSON 解码和 GORM 生成的查询都是同步的。

我认为我之前在某个方面看错了,即如果其中一个类型发生变化,Workspace 类型和字段需要在多个地方更新。我想上面代码剩下的唯一问题是它相当可预测且冗长,例如需要定义的路由匹配器模式,以及每个基本路由的实现都将遵循可预测的模式。

kanishka:

我主要想了解,那些拥有5-10名开发人员、处理各种软件问题的初创公司,他们的后端不同部分都在使用什么技术。他们可能正在构建一个单体应用或几个微服务。我可以想象,根据初创公司的性质,Web CRUD可能只是他们正在构建的整体软件集合中的一小部分。

根据我的经验,这在很大程度上取决于团队的构成。你的初创公司是由一群拥有多年Python经验的5-10名开发人员组成的吗?那显然应该使用Django。不过,我也见过许多大型组织出于基础设施成本的原因转向Go:

Bitly | Blog – 10 Mar 22

为什么我们用Go编写一切

预览图

我们喜欢易于阅读的代码,因为我们喜欢易于维护的代码。这就是我们喜欢Go的原因。

……同样也因为Go的简洁性降低了长期维护成本。总之,再次强调,使用对你和你的团队有效的东西(工具始终只是达到目的的手段)。

kanishka:

我只是想弄清楚,人们在使用Golang构建Web CRUD时是否重视DRY原则,或者如果他们确实重视DRY,但只是不在系统的这一部分使用Golang。

当你说“DRY”时,你指的是不为系统中的每种记录类型都写一遍 update foo set bar = 'hello' where id = 1 这样的代码吗?如果是这样,答案是肯定的。在我参与过的每一个大型Go应用中,我们都使用生成的代码来处理这类简单的更新操作(要么通过像GORM这样的工具,要么通过内部的SQL生成器)。你还担心哪些类型的重复呢?

kanishka:

我不理解为什么像 Rails API 模式或 Django REST 这样的东西在 Go 语言中不是一个流行的选择。

Go 几乎是为 Web API 量身定制的,是我用过的用于快速构建 Web API 性能最好、最符合人体工程学的语言。你具体遇到了什么问题?

kanishka:

我可以理解这样的框架可能不会被每个项目使用,但任何现实的 CRUD 应用都会有几十个实体,需要 JSON 解码、数据库序列化和各种 REST 路由。

如果你有一个对你和你的团队有效的框架或工具,那就用它。但对于像 JSON 编码/解码这样的事情,我一时想不出还有哪种语言/框架将 JSON 作为标准库的一部分。这表明它是 Go 语言相当核心的一部分。根据我的经验,在我工作的团队中,Go 非常适合这些任务。

另外,我认为“几十个”实体属于小项目。我还认为,如果你只进行简单的 CRUD 操作,那么你的应用比我曾经参与过的任何真实生产应用都要简单。对于最简单的 CRUD 查询,我确实经常使用 ORM 或生成的 SQL,但根据我的经验,手写 SQL 总是性能更高,而且通常是必需的。也许你构建的应用比我的更简单。

kanishka:

感觉需要手动维护大量简单、非 DRY(不要重复自己)的代码。使用像 GORM 这样的东西可能会解决其中一些问题,但不是全部。

对于最简单的 CRUD 操作以及为了能够轻松地将数据库查询扫描到结构体中,我有时确实会使用 GORM。生态中有很多包可以帮助解决这个问题,例如 jmoiron/sqlx。这是一个已解决的问题。另外,再说一次,如果你主要的问题是编写简单的查询,那么你使用的肯定是人为设计的例子或者小项目。

kanishka:

我的结论是,人们没有将 Go 用于大型 CRUD 项目,而是将这部分工作交给 Python Django 或 Rails,而将 Go 用于专门的微服务?

一家名为 Google 的小型科技初创公司可能对此有话要说。你到底是如何得出这个结论的?你调查了哪些现实世界的项目?请发给我一两个 GitHub 仓库链接。

我以构建 Web 应用为生已经快二十年了,并且我选择 Go 作为我未来大部分 Web API 的开发语言。在某些情况下,它不是我的首选工具,或者环境要求我使用其他东西——但总的来说,Go 是我目前的首选工具,并且在我公司获得了很大的关注。我也看到它在其他地方越来越受欢迎(纽约时报UberGithub,我还可以继续列举)。如果它不适合你,就用别的。但对我以及许多其他团队来说,它运行得非常好。

在Go中管理CRUD操作确实需要不同于Rails或Django的思维方式。Go社区倾向于使用轻量级库而非全功能框架,这带来了更高的灵活性和性能。以下是几种常见的管理模式:

1. 分层架构模式

最常见的做法是采用清晰的分层架构:

// 领域模型
type User struct {
    ID        uint      `json:"id" gorm:"primaryKey"`
    Name      string    `json:"name"`
    Email     string    `json:"email" gorm:"unique"`
    CreatedAt time.Time `json:"created_at"`
}

// 仓库层接口
type UserRepository interface {
    Create(user *User) error
    FindByID(id uint) (*User, error)
    Update(user *User) error
    Delete(id uint) error
    List(limit, offset int) ([]User, error)
}

// GORM实现
type GormUserRepository struct {
    db *gorm.DB
}

func (r *GormUserRepository) Create(user *User) error {
    return r.db.Create(user).Error
}

func (r *GormUserRepository) FindByID(id uint) (*User, error) {
    var user User
    err := r.db.First(&user, id).Error
    return &user, err
}

2. 使用代码生成减少重复

许多项目使用代码生成工具来自动化CRUD代码:

// 使用sqlc生成类型安全的SQL代码
-- sqlc.yaml
version: "2"
sql:
  - schema: "schema.sql"
    queries: "queries.sql"
    engine: "postgresql"
    gen:
      go:
        package: "db"
        out: "internal/db"

// 生成的代码
type CreateUserParams struct {
    Name  string
    Email string
}

const createUser = `-- name: CreateUser :one
INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email, created_at
`

func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
    row := q.db.QueryRowContext(ctx, createUser, arg.Name, arg.Email)
    var i User
    err := row.Scan(&i.ID, &i.Name, &i.Email, &i.CreatedAt)
    return i, err
}

3. 通用处理器模式

通过反射和接口实现通用CRUD处理器:

type CRUDHandler[T any] struct {
    repo Repository[T]
}

func (h *CRUDHandler[T]) Create(c *gin.Context) {
    var item T
    if err := c.ShouldBindJSON(&item); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    if err := h.repo.Create(&item); err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }
    
    c.JSON(201, item)
}

func (h *CRUDHandler[T]) Get(c *gin.Context) {
    id := c.Param("id")
    item, err := h.repo.FindByID(id)
    if err != nil {
        c.JSON(404, gin.H{"error": "not found"})
        return
    }
    c.JSON(200, item)
}

// 注册路由
func RegisterCRUDRoutes[T any](router *gin.Engine, path string, repo Repository[T]) {
    handler := &CRUDHandler[T]{repo: repo}
    
    router.POST(path, handler.Create)
    router.GET(path+"/:id", handler.Get)
    router.PUT(path+"/:id", handler.Update)
    router.DELETE(path+"/:id", handler.Delete)
    router.GET(path, handler.List)
}

4. 使用Ent等现代工具

Facebook的Ent框架提供了更完整的解决方案:

// 定义schema
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("name"),
        field.String("email").Unique(),
        field.Time("created_at").
            Default(time.Now).
            Immutable(),
    }
}

// 生成的类型安全API
client.User.
    Create().
    SetName("John").
    SetEmail("john@example.com").
    Save(ctx)

users, err := client.User.
    Query().
    Where(user.NameContains("John")).
    Limit(10).
    All(ctx)

5. 组合式路由注册

通过结构体标签自动注册路由:

type ProductController struct {
    service ProductService
}

func (c *ProductController) Routes() []Route {
    return []Route{
        {
            Method:  "POST",
            Path:    "/products",
            Handler: c.Create,
        },
        {
            Method:  "GET",
            Path:    "/products/:id",
            Handler: c.Get,
        },
    }
}

// 自动注册所有控制器
func AutoRegisterRoutes(router *gin.Engine, controllers ...interface{}) {
    for _, ctrl := range controllers {
        if router, ok := ctrl.(interface{ Routes() []Route }); ok {
            for _, route := range router.Routes() {
                router.Handle(route.Method, route.Path, route.Handler)
            }
        }
    }
}

Go社区确实更倾向于使用专门的微服务架构,但对于大型CRUD应用,通过合理的架构设计、代码生成和工具链支持,完全可以高效管理。许多公司如Uber、Twitch等都在生产环境中使用Go构建大型CRUD系统,关键在于选择合适的工具和模式来平衡灵活性和开发效率。

回到顶部