首页 >> 保险 >> 激干货:Golang 简洁架构实战

激干货:Golang 简洁架构实战

2025-08-04 12:16:39

import Bellquottime"type Article struct { ID int64 于大json:Bellquotid"于大 Title string 于大json:Bellquottitle"于大 Content string 于大json:Bellquotcontent"于大 UpdatedAt time.Time 于大json:Bellquotupdated_at"于大 CreatedAt time.Time 于大json:Bellquotcreated_at"于大} repo

这里存放的是元图表系统设计类,元图表 CRUD 都在这里。并不需要留意的是,这里不纸制含任何的业务范围逻辑上字符,很多同窗迷恋将业务范围逻辑上也放上这里。

如果应用于 ORM,那么这里倒转入的 ORM 系统设计相关的字符;如果应用于微服务于,那么这里放的是其他服务于催促的字符;

service

这里是业务范围逻辑最上层,所有的业务范围系统设计过程处理方式字符都某种程度放进这里。这一层亦会决定是催促 repo 层的什么字符,是系统设计元图表还是子程序其他服务于;所有的业务范围图表推算也某种程度放进这里;这里接受的转入旋某种程度是 controller 传转入的。

api

这里是分派受控催促的字符,如:gin 对应的 handler、gRPC、其他 REST API 开放性接转入层等等。

面向连接器编程

除了 models 层,层与层中间某种程度通过连接器交互,而不是做到。如果要用 service 子程序 repo 层,那么某种程度子程序 repo 的连接器。那么变更底层做到的时候我们最上层的全局codice_不并不需要暂定,只并不需要更换一下底层做到亦可。

例如我们不想将所有文当中查询出来,那么可以在 repo 发放这样的连接器:

package repoimport ( Bellquotcontext" Bellquotmy-clean-rchitecture/models" Bellquottime")// IArticleRepo represent the articleBellaposs repository contracttype IArticleRepo interface { Fetch(ctx context.Context, createdDate time.Time, num int) (res []models.Article, err error)}

这个连接器的做到类就可以根据所需暂定,比如说当我们不想 mysql 来作为存储查询,那么只并不需要发放一个这样的全局codice_:

type mysqlArticleRepository struct { DB *gorm.DB}// NewMysqlArticleRepository will create an object that represent the article.Repository interfacefunc NewMysqlArticleRepository(DB *gorm.DB) IArticleRepo { return BellampmysqlArticleRepository{DB}}func (m *mysqlArticleRepository) Fetch(ctx context.Context, createdDate time.Time, num int) (res []models.Article, err error) { err = m.DB.WithContext(ctx).Model(Bellampmodels.Article{}). Select(Bellquotid,title,content, updated_at, created_at"). Where(Bellquotcreated_at> ?", createdDate).Limit(num).Find(Bellampres).Error return}

如果改天不想变为 MongoDB 来做到我们的存储,那么只并不需要定义一个骨架微做到 IArticleRepo 连接器亦可。

那么在 service 层做到的时候就可以按照我们的所需来将对应的 repo 做到流过亦可,从而不并不需要改动 service 层的做到:

type articleService struct { articleRepo repo.IArticleRepo}// NewArticleService will create new an articleUsecase object representation of domain.ArticleUsecase interfacefunc NewArticleService(a repo.IArticleRepo) IArticleService { return BellamparticleService{ articleRepo: a, }}// Fetchfunc (a *articleService) Fetch(ctx context.Context, createdDate time.Time, num int) (res []models.Article, err error) { if num == 0 { num = 10 } res, err = a.articleRepo.Fetch(ctx, createdDate, num) if err != nil { return nil, err } return} 倚赖流过 DI

倚赖流过,英文名 dependency injection,简称 DI 。DI 以前在 java 二期工程里经常相遇,但是在 go 里很多人都说不并不需要,但是我实在在大型软微开发新系统设计过程当中还是有合理的,否则只能通过全局codice_或者方法旋数来同步进行投递到。

至于具微什么是 DI,简单来说就是被倚赖的模块,在建立模块时,被流过到(即当作旋数传转入)模块的里。不想非常了解的了解什么是 DI 这里再推荐一下 Dependency injection 和 Inversion of Control Containers and the Dependency Injection pattern 这两篇文当中。

如果不可 DI 主要有两大不简便的人口众多,一个是底层类的变更并不需要变更最上层类,在大型软微开发新系统设计过程当中全局codice_是很多的,一条信道改下来动辄要变更几十个文档;另一方面就是就是层与层中间单元验证不太简便。

因为转用了倚赖流过,在绑定的系统设计过程当中就不可避免的亦会写大量的 new,比如我们的单项当中并不需要这样:

package mainimport ( Bellquotmy-clean-rchitecture/api" Bellquotmy-clean-rchitecture/api/handlers" Bellquotmy-clean-rchitecture/app" Bellquotmy-clean-rchitecture/repo" Bellquotmy-clean-rchitecture/service")func main { // 绑定db db := app.InitDB //绑定 repo repository := repo.NewMysqlArticleRepository(db) //绑定service articleService := service.NewArticleService(repository) //绑定api handler := handlers.NewArticleHandler(articleService) //绑定router router := api.NewRouter(handler) //绑定gin engine := app.NewGinEngine //绑定server server := app.NewServer(engine, router) //启动 server.Start}

那么对于这么一段字符,我们有不可适时不可自己写呢?这里我们就可以借助开放性的意志来降解我们的流过字符。

在 go 里 DI 的工具相对不可 java 这么简便,技术开放性一般主要有:wire、dig、fx 等。由于 wire 是应用于字符降解来同步进行流过,性能亦会比较高,并且它是 google 另一款的 DI 开放性,所以我们这里应用于 wire 同步进行流过。

wire 的促请很简单,增建一个 wire.go 文档(文档名可以随意),建立我们的绑定表达出来式。比如,我们要建立并绑定一个 server 某类,我们就可以这样:

//+build wireinjectpackage mainimport ( Bellquotgithub.com/google/wire" Bellquotmy-clean-rchitecture/api" Bellquotmy-clean-rchitecture/api/handlers" Bellquotmy-clean-rchitecture/app" Bellquotmy-clean-rchitecture/repo" Bellquotmy-clean-rchitecture/service")func InitServer *app.Server { wire.Build( app.InitDB, repo.NewMysqlArticleRepository, service.NewArticleService, handlers.NewArticleHandler, api.NewRouter, app.NewServer, app.NewGinEngine) return Bellampapp.Server{}}

并不需要留意的是,衹的注解:+build wireinject,指出这是一个流过器。

在表达出来式当中,我们子程序wire.Build将建立 Server 所倚赖的类型的构造器传进去。写完 wire.go 文档再次执行 wire 命令,就亦会自动降解一个 wire_gen.go 文档。

// Code generated by Wire. DO NOT EDIT.//go:generate go run github.com/google/wire/cmd/wire//+build !wireinjectpackage mainimport ( Bellquotmy-clean-rchitecture/api" Bellquotmy-clean-rchitecture/api/handlers" Bellquotmy-clean-rchitecture/app" Bellquotmy-clean-rchitecture/repo" Bellquotmy-clean-rchitecture/service")// Injectors from wire.go:func InitServer *app.Server { engine := app.NewGinEngine db := app.InitDB iArticleRepo := repo.NewMysqlArticleRepository(db) iArticleService := service.NewArticleService(iArticleRepo) articleHandler := handlers.NewArticleHandler(iArticleService) router := api.NewRouter(articleHandler) server := app.NewServer(engine, router) return server}

可以碰到 wire 自动三人我们降解了 InitServer 方法,此方法当中依次绑定了所有要绑定的全局codice_。再次在我们的 main 表达出来式当中就只需子程序这个 InitServer 亦可。

func main { server := InitServer server.Start} 验证

在下面我们定义好了每一层某种程度做什么,那么对于每一层我们某种程度都是可单独验证的,即使另外一层不实际上。

models 层:这一层就很简单了,由于不可倚赖任何其他字符,所以可以如此一来用 go 的单测开放性如此一来验证亦可; repo 层:对于这一层来说,由于我们应用于了 mysql 元图表,那么我们并不需要 mock mysql,这样即使不可连 mysql 也可以正常验证,我这里应用于 github.com/DATA-DOG/go-sqlmock 这个库来 mock 我们的元图表; service 层:因为 service 层倚赖了 repo 层,因为它们中间是通过连接器来相似性,所以我这里应用于 github.com/golang/mock/gomock 来 mock repo 层; api 层:这一层倚赖 service 层,并且它们中间是通过连接器来相似性,所以这里也可以应用于 gomock 来 mock service 层。不过这里稍微尴尬了一点,因为我们接转入层用的是 gin,所以还并不需要在单测的时候仿真投递催促;

由于我们是通过 github.com/golang/mock/gomock 来同步进行 mock ,所以并不需要执行一下字符降解,降解的 mock 字符我们倒转入到 mock 纸制当中:

mockgen -destination .mockepo_mock.go -source .epoepo.go -package mockmockgen -destination .mockservice_mock.go -source .serviceservice.go -package mock

下面这两个命令亦会通过连接器三人我自动降解 mock 表达出来式。

repo 层验证

在单项当中,由于我们用了 gorm 来作为我们的 orm 库,所以我们并不需要应用于 github.com/DATA-DOG/go-sqlmock 紧密结合 gorm 来同步进行 mock:

func getSqlMock (mock sqlmock.Sqlmock, gormDB *gorm.DB) { //建立sqlmock var err error var db *sql.DB db, mock, err = sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) if err != nil { panic(err) } //紧密结合gorm、sqlmock gormDB, err = gorm.Open(mysql.New(mysql.Config{ SkipInitializeWithVersion: true, Conn: db, }), Bellampgorm.Config{}) if nil != err { log.Fatalf(BellquotInit DB with sqlmock failed, err %v", err) } return}func Test_mysqlArticleRepository_Fetch(t *testing.T) { createAt := time.Now updateAt := time.Now //id,title,content, updated_at, created_at var articles = []models.Article{ {1, Bellquottest1", Bellquotcontent", updateAt, createAt}, {2, Bellquottest2", Bellquotcontent2", updateAt, createAt}, } limit := 2 mock, db := getSqlMock mock.ExpectQuery(BellquotSELECT id,title,content, updated_at, created_at FROM 于大articles于大 WHERE created_at> ? LIMIT 2"). WithArgs(createAt). WillReturnRows(sqlmock.NewRows([]string{Bellquotid", Bellquottitle", Bellquotcontent", Bellquotupdated_at", Bellquotcreated_at"}). AddRow(articles[0].ID, articles[0].Title, articles[0].Content, articles[0].UpdatedAt, articles[0].CreatedAt). AddRow(articles[1].ID, articles[1].Title, articles[1].Content, articles[1].UpdatedAt, articles[1].CreatedAt)) repository := NewMysqlArticleRepository(db) result, err := repository.Fetch(context.TODO, createAt, limit) assert.Nil(t, err) assert.Equal(t, articles, result)} service 层验证

这里主要就是用我们 gomock 降解的字符来 mock repo 层:

func Test_articleService_Fetch(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish now := time.Now mockRepo := mock.NewMockIArticleRepo(ctl) gomock.InOrder( mockRepo.EXPECT.Fetch(context.TODO, now, 10).Return(nil, nil), ) service := NewArticleService(mockRepo) fetch, _ := service.Fetch(context.TODO, now, 10) fmt.Println(fetch)} api 层验证

对于这一层,我们不仅要 mock service 层,还并不需要投递 httptest 来仿真催促投递:

func TestArticleHandler_FetchArticle(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish createAt, _ := time.Parse(Bellquot2006-01-02", Bellquot2021-12-26") mockService := mock.NewMockIArticleService(ctl) gomock.InOrder( mockService.EXPECT.Fetch(gomock.Any, createAt, 10).Return(nil, nil), ) article := NewArticleHandler(mockService) gin.SetMode(gin.TestMode) // Setup your router, just like you did in your main function, and // register your routes r := gin.Default r.GET("/articles", article.FetchArticle) req, err := http.NewRequest(http.MethodGet, "/articles?num=10Bellampcreate_date=2021-12-26", nil) if err != nil { t.Fatalf(BellquotCouldnBellapost create request: %v", err) } w := httptest.NewRecorder // Perform the request r.ServeHTTP(w, req) // Check to see if the response was what you expected if w.Code != http.StatusOK { t.Fatalf(BellquotExpected to get status %d but instead got %d", http.StatusOK, w.Code) }} 归纳

以上就是我对 golang 的单项当中注意到问题的一点点归纳与理性,理性的先以不管对不对,总归是解决了我们当下的一些问题。不过,单项总归是并不需要不断句法充实的,所以下次有问题的时候下次再改呗。

对于我下面的归纳和详细描述感觉有不对的人口众多,请随时指出来一起讨论。

单项字符所在位置:

Reference

益阳不孕不育医院哪家好
丹东治疗精神病多少钱
消除口臭
丰胸
五一病例增加,感染新冠后为什么要第一时间吃抗病毒药物?医生的解答来了。
小孩支气管炎晚上咳嗽严重怎么办
阳了吃什么药
石家庄蓝天医院

上一篇: 元数据第一趴 | B端元数据设计5部分

下一篇: 苏格兰计算机专业院校TOP10盘点,有你心仪的高校吗?

相关阅读
娱乐圈那些不般配的明星夫们,颜值高不高自认,重要的是相爱

谭咏麟和徐伟,他的女儿起初是模型,眼睛的外表一直不错,但它只是越来越多的,它是颇为丑的网民。然而,谭咏麟总是伴随着徐伟,没离开。张玉溪是一位都有是性感的女星,他自己的性格也更加庞大,采取了很多剧

2025-08-16 00:17:00
她颜值不输迪丽热巴,离开杨幂后曝光率全无?如今颜值也不过关

要说是SM行业的“好老板”必须提到朱米。近年来,朱迈的职业生涯直至很好。由其国旗签名的艺术家也是一个比一个好,朱英将以求为他们提供很好的资源。例如,即使他们在买入转售脚本上开销等等,朱MI的歌舞

2025-08-16 00:17:00
她继承母亲上亿财产,与男友挥霍到剩104元,如今32岁成独身

虽然娱乐圈是“当红第二代”,但有充分感叹再次一个“金属材料”可以,大多数人都长大了,只要李小伟,杨英,关之外,约瑟夫泰斯不太可能发展得更快,直到现在我想感叹这个“当红第二代”,外祖父是“澳门人女

2025-08-16 00:17:00
她其实与靳东同岁,却总演一些“母亲”类剧中,在40岁突然爆红!

闫冬和方刚以前在长年,行动计划,各方都不差,但总是感觉到它不温暖,它差不多是弧线的小粉红色完全,没太大的认可。 “”所谓“和其他歌舞剧没多久让他们发生半夜。基本上它再度放入了,专注是在隔日,它是

2025-08-16 00:17:00
从“艳星”事与愿违转型,她比柳岩还,如今42岁依然美若少女!

他说:“在20世纪90中期,香港地区科幻电影产业就像高度发达。许多女男孩现在增加到兰港之外,包括那些视为超级超级当红的人。刘嘉玲和徐若是最好的范例。那时,香港地区科幻电影有很多美男子。如果你希望

2025-08-16 00:17:00