上一篇我们提到了几种事务的解决方案,可以避免在repo中写很多不同的事务。代码看起来更优雅。以获取一篇文章为例。我们在获取文章的时候,很多时候可能会通过id获取,但是也有可能通过标题等其他信息获取文章的数据。这时候,我们的repo层代码怎么写呢?最简单的方法我们直接在repo里面写两个方法//IArticleRepoIArticleRepotypeIArticleRepointerface{GetArticleByTitle(ctxcontext.Context,titlestring)(*Article,error)GetArticleByID(ctxcontext.Context,idint)(*Article,error)}最简单最直观,但问题是我们实际的业务需求往往比我们的示例更复杂。如果我们需要通过id或title来获取呢?添加另一个GetArticleByIDOrTitle?这样做不是不可以,但是这样做会让我们repo的代码随着时间的推移越来越多。更何况,命名也是个问题,因为可能会有各种组合。接下来,我将向您提供一个我们正在使用的。这种思路,使用Go中常用的编程范式FunctionOptions,让我们的repo更加优雅,还可以扩展DBOption注:作者这里使用的是GORM,但是这种方法不仅仅适用于orm的情况,它比较方便的就typeDBOptionfunc(*gorm.DB)*gorm.DB//IArticleRepoIArticleRepotypeIArticleRepointerface{WithByID(iduint)DBOptionWithByTitle(titlestring)DBOptionGetArticle(ctxcontext.Context,opts...DBOption)(*Article,error)}我们定义一个DBOption这个Option方法会作为我们repo层方法中的最后一个参数,这样我们在定义方法的时候可以更加简洁,也不需要定义一大堆的GetAArticleByXXX方法,而是通过定义很多WithByXXXOption方法来解决这个在usecase层,我们只需要调用func(u*article)GetArticle(ctxcontext.Context,idint)(*domain.Article,error){//这里可能还有其他的业务逻辑...returnu.repo.GetArticle(ctx,u.repo.WithByID(uint(id)))}优点复用:虽然看起来我们只是把GetArticleByXXX换成WithByXXX应该有多少个方法并没有减少,但是我们拆分之后,会发现很多可以复用的方法,比如WithByID,这个方法几乎每个实体都有,所以我们不用重复写。//GetArticle和GetAuthor都可以使用func(u*article)GetArticle(ctxcontext.Context,idint)(*domain.Article,error){//这里可能还有其他业务逻辑...returnu.repo.GetArticle(ctx,u.repo.WithByID(uint(id)))}func(u*article)GetAuthor(ctxcontext.Context,idint)(*domain.Author,error){//这里可能还有其他业务逻辑...returnu.repo.GetAuthor(ctx,u.repo.WithByID(uint(id)))}最小化:这样修改后,拆分合并更方便,很多查询条件可以最小化。比如我们可以添加一个WithSelects方法,我们可以在调用usecase的时候传入当前场景中只需要关注的字段//GetArticle返回文章,还需要返回作者姓名func(u*article)GetArticle(ctxcontext.Context,idint)(*domain.Article,error){article,err:=u.repo.GetArticle(ctx,u.repo.WithByID(uint(id)))iferr!=nil{returnerr}article.Author,err=u.repo.GetAuthor(ctx,u.repo.WithByArticleID(id),u.repo.WithBySelects("id","name"))returnarticle,err}可测试性:repo层测试将变得更加方便,所以我们可以将查询条件拆分出来进行测试,这将比之前将它们耦合在一起简单得多。抽象:该方法可以方便我们对CURD接口进行抽象。在实现repo层的时候,我们可以直接抽象出curd的所有方法//这里以creation为例func(r*userRepo)optionDB(ctxcontext.Context,opts...model.DBOption)*gorm.DB{db:=r.db.WithContext(ctx)for_,opt:=rangeopts{db=opt(db)}returndb}func(r*userRepo)create(ctxcontext.Context,dataany,opts...model.DBOption)error{db:=r.optionDB(ctx,opts...)err:=db.Create(data).Erroriferr!=nil{returnpb.ErrorDbCreateFailf("err:%+v",err)}returnnil}总结今天,我给大家介绍了使用FunctionOption方式来编写repo层的代码。接下来简单总结一下typeDBOptionfunc(*gorm.DB)*gorm.DB//IArticleRepoIArticleRepotypeIArticleRepointerface{WithByID(iduint)DBOptionWithByTitle(titlestring)DBOptionGetArticle(ctxcon优势重用:虽然看起来我们只是把GetArticleByXXX换成了WithByXXX,其实也不少方法,但是拆分之后我们会发现有很多可以复用的方法,比如WithByID这个方法几乎每个实体都有,所以我们不用重复写,并且很多查询条件可以最小化,比如我们可以添加一个WithSelects方法,我们可以在调用usecase的时候,传入当前场景中只需要关注的字段即可。可测试性:repo的测试layer会变得更方便,这样修改后,我们可以拆分查询条件进行测试,会比之前的耦合测试简单很多。抽象:这个方法可以让我们更方便的对CURD接口进行抽象。最大的缺点就是有的问题可能单考都考不上。在用例测试中,回购层被模拟。在测试repo的时候,我们大多数只会测试当前的方法,所以当usecase使用比较复杂的查询语句时,repo测试最好是测试真实的使用场景,而不是只测试单个Option方法。今天的文章就到这里,下篇文章给大家介绍API定义上的小技巧,转载本文请联系墨灰手公众号。原文链接:https://lailin.xyz/post/operator-09-kubebuilder-code.html
