我使用Go和gRPC建立了一个微服务,并试图找出最佳的程序结构,它能够用做我将来程序的模板。 我有Java背景,并发现本身在Java和Go之间挣扎,它们之间的编程理念彻底不一样。我写了一系列关于在项目工做中作出的设计决策和取舍的文章。 这是其中的第一篇, 是关于程序结构的。html
Go的标准程序结构的最佳资源多是Github上的标准Go程序结构¹,但它不适合个人项目。在阅读了Sylvain Wallez的文章²以后,我终于获得了一些关于其背后缘由的信息。 Go起初是专为API和网络服务器而设计,Github上的大多数Go项目都是以库的形式编写的,所以“标准Go程序结构”可能很是适合。 商业微服务项目是一种彻底不一样的动物,须要不一样的程序结构。但我仍是采用了“标准Go程序结构”中适用的一些建议,这些建议约占30%。java
通常来讲,建立应用程序结构有两种不一样的方法:基于业务功能或基于技术结构。你们的共识³是基于业务功能的更好,对于单体项目(monolithic project)来讲也确实如此。在微服务架构中,状况发生了变化,由于每一个服务都有本身的源码库,这至关于已经把应用程序按业务功能查分红了一个个的微服务。所以,在每一个微服务中,基于技术结构建立项目结构其实是可行的。git
我还找到了Ben Johnson关于应用程序结构⁴和包结构⁵的一些好建议。但它没有为个人项目提供完整的解决方案,因此我决定建立本身的程序结构。程序结构取决于项目要求,如下是需求。github
1.在数据持久层上支持不一样的数据库(Sql和NoSql数据库)golang
2.使用不一样的协议(如gRPC或REST)支持来自其余微服务的数据sql
3.松散耦合和高度内聚数据库
4.支持简单一致的日志记录,并可以更改它(例如,日志记录级别和日志记录库),而无需修改程序中的日志记录语句。编程
5.支持业务级别的事物交易。segmentfault
程序结构也受到程序设计的影响。 我采用了 Bob Martin的清晰架构(Clean Architecture)⁶ 和 Go的 简洁⁷ 设计风格.缓存
在业务逻辑方面,有三层:“模型(model)”,即域模型; “数据服务(dataservice)”,它是数据持久性(数据库)层; “用例(usecase)”,这是业务逻辑层。
adapter: 这是应用程序和外部数据服务之间的接口,例如另外一个gRPC服务。 全部数据转换都发生在这里,这样你的业务逻辑代码不须要了解外部服务的具体实现(不管是gRPC仍是REST)。
cmd: 命令。 全部不一样类型的“main.go”都在这里,你能够有多个。 这是应用程序的起点。
config: 设置程序和配置数据,包括配置文件。
container: 应用程序依赖注入容器,负责建立具体类型并将它们注入每一个函数。
dataservice: 持久层,负责检索和修改域模型的数据。 它只依赖(depend)于模型(model)层。 数据服务能够经过RPC或RESTFul调用从数据库或其余微服务获取数据。
model: 域模型层,具备域结构(model struct)。 全部其余层依赖于此层,而此层不依赖于任何其余层。
script: 与设置应用程序相关的脚本,例如,数据库脚本。
tool: 第三方工具。
usecase : 这是一个重要的层而且是业务逻辑的切入点。 每一个业务功能都由用例实现。 它是程序顶层,所以没有其余层依赖于它(“cmd”除外),但它依赖于其余层。
用例和数据服务层功能所有由接口调用,所以能够轻松更改实现。
adapter:
当程序须要与微服务或其余外部服务进行交互时,你须要建立接口以减小依赖性。例如,本程序中的“缓存服务”是一个gRPC微服务。每一个外部服务都有本身的子包和文件。例如,缓存服务具备“cacheclient”包和“cacheClient.go”文件,该文件定义了与外部“缓存”微服务交互的类型和方法。
在咱们的示例中,与其余数据服务不一样,“cacheClient.go”文件没有定义缓存服务的接口。实际上它有一个,可是interface是在“dataservice”包中定义的,由于“缓存服务”也是一个数据服务。更明确的方法多是在两个包中各自建立一个接口,这将保持包结构的统一。可是这两个接口将是相同且冗余的,因此我删除了适配器中的接口。
因为咱们还须要将应用程序自己发布为gRPC服务,所以须要在此处建立“userclient”子包。 “generatedclient”子包是为gRPC和Protobuf生成的代码。“userGrpc.go”用来处理域模型结构和gRPC结构之间的格式转换。
cmd:
应用程序的命令,是整个程序的起点。 你能够将应用程序做为本地应用程序运行,也能够将其做为微服务应用程序运行,在这种状况下,你同时拥有客户端(grpcClientMain.go)和服务器端(grpcServerMain.go)主文件。 全部将来的主文件(main.go)也将在此处,例如,Web应用程序服务器主文件。
config:
在此保存全部应用配置文件。 “appConfig.go”负责从配置文件中读取并数据将它们加载到应用程序配置结构中。 你能够为不一样的环境提供不一样的配置文件(YAML文件),例如“Dev”和“Prod”。
container:
本程序中最复杂的包,它为每一个接口建立具体结构并将它们注入其余层。此包中的子包结构相似于应用逻辑分层,它还具备“用例(usecase)”,“数据服务(dataservice)”和“数据存储(datastore)”层。
在本包顶层,“container.go”定义容器接口,它的实现文件“serviceContainer.go”在“servicecontainer”子包中。它是此包的入口点,它将此包中的其余代码粘合在一块儿。 “usecasefactory”子包中的“registrationFactory.go”和其余工厂(factory)使用工厂方法模式(factory method pattern)建立具体结构(struct)。 日志⁸不属于任何应用层,所以我为它建立了一个单独的子包“loggerfactory”。它还有一个“logger”子包来定义日志记录接口。我在文章程序容器9中解释了这个包中的全部内容。
dataservice:
域模型的持久层。 顶层只有一个文件 - “dataService.go”,它具备数据服务的全部接口,包括其余微服务提供的数据服务(例如gRPC)。 其余包只须要依赖于顶级数据服务接口,而不须要了解特定数据库的实现细节。
对于域模型中的每种类型,都有相应的数据服务。 例如,对于模型“User”,有一个“userdata”子包,它包含用户持久性服务的全部实现,包括sqldb(MySql)和CouchDB。
model:
该层不须要接口,模型都是具体结构。
Script:
目前它只有MySql的数据库脚本。
tool:
此程序包适用于第三方工具。 若是你不想直接依赖第三方库,或者须要加强这些第三方库,请在此处进行封装。 不要与“adapter”包混淆,后者也处理第三方库,但只适用于应用程序级数据服务。 “tool”包更适用于较低级别的库。
useCase:
顶级包下只有一个文件“useCase.go”,它包含全部用例接口。 目前,有三个用例:“RegistrationUseCase”,“ListUserUseCase”和“ListCourse”。
每一个用例都有一个子包。 该子包中的文件定义了实现用例接口的具体结构。 全部其余包仅依赖于顶级的用例接口,不须要了解每一个用例的实现细节。
这个程序结构最终会产生不少小的子包,每一个子包只有一个或几个文件。 这与Go习惯用法“考虑更少,更大的包¹⁰相矛盾. 如下才是习惯用法应建立的包:
若是你为其余人编写一个外部库,那么将代码放入一个大包中是一个很好的规则,由于人们不须要多个import语句来使用你的库。 可是在你本身的应用程序中,拥有小包是能够的,特别是当你只将接口暴露给其余层时。
本程序为什要用小包呢? 首先“useCase.go”只定义接口,而其余包(容器除外)仅依赖于接口,所以“useCase.go”须要一个独立的包。 其次,用文件夹分隔每一个用例使程序更清晰易读。
对于gRPC微服务项目,“标准Go程序结构”可能不太适合。 基于业务逻辑而不是技术结构建立应用程序结构对单体项目是一个很好的建议,不必定适合微服务项目。 本文使用一个真实的应用程序做为示例来展现什么是一个很好的微服务应用程序结构及其背后的缘由。
完整的源程序连接 github: https://github.com/jfeng45/se...
[1]golang-standards/project-layout
[2]Go: the Good, the Bad and the Ugly
[3]Package by feature, not layer
[4]Structuring Applications in Go
[7]Go at Google: Language Design in the Service of Software Engineering
[8]Go Microservice with Clean Architecture: Application Logging
[9]Go Microservice with Clean Architecture: Application Container
[10]Practical Go: Real world advice for writing maintainable Go programs
不堆砌术语,不罗列架构,不迷信权威,不盲从流行,坚持独立思考