Go 工程化之如何优雅的写出 Repo 层代码

开发 前端
我们在获取文章的时候大部分时候可能都是通过 id 获取的,但是我们也可能通过标题等其它信息获取文章的数据,这时候我们的 repo 层代码怎么写呢?

[[442185]]

上篇文章中我们提到了事务的几种解决方案,可以避免在 repo 中写很多不同事务的方法,这篇我们看一下怎么让 repo 层的代码看起来优雅一点

还是以获取一篇文章为例,我们在获取文章的时候大部分时候可能都是通过 id 获取的,但是我们也可能通过标题等其它信息获取文章的数据,这时候我们的 repo 层代码怎么写呢?

最简单的方式,就是我们直接在 repo 这里写两个方法

  1. // IArticleRepo IArticleRepo 
  2. type IArticleRepo interface { 
  3.  GetArticleByTitle(ctx context.Context, title string) (*Article, error) 
  4.  GetArticleByID(ctx context.Context, id int) (*Article, error) 

这样最简单也最直观,但是问题是我们的实际的业务需求往往比我们的例子复杂,如果我们需要通过 id 或者标题获取呢?再添加一个 GetArticleByIDOrTitle ?

这么做的话也不是不行,但是这么做的话就会让我们的 repo 的代码随着时间的增长越来越多不说,命名也是问题,因为组合的方式可能是多种多样的

接下来给大家提供一种我们正在使用的一种思路,利用 Function Options 这种 Go 常见的编程范式,使我们的 repo 更优雅,也可扩展

DBOption

注意: 笔者这里使用的是 GORM,但是这种方式不仅仅适用于 orm 的情况,只是相对方便一点而已

  1. type DBOption func(*gorm.DB) *gorm.DB 
  2. // IArticleRepo IArticleRepo 
  3. type IArticleRepo interface { 
  4.  WithByID(id uint) DBOption 
  5.  WithByTitle(title string) DBOption 
  6.  GetArticle(ctx context.Context, opts ...DBOption) (*Article, error) 

我们定义一个的 DBOption 这个 Option 方法会作为我们 repo 层方法中的最后一个参数,这样我们在定义方法的时候就可以简洁一些,就不必定义很多 GetArticleByXXX 方法了,而是通过定义很多 WithByXXX 的 Option 方法来解决。

这样在 usecase 层,我们只需要这么调用即可

  1. func (u *article) GetArticle(ctx context.Context, id int) (*domain.Article, error) { 
  2.  // 这里可能有其他业务逻辑... 
  3.  return u.repo.GetArticle(ctx, u.repo.WithByID(uint(id))) 

优点

复用: 虽然看上去我们只是把 GetArticleByXXX 换成了 WithByXXX 该有的方法并没有变少,但是我们拆分之后会发现很多可以复用的方法,例如 WithByID 这种几乎是每个实体都会有的方法,我们就不用重复写了。

  1. // GetArticle 和 GetAuthor 都能用上 
  2. func (u *article) GetArticle(ctx context.Context, id int) (*domain.Article, error) { 
  3.  // 这里可能有其他业务逻辑... 
  4.  return u.repo.GetArticle(ctx, u.repo.WithByID(uint(id))) 
  5. func (u *article) GetAuthor(ctx context.Context, id int) (*domain.Author, error) { 
  6.  // 这里可能有其他业务逻辑... 
  7.  return u.repo.GetAuthor(ctx, u.repo.WithByID(uint(id))) 

最小化: 这么修改了之后,拆分组合更加方便了,很多查询条件都可以最小化,例如我们可以添加一个 WithSelects 的方法,我们在 usecase 调用的时候就可以传入当前场景只需要关注的字段就可以了

  1. // GetArticle 返回文章的同时也需要返回作者的名字 
  2. func (u *article) GetArticle(ctx context.Context, id int) (*domain.Article, error) { 
  3.  article, err := u.repo.GetArticle(ctx, u.repo.WithByID(uint(id))) 
  4.  if err != nil { 
  5.   return err 
  6.  } 
  7.  article.Author, err = u.repo.GetAuthor(ctx, u.repo.WithByArticleID(id), u.repo.WithBySelects("id""name")) 
  8.  return article, err 

可测性: repo 层的测试会变得更加方便,这样修改之后我们可以将查询条件拆分出来进行测试,会比之前耦合在一起测试简单很多。

抽象: 这种方式可以让我们抽象 CURD 接口更加方便,在 repo 层实现的时候,我们可以直接把 curd 的方法都给抽象出来

  1. // 这里以创建为例 
  2. func (r *userRepo) optionDB(ctx context.Context, opts ...model.DBOption) *gorm.DB { 
  3.  db := r.db.WithContext(ctx) 
  4.  for _, opt := range opts { 
  5.   db = opt(db) 
  6.  } 
  7.  return db 
  8. func (r *userRepo) create(ctx context.Context, data any, opts ...model.DBOption) error { 
  9.  db := r.optionDB(ctx, opts...) 
  10.  err := db.Create(data).Error 
  11.  if err != nil { 
  12.   return pb.ErrorDbCreateFailf("err: %+v", err) 
  13.  } 
  14.  return nil 

总结

今天给大家介绍了使用 Function Option 的方式来写 repo 层的代码,接下来我们就简单总结一下

  1. type DBOption func(*gorm.DB) *gorm.DB 
  2. // IArticleRepo IArticleRepo 
  3. type IArticleRepo interface { 
  4.  WithByID(id uint) DBOption 
  5.  WithByTitle(title string) DBOption 
  6.  GetArticle(ctx con 

优点

  • 复用: 虽然看上去我们只是把 GetArticleByXXX 换成了 WithByXXX 该有的方法并没有变少,但是我们拆分之后会发现很多可以复用的方法,例如 WithByID 这种几乎是每个实体都会有的方法,我们就不用重复写了。
  • 最小化: 这么修改了之后,拆分组合更加方便了,很多查询条件都可以最小化,例如我们可以添加一个 WithSelects 的方法,我们在 usecase 调用的时候就可以传入当前场景只需要关注的字段就可以了
  • 可测性: repo 层的测试会变得更加方便,这样修改之后我们可以将查询条件拆分出来进行测试,会比之前耦合在一起测试简单很多。
  • 抽象: 这种方式可以让我们抽象 CURD 接口更加方便

缺点

  • 最大的缺点就是有的问题在单测可能测试不出来了,usecase 的测试中,repo 层被 mock 掉了,repo 在测试的时候大部分我们只会测试当前的方法,所以 usecase 有使用比较复杂的查询语句的时候,repo 测试最好测一测真实的使用场景,不要仅测试单个 Option 方法

今天的文章就到这里,下篇文章给大家介绍一下 API 定义上的一点小技巧

本文转载自微信公众号「mohuishou」,可以通过以下二维码关注。转载本文请联系mohuishou公众号。

原文链接:https://lailin.xyz/post/operator-09-kubebuilder-code.html

 

责任编辑:武晓燕 来源: mohuishou
相关推荐

2021-01-04 07:57:07

C++工具代码

2019-09-20 15:47:24

代码JavaScript副作用

2022-03-11 12:14:43

CSS代码前端

2021-12-24 09:00:43

Go语言进程

2020-05-14 09:15:52

设计模式SOLID 原则JS

2021-03-19 07:23:23

Go架构Go工程化

2021-12-07 08:16:34

React 前端 组件

2021-06-05 18:01:05

工具Rollup前端

2023-09-15 10:33:45

前端工程化commit

2020-05-08 14:45:00

JS代码变量

2021-11-08 07:48:48

Go语言对象

2019-12-24 10:40:53

Java代码编程

2024-03-28 14:29:46

JavaScript编程

2021-07-06 10:03:05

软件开发 技术

2023-11-02 09:02:55

Java模式

2021-11-22 06:17:26

npm工程化工具

2018-07-12 14:20:33

SQLSQL查询编写

2018-12-27 09:00:00

Java代码编程语言

2022-12-01 07:46:01

工程化工具

2020-07-15 08:17:16

代码
点赞
收藏

51CTO技术栈公众号