golang orm
Anyone who’s starting to work with Golang can note some lower productivity comparing with another language when building the layer of the database. A lot of repetead SQL code that makes us waste time worrying with things that do not add value to what we want to deliver.
在构建数据库层时,任何开始使用Golang的人都会注意到与另一种语言相比生产力有所降低。 许多重复SQL代码使我们浪费时间担心那些无法为我们交付的产品增加价值的事物。
To see how this lib works on the field, I built a little example project of a book library, that has CRUD operations and you will see how do insert, update, delete, do queries, and how to handle transactions on the database in a simple way.
为了查看该库如何在现场工作,我构建了一个带有CRUD操作的小书库示例项目,您将看到如何在数据库中插入,更新,删除,执行查询以及如何处理数据库中的事务。简单的方法。
The book library API has four operations:
图书库API有四个操作:
POST /books (Insert a new book on the library)
POST / books (在图书馆插入新书)
GET /books/{bookID} (Get a book by your ID on the library)
GET / books / {bookID} (通过您在图书馆的ID获得一本书)
PUT /books/{bookID} (Update a book on the library)
PUT / books / {bookID} (在图书馆中更新一本书)
GET /books (Get all books on the library)
GET / books (获取图书馆中的所有书籍)
To do the routing of the application I choose chi and as you can see the main function became something like that:
为了进行应用程序的路由,我选择chi ,并且您可以看到主要功能变成了类似的样子:
Here I map the routes with your handlers but I create the repo of REL libs too, as you can see at the part of code below:
在这里,我用您的处理程序映射了路由,但是我也创建了REL库的仓库,如下面的代码所示:
adapter, err := mysql.Open(dsn)
if err != nil {
panic(err)
}
defer adapter.Close() // initialize rel's
repo := rel.New(adapter)
With this, I have an instance of REL repo with the connection to MySQL, and this repo provides all functions that I need to communicate with the database. So I reuse the REL repo in the Repository Layer, as you can see below:
有了这个,我有了一个与MySQL连接的REL回购实例,这个回购提供了我与数据库通信所需的所有功能。 因此,我在存储库层中重用了REL repo,如下所示:
type BooksRepository struct {
repo rel.Repository
} func New(repo rel.Repository) BooksRepository {
return BooksRepository{
repo: repo,
}
}
To start using the repo functions I had to map the entities as below:
要开始使用回购功能,我必须按如下所示映射实体:
By default, REL will use the name of each field of the struct as the name of the column, and the name of the struct as name of the table on plural.
默认情况下,REL将使用该结构的每个字段的名称作为列的名称,并使用该结构的名称作为复数形式的表的名称。
To customize the column name I use the tag db:"shelf_id"
saying to the REL what name of this column has to be. And to customize the table name, I implement the function Table()
with the struct as the receiver that I want to specify the name of table returning the name what I want.
为了自定义列名,我使用标签db:"shelf_id"
向REL表示该列的名称。 为了自定义表名,我以struct作为接收器实现了函数Table()
,我想指定表名并返回想要的名称。
After this I implement the first operation of the repository to insert a new book and wheres shelf this book belongs, to do that I needed two functions: one to find the shelf where the book will belong to validate your capacity and another function to insert the book and increment the amount of the shelf:
之后,我执行存储库的第一个操作以插入一本新书以及该书所属的书架,为此,我需要两个功能:一个用于查找书所属的书架以验证您的能力,另一个功能用于插入书本。预定并增加货架数量:
Looking at each part of this code:
查看这段代码的每个部分:
r.repo.Find(ctx, &shelf, where.Eq("id", shelfID))
Here I’m doing a select from table shelf and filtering by your ID. REL is capable of figuring out which tables to query only by using the struct passed in the second parameter, and allows the possibility to use some other parameters to do a filter and another SQL statements, here I’m using the where
to fetch the only ID with the value on variable shelfID
. You can see a lot of other options on the REL documentation.
在这里,我从桌架上进行选择,并按您的ID进行过滤。 REL能够仅通过使用第二个参数中传递的结构来确定要查询的表,并允许使用其他一些参数来进行过滤和其他SQL语句的可能性,这里我使用的是where
获取唯一数据ID的值包含在变量shelfID
。 您可以在REL文档中看到许多其他选项。
r.repo.Preload(ctx, &shelf, "books")
Now to fetch all books on the shelf, I call the function Preload that will load all the books inside of shelf struct using the mapping that was done before.
现在要获取书架上的所有书,我调用函数Preload,该函数将使用之前完成的映射将所有书加载到书架结构中。
The REL will execute the query and print the SQL generated:
REL将执行查询并打印生成SQL:
SELECT * FROM `shelf` WHERE `id`=? LIMIT 1;
SELECT * FROM `book` WHERE `shelf_id` IN (?);
To insert a book I need a transaction to insert and update the shelf table:
要插入一本书,我需要一笔交易来插入和更新书架表:
r.repo.Transaction(ctx, func(ctx context.Context) error {
book.ShelfID = book.BookShelf.ID
r.repo.MustInsert(ctx, &book, rel.Cascade(false))
return r.repo.Update(ctx, &book.BookShelf, rel.Inc("amount"), rel.Reload(false))
})
And to do that I’m using the Transaction
function that expects a callback function that will be executed inside the transaction, being responsible for handle the errors. To perform the rollback I use the MustInsert
function, responsible for insert a book. Moreover using the Update
function to update the shelf table only incrementing the amount
field and saying to not reload the variable after the update. With this, the SQL executed will be:
为此,我正在使用Transaction
函数,该函数期望将在事务内部执行的回调函数负责处理错误。 为了执行回滚,我使用MustInsert
函数,该函数负责插入一本书。 此外,使用Update
功能更新货架表仅增加了amount
字段,并说在更新后不重新加载变量。 这样,执行SQL将是:
begin transaction
INSERT INTO `book` (`title`,`description`,`author`,`edition`,`shelf_id`,`created_at`,`updated_at`) VALUES (?,?,?,?,?,?,?);
UPDATE `shelf` SET `amount`=`amount`+? WHERE `id`=?;
commit transaction
The REL lib provides a way to do tests by mocking the connection with the database and validating if the functions will work as expected, to test these functions was created the following tests:
REL库提供了一种通过模拟与数据库的连接并验证功能是否可以按预期工作来进行测试的方法,以测试这些功能是通过以下测试创建的:
After inserting the books was built the operation to find a book by ID:
插入书籍后,便建立了通过ID查找书籍的操作:
To find a book by ID I did something like I already did when finding a shelf by ID using the Find
and Preload
functions. And also create a test to validate if all will be working as expected.
为了按ID查找书,我做了一些与使用Find
和Preload
函数按ID Find
书架时已经做过的事情。 并创建一个测试以验证所有功能是否都能按预期工作。
In order to update a book I had to implement the function UpdateBook
on the repository and to do that I only had to use the Update
function of REL repo, specifying each field needed to update and after create a test after to validate if all works as expected, as below:
为了更新一本书,我必须在存储库上实现功能UpdateBook
,并且要做到这一点,我只需要使用REL repo的Update
函数,指定需要更新的每个字段,并在创建测试后验证是否所有功能都可以预期如下:
To finish the CRUD of books on a library, I added a function to find all existent books on the library implementing the function FindAllBooks
that use two functions of REL repo, the FindAll
function to fetch all books and the Preload
function to fetch the shelf of each returned book, and of course, was built a test to validate:
为了完成图书馆图书的FindAllBooks
,我添加了一个功能来查找图书馆中所有现有的图书,以实现使用REL repo的两个功能的FindAllBooks
函数,即FindAll
函数用于获取所有图书,而Preload
函数用于获取书架。每本归还的书,当然都经过了测试以验证:
With this, the database layer was built using the REL lib without any SQL complexity and you can find this project on my Github:
这样,数据库层是使用REL lib构建的,而没有任何SQL复杂性,您可以在我的Github上找到此项目:
Please leave a comment or suggestion and let me know what you think about REL. If you are familiar with another library, also leave a comment so we can compare them.
请发表评论或建议,让我知道您对REL的看法。 如果您熟悉另一个库,也请发表评论,以便我们进行比较。
翻译自: https://medium.com/@rafaelholanda90/looking-for-an-orm-to-database-layer-with-golang-92e360d3a920
golang orm