想必现在有很多人对于golang微服务架构图解方面的知识都比较想要了解,此刻林女士就为大家整理了一些关于golang 程序设计相关的信息分享给大家,希望可以解决你的疑问。

由于golang没有java那样统一的编码模式,我们和其他团队一样,采用了《Go的面向包的设计和架构分层》一文中介绍的一些理论,然后结合之前的项目经验进行分包:

其实上面的划分只是简单的把功能分成包,在项目实践的过程中还是有很多问题的。例如:

所以现在我们工作中的代码越来越多,代码中的各种init、function、struct、全局变量感觉越来越乱。每个模块都不是独立的,看似逻辑上划分成模块,但没有明确的上下级关系。每个模块中可能会有配置读取、外部服务调用、协议转换等等。随着时间的推移,服务的不同包函数之间的调用逐渐演变成网状结构,数据流的流向和逻辑梳理变得越来越复杂。如果不查看代码调用,很难确定数据流的方向。

但是,正如《重构》所说:让代码先工作——如果代码不能工作,就不能产生价值;然后努力让它变得更好——通过重构代码,我们可以让自己和他人更好地理解代码,我们可以根据需要不断地修改代码。

所以我觉得是时候改变自己了。

在简明架构中,对我们的项目提出了几个要求:

上图中的同心圆代表不同领域的各种软件。一般来说,你越深入,你的软件水平越高。外圈是战术实现机制,内圈是战略核心策略。对于我们的项目来说,代码依赖应该是从外到内的单向单层依赖,包括代码名,或者类的函数、变量或者任何其他命名的软件实体。

对于简洁的体系结构,有四个层次:

所以对于我的项目来说,它也分为四层:

代码分层

封装各种实体类对象,与数据库、UI等交互。任何实体类都应该放在这里。比如:

这里是数据库操作类,数据库CRUD都在这里。需要注意的是,这里没有业务逻辑代码,很多同学也喜欢把业务逻辑放在这里。

如果使用ORM,那么与ORM操作相关的代码放在这里;如果使用微服务,那么这里是其他服务请求的代码;

这是业务逻辑层,所有业务流程处理代码都应该放在这里。这一层会决定是请求回购层的什么代码,操作数据库还是调用其他服务;所有业务数据计算也要放在这里;这里接受的参数应该由控制器传入。

下面是接收外部请求的代码,比如gin,gRPC,其他REST API框架访问层对应的处理程序等。

除了模型层,其他层应该通过接口而不是实现来相互交互。如果要用服务调用回购层,就要调用回购的接口。然后在修改底层实现的时候,我们的上层基类不需要改变,只需要改变底层实现即可。

例如,如果我们想查询所有文章,我们可以在repo中提供这样一个接口:

该接口的实现类可以根据需要进行更改。例如,当我们希望mysql作为存储查询时,我们只需要提供这样一个基类:

如果有一天想换成MongoDB实现我们的存储,只需要定义一个结构实现IArticleRepo接口。

然后在实现服务层的时候,可以根据我们的要求注入相应的repo实现,所以不需要改变服务层的实现:

依赖注入,英文名Dependency injection,简称DI。DI以前在java工程中经常遇到,go中很多人说没必要,但我觉得在大型软件开发过程中还是有必要的,否则只能通过全局变量或者方法参数传递。

至于DI是什么,简单来说就是依赖模块在创建的时候被注入(即作为参数传入)到模块中。为了更深入地理解什么是DI,这里推荐两篇文章:依赖注入和控制容器的反转以及依赖注入模式。

如果不使用DI,有两个主要的不便之处。一个是下层阶级的修改需要上层阶级的修改。在大型软件开发过程中,基类很多,每改变一个环节都要修改几十个文件。另一方面,层间的单元测试不方便。

因为采用了依赖注入,所以在初始化的过程中不可避免的会写很多新的。例如,我们在项目中需要这样做:

那么,有没有办法让我们自己不写这样一段代码呢?在这里,我们可以使用框架的力量来生成我们的注入代码。

在go中,DI的工具不如java方便,技术框架一般有:wire、dig、fx等。因为wire使用代码生成进行注入,所以性能会比较高,而且是google推出的阿迪框架,所以我们这里使用wire进行注入。

电线的要求很简单。创建一个新的wire.go文件(文件名可以是任意的)并创建我们的初始化函数。例如,如果我们想创建并初始化一个服务器对象,我们可以这样做:

注意第一行的注释:build wireinject表明这是一个注入器。

在函数中,我们称之为wire。Build()来传入创建服务器所依赖的类型的构造函数。写入wire.go文件后执行wire命令,会自动生成一个wire_gen.go文件。

您可以看到wire自动为我们生成了它。

InitServer 方法,此方法中依次初始化了所有要初始化的基类。之后在我们的 main 函数中就只需调用这个 InitServer 即可。

在上面我们定义好了每一层应该做什么,那么对于每一层我们应该都是可单独测试的,即使另外一层不存在。

由于我们是通过 github.com/golang/mock/gomock 来进行 mock ,所以需要执行一下代码生成,生成的 mock 代码我们放入到 mock 包中:

上面这两个命令会通过接口帮我自动生成 mock 函数。

在项目中,由于我们用了 gorm 来作为我们的 orm 库,所以我们需要使用 github.com/DATA-DOG/go-sqlmock 结合 gorm 来进行 mock:

这里主要就是用我们 gomock 生成的代码来 mock repo 层:

对于这一层,我们不仅要 mock service 层,还需要发送 httptest 来模拟请求发送:

以上就是我对 golang 的项目中发现问题的一点点总结与思考,思考的先不管对不对,总归是解决了我们当下的一些问题。不过,项目总归是需要不断重构完善的,所以下次有问题的时候下次再改呗。

对于我上面的总结和描述感觉有不对的地方,请随时指出来一起讨论。

项目代码位置:https://github.com/devYun/go-clean-architecture

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

https://github.com/bxcodec/go-clean-arch

https://medium.com/hackernoon/golang-clean-archithecture-efd6d7c43047

https://farer.org/2021/04/21/go-dependency-injection-wire/

相关问答:Go语言现在的前景怎么样?

本文试着回答:Golang真的是编程的未来吗?

Go是富有成效的。但它并不能代表进步的方向,而且我也不相信它能代表其他新编程语言的发展方向。

Go不是未来。也不是什么新鲜玩意儿:

内置垃圾收集的c语言?

那么,使用一种具有垃圾收集功能的快速系统语言如何呢?Erlang在80年代就是这么做的。

原生支持并发呢?又是Erlang。Erlang的模型还支持开箱即用的分布式计算。

大家看到了吧,Go清除了C语法中的一些瑕疵,用起来非常的爽快。

C语言的用户群普遍认为,垃圾收集是快速而有效。对于Go语言,特别是谷歌给与的支持,给了这一点额外的可信度。

它提供了一个内置的并发模型,这会引入更多的开发人员社区来处理并发。

Rob Pike在语言上设置了一些限制,使得Go很难使用。

这不是未来。

谷歌正在把原本保守的企业创新的底线,从遥远的过去拉到当前,让我们都从中受益。

Happy coding :-)