我使用Go和gRPC创建了一个微服务,并试图找出最佳的程序结构,它可以用作我未来程序的模板。 我有Java背景,并发现自己在Java和Go之间挣扎,它们之间的编程理念完全不同。我写了一系列关于在项目工作中做出的设计决策和取舍的文章。 这是其中的第一篇, 是关于程序结构的。

程序结构的资源

Go的标准程序结构的最佳资源可能是Github上的标准Go程序结构¹,但它不适合我的项目。在阅读了Sylvain Wallez的文章²之后,我终于得到了一些关于其背后原因的信息。 Go起初是专为API和网络服务器而设计,Github上的大多数Go项目都是以库的形式编写的,因此“标准Go程序结构”可能非常适合。 商业微服务项目是一种完全不同的动物,需要不同的程序结构。但我还是采用了“标准Go程序结构”中适用的一些建议,这些建议约占30%。

一般来说,创建应用程序结构有两种不同的方法:基于业务功能或基于技术结构。大家的共识³是基于业务功能的更好,对于单体项目(monolithic project)来说也确实如此。在微服务架构中,情况发生了变化,因为每个服务都有自己的源码库,这相当于已经把应用程序按业务功能查分成了一个个的微服务。因此,在每个微服务中,基于技术结构创建项目结构实际上是可行的。

我还找到了Ben Johnson关于应用程序结构⁴和包结构⁵的一些好建议。但它没有为我的项目提供完整的解决方案,所以我决定创建自己的程序结构。程序结构取决于项目要求,以下是需求。

项目需求:

1.在数据持久层上支持不同的数据库(Sql和NoSql数据库)

2.使用不同的协议(如gRPC或REST)支持来自其他微服务的数据

3.松散耦合和高度内聚

4.支持简单一致的日志记录,并能够更改它(例如,日志记录级别和日志记录库),而无需修改程序中的日志记录语句。

5.支持业务级别的事物交易。

程序结构也受到程序设计的影响。 我采用了 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/servicetmpl

索引:

[1][golang-standards/project-layout]
(https://github.com/golang-standards/project-layout)

[2][Go: the Good, the Bad and the Ugly]
(https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/)

[3][Package by feature, not layer]
(http://www.javapractices.com/topic/TopicAction.do?Id=205)

[4][Structuring Applications in Go]
(https://medium.com/@benbjohnson/structuring-applications-in-go-3b04be4ff091)

[5][Standard Package Layout]
(https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1)

[6][The Clean Code Blog]
(https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)

[7][Go at Google: Language Design in the Service of Software Engineering]
(https://talks.golang.org/2012/splash.article)

[8][Go Microservice with Clean Architecture: Application Logging]
(https://jfeng45.github.io/posts/go_logging_and_error_handling/)

[9][Go Microservice with Clean Architecture: Application Container]
(https://jfeng45.github.io/posts/application_container/)

[10][Practical Go: Real world advice for writing maintainable Go programs]
(https://dave.cheney.net/practical-go/presentations/qcon-china.html)

清晰架构(Clean Architecture)的Go微服务: 程序结构的更多相关文章

  1. (转)微服务架构 互联网保险O2O平台微服务架构设计

    http://www.cnblogs.com/Leo_wl/p/5049722.html 微服务架构 互联网保险O2O平台微服务架构设计 关于架构,笔者认为并不是越复杂越好,而是相反,简单就是硬道理也 ...

  2. Java 18套JAVA企业级大型项目实战分布式架构高并发高可用微服务电商项目实战架构

    Java 开发环境:idea https://www.jianshu.com/p/7a824fea1ce7 从无到有构建大型电商微服务架构三个阶段SpringBoot+SpringCloud+Solr ...

  3. 清晰架构(Clean Architecture)的Go微服务: 程序容器(Application Container)

    清晰架构(Clean Architecture)的一个理念是隔离程序的框架,使框架不会接管你的应用程序,而是由你决定何时何地使用它们.在本程序中,我特意不在开始时使用任何框架,因此我可以更好地控制程序 ...

  4. 通俗地理解面向服务的架构(SOA)以及微服务之间的关系

    SOA是一种软件的应用架构方法,它基于面向对象,但又不是面向对象,整体上是面向服务的架构.SOA由精确的服务定义.松散的构件服务组成,以及业务流程调用等多个方面形成的一整套架构方法. 这话是不是听起来 ...

  5. Re:从0开始的微服务架构--(二)快速快速体验微服务架构?--转

    原文地址:https://mp.weixin.qq.com/s/QO1QDQWnjHZp8EvGDrxZvw 这是专题的第二篇文章,看看如何搭建一个简单模式的微服务架构. 记得好久之前看到一个大牛说过 ...

  6. 【分布式微服务企业快速架构】SpringCloud分布式、微服务、云架构快速开发平台源码

    鸿鹄云架构[系统管理平台]是一个大型 企业.分布式.微服务.云架构的JavaEE体系快速研发平台,基于 模块化.微服务化.原子化.热部署的设计思想,使用成熟领先的无商业限制的主流开源技术 (Sprin ...

  7. 清晰架构(Clean Architecture)的Go微服务: 设计原则

    我最近写了一个Go微服务应用程序,这个程序的设计来自三个灵感: 清晰架构"Clean Architecture"¹ and SOLID (面向对象设计)² 设计 原则³ Sprin ...

  8. 清晰架构(Clean Architecture)的Go微服务: 编码风格

    编码风格在编程中是一个相对乏味的主题,但是合适的编码风格对一个有效的程序员是至关重要的. 它有三个组成部分: 程序结构 ( application layout) 编码规则或风格 命名约定 我已经在清 ...

  9. 清晰架构(Clean Architecture)的Go微服务

    我用Go和gRPC创建了一个微服务项目,并试图找出最好的程序结构,它可以作为我其他项目的模板.我还将程序设计和编程的最佳实践应用于Go Microservice程序,例如清晰架构(Clean Arch ...

随机推荐

  1. Centos7安装redis5.0.7

    1. 安装依赖包 yum install -y gcc gcc-c++ 2. 下载最新版redis安装包并解压安装 cd /usr/local/src wget http://download.red ...

  2. php为什么要用swoole?

    最近两个月一直在研究 Swoole,那么借助这篇文章,我希望能够把 Swoole 安利给更多人.虽然 Swoole 可能目前定位是一些高级 phper 的玩具,让中低级望而生畏,可能对一些应用场景也一 ...

  3. 安卓开发之Java学习

    Java之素数(这里附上王智超大佬的博客地址)https://blog.csdn.net/weixin_43862765/article/details/103311286

  4. python字符串删除,列表删除以及字典删除的总结

    一:字符串删除  1,字符串本身是不可变的,一个字符串定义以后,对他本身是不能做任何操作的,所以的增删改都是对原字符串拷贝的副本的操作,原来的字符串还是原来的字符串,它本身并没 有变 2,字符串本身是 ...

  5. phpstorm2019激活码

    6ZUMD7WWWU-eyJsaWNlbnNlSWQiOiI2WlVNRDdXV1dVIiwibGljZW5zZWVOYW1lIjoiSmV0cyBHcm91cCIsImFzc2lnbmVlTmFtZ ...

  6. java switch语句 要点注意

    1.格式 switch(表达式){ case 常量1:语句1:break: case 常量2:语句2:break: case 常量3:语句3:break: .... case 常量n:语句n:brea ...

  7. Django-分页-form数据校验

    分页 view层 def fenye(request): all_data = models.AuthorDetail.objects.all() current_page = request.GET ...

  8. android java socket断线重连

    android java socket断线重连 thread = new Thread(new Runnable() { @Override public void run() { while (tr ...

  9. PAT(甲级)2019年秋季考试

    第一题用搜索,超时了,待补 更新第一题思路 dfs + 剪枝,首先确定 n的最后一位数字肯定是9,为什么呢,因为 任意两个相邻的数肯定互为质数(gcd=1),所以 n 的末尾肯定是9,这样n+1产生的 ...

  10. 【nodejs原理&源码赏析(2)】KOA中间件的基本运作原理

    [摘要] KOA中间件的基本运作原理 示例代码托管在:http://www.github.com/dashnowords/blogs 在中间件系统的实现上,KOA中间件通过async/await来在不 ...