快捷搜索:  汽车  科技

go语言版本管理:选择使用ORM还是不采用ORM

go语言版本管理:选择使用ORM还是不采用ORM该示例正在执行以下操作:type Post struct { gorm.Model Published time.Time Title string Content string Comments []Comment `gorm:"foreignkey:PostID"` Tags []*Tag `gorm:"many2many:post_tags;"` } type Tag struct { gorm.Model Name string Posts []*Post `gorm:"many2many:post_tags;"` } type Comment struct { gorm.Model Author string Published time.Ti

(原文作者:Eli Bendersky)

我一直喜欢使用Go的数据库/ sql软件包来处理数据库。最近,对gORM(https://gorm.io/)的一些提及激起了我对在Go中使用ORM与直接使用数据库/ sql的好奇。过去在ORM上有过多种经验,我决定从实际实验开始,编写有或没有gorm的相同简单应用程序,并比较所花费的精力结果。

这使我写下了有关ORM优缺点的一些一般性想法。如果您对这种事情感兴趣,请继续阅读!

我的无ORM与ORM实验

我的实验包括定义一个简单的数据库,该数据库可以是博客引擎的子集,并编写一些Go代码来填充和查询该数据库,并比较使用普通SQL和使用ORM的外观。

这是数据库模式:

go语言版本管理:选择使用ORM还是不采用ORM(1)

虽然简单,但该模式演示了一个惯用的规范化数据库,该数据库很可能包含构建简单的Wiki或博客应用程序所需的所有元素-它具有一对多关系(在帖子和评论之间)和多对多关系(在帖子和标签之间)。如果您更喜欢将数据库模式读取为SQL,以下是从代码示例(https://github.com/eliben/code-for-blog/tree/master/2019/orm-vs-no-orm/sql)中获取的定义:

create table Post ( postID integer primary key published date title text content text ); create table Comment ( commentID integer primary key postID integer author text published date content text -- One-to-many relationship between Post and Comment; each Comment -- references a Post it's logically attached to. foreign key(postID) references Post(postID) ); create table Tag ( tagID integer primary key name text unique ); -- Linking table for the many-to-many relationship between Tag and Post create table PostTag ( postID integer tagID integer foreign key(postID) references Post(postID) foreign key(tagID) references Tag(tagID) );

该SQL已通过SQLIte进行了测试;其他RDBMS可能需要稍作调整。使用gorm时,无需编写此SQL。相反,我们使用gorm的一些魔术字段标签定义“对象”(真正的struct):

type Post struct { gorm.Model Published time.Time Title string Content string Comments []Comment `gorm:"foreignkey:PostID"` Tags []*Tag `gorm:"many2many:post_tags;"` } type Tag struct { gorm.Model Name string Posts []*Post `gorm:"many2many:post_tags;"` } type Comment struct { gorm.Model Author string Published time.Time Content string PostID int64 }

使用此数据库的代码(https://github.com/eliben/code-for-blog/tree/master/2019/orm-vs-no-orm)有两种变体:

  1. no-ORM;通过数据库/ sql包使用普通的SQL查询。
  2. ORM;使用gorm库进行数据库访问。

该示例正在执行以下操作:

  1. 将一些数据(帖子,评论,标签)添加到数据库。
  2. 查询给定标签中的所有帖子。
  3. 查询所有帖子详细信息(所有附加评论,带有标记的所有标签)。

举例来说,这是任务(2)的两个变体-查找给定标记中的所有帖子(这可能是在博客上填充某种档案列表页面)。首先,无ORM:

func dbAllPostsInTag(db *sql.DB tagID int64) ([]post error) { rows err := db.Query(` select Post.postID Post.published Post.title Post.content from Post inner join PostTag on Post.postID = PostTag.postID where PostTag.tagID = ?` tagID) if err != nil { return nil err } var posts []post for rows.Next() { var p post err = rows.Scan(&p.Id &p.Published &p.Title &p.Content) if err != nil { return nil err } posts = append(posts p) } return posts nil }

如果您了解SQL,这将非常简单。我们必须在Post和PostTag之间执行 内部联接( inner join),并通过标签ID对其进行过滤。其余代码只是遍历结果。

接下来,ORM:

func allPostsInTag(db *gorm.DB t *Tag) ([]Post error) { var posts []Post r := db.Model(t).Related(&posts "Posts") if r.Error != nil { return nil r.Error } return posts nil }

在ORM代码中,为了达到相同的效果,我们倾向于直接使用对象(在此处标记)而不是它们的ID。这里gorm生成的SQL查询与我在no-ORM变体中手动编写的查询几乎相同。

除了为我们生成SQL外,gorm还提供了一种更简便的方法来填充结果片段。在使用数据库/ sql的代码中,我们显式遍历结果,将每一行分别扫描到各个struct字段中。gorm的Related方法(以及其他类似的查询方法)将自动填充结构,并且还将一次性扫描整个结果集。

随意玩代码(https://github.com/eliben/code-for-blog/tree/master/2019/orm-vs-no-orm)!我对这里gorm节省的代码量感到惊讶(对于代码密集型部分节省了约50%的代码),对于使用gorm进行的这些简单查询并不难-调用直接来自API文档方式。我对我的特定示例的唯一抱怨是,在Post和Tag之间建立多对多关系有点挑剔,并且gorm struct字段标签看起来丑陋而神奇。

分层的复杂性使其头部的丑陋

像上面这样的简单实验的问题在于,通常很难限制系统的边界。它显然适用于简单的情况,但是我很想知道将其推到极限时会发生什么-它如何处理复杂的查询和数据库模式?所以我转向浏览Stack Overflow。有很多与gorm相关的问题,而且可以肯定的是,通常的分层复杂性问题是显而易见的(示例1(https://stackoverflow.com/questions/55914830/value-0-zero-not-getting-updated-in-postgres-database-when-updation-is-perfo), 示例2(https://stackoverflow.com/questions/55656002/how-to-select-by-fields-in-preloaded-object))。让我解释一下我的意思。

当包装层本身很复杂时,将复杂功能包装在另一层中的任何情况都存在增加整体复杂性的风险。这通常伴随着抽象的泄漏-如果包装层无法完美完成包装底层功能的工作,并迫使程序员同时与这两层战斗。

不幸的是,gorm非常容易受到此问题的影响。堆栈溢出有无穷无尽的问题,用户最终要抵制gorm本身带来的复杂性,解决其局限性等等。几乎没有什么比确切知道您想要的东西(即您要发出的SQL查询)更糟糕的了,但是却无法编制正确的gorm调用序列来结束该查询。

使用ORM的利与弊

从我的实验中可以明显看出使用ORM的一个关键优势:它节省了大量繁琐的编码。以DB为中心的代码大约节省了50%,这对于某些应用程序来说确实有很大的不同。

在这里不明显的另一个优势是从不同数据库后端进行抽象。在Go中,这可能不是问题,因为 数据库/ sql已经提供了一个很好的可移植层。在缺少标准化SQL访问层的语言中,这种优势要强得多。

至于缺点:

  1. 学习的另一层,具有所有特质,特殊语法,魔术标记等。如果您已经对SQL本身有经验,那么这主要是一个缺点。
  2. 即使您没有SQL的经验,也有大量的知识,许多人可以帮助您解决问题。任何一个ORM都是很多人无法共享的晦涩知识,并且您将花费大量时间来弄清楚如何强行喂食它。
  3. 调试查询性能具有挑战性,因为我们从“金属”中进一步抽象了一个层次。有时需要进行大量调整才能使ORM为您生成正确的查询,而当您已经知道需要哪些查询时,这会令人沮丧。

最后,从长远来看,它的缺点是很明显的:多年来,SQL一直保持不变,而ORM是特定于语言的,并且往往会一直出现和消失。每种流行语言都有多种ORM供您选择;当您从一个团队/公司/项目转移到另一个团队/公司/项目时,可能会切换,这是额外的精神负担。或者,您可以完全切换语言。SQL是一个更为稳定的层,跨团队/语言/项目始终伴随着您。

结论

使用原始SQL实现了一个简单的应用程序框架并将其与使用gorm的实现进行了比较,我可以看到ORM在减少样板方面的吸引力。我还记得很多年前的我自己,是一名数据库新手,并使用Django及其ORM来实现应用程序-太好了!我不必过多考虑SQL或底层数据库,它可以正常工作。但是那个用例真的很简单。

戴上“经验丰富且咸”的帽子,我还可以看到使用ORM的许多缺点。具体来说,我认为在像Go这样的语言中,ORM对我没有用,该语言已经具有良好的SQL接口,并且可以跨数据库后端进行移植。我宁愿花一些时间输入,但是这样可以节省我阅读ORM文档,优化查询以及最重要的调试时间。

如果您的工作是编写大量简单的类似于CRUD的应用程序,那么我可以看到ORM在Go中仍然有用,在这种情况下节省的键入克服了缺点。最后,所有这些都归结为以下核心问题:作为额外功能的额外依赖项的优势(http://eli.thegreenplace.net/2017/benefits-of-dependencies-in-software-projects-as-a-function-of-effort/):在DB接口代码之外需要花费大量精力来花费一个项目的情况-否则,不是简单的CRUD-我认为ORM依赖不值得。

原文:https://eli.thegreenplace.net/2019/to-orm-or-not-to-orm/

猜您喜欢: