真实生产级云原生微服务项目实战-服务开发框架设计与实践

真实生产级云原生微服务项目实战-服务开发框架设计与实践

文章目录

  • 真实生产级云原生微服务项目实战-服务开发框架设计与实践
    • 概述
    • 项目代码组织
    • 为何采用单体仓库
    • 接口参数校验及实现统一异常处理
    • 如何实现强类型接口设计
    • 分环境配置
    • 主流服务框架概览
    • 代码仓库
    • 公众号
    • 参考

概述

本文主要讲解微服务基础框架的设计与实践,包括:项目代码组织、单体仓库、接口参数校验及统一异常处理、实现强类型接口调用、分环境配置、以及主流服务框架对比。

Spring Boot 是业界微服务应用开发的主流框架之一,但是在真正的开发生产级微服务之前,我们仍然需要对 Spring Boot 进行一些必要的封装,以适应我们开发业务的需求,这种封装沉淀下来就是一个企业的微服务基础框架。标准化和重用这个框架可以大大提升我们后续开发微服务的效率。

接下来的内容,主要讲解如何基于 Spring Boot 做一些简单的扩展,定制出一个轻量级的微服务基础框架,这个框架将作为我们后续微服务开发的基础。

项目代码组织

微服务项目采用 Maven 多模块项目进行组织。有一个父pom,它规范了整个项目的依赖,每个模块优先使用父pom规范的依赖,然后,再根据需要引入一些模块专门的依赖。

common-lib这个模块,它是一个公共共享模块,其他的所有的微服务都依赖这个common-lib公共共享模块,其是基础框架类的封装,我们的服务开发框架相关的代码大部分都在这个common-lib里。

<modules>
    <module>common-libmodule>
    <module>account-apimodule>
    <module>account-svcmodule>
    <module>company-apimodule>
    <module>company-svcmodule>
    <module>mail-apimodule>
    <module>mail-svcmodule>
    <module>sms-apimodule>
    <module>sms-svcmodule>
    <module>bot-apimodule>
    <module>bot-svcmodule>
    <module>ical-svcmodule>
    <module>whoami-apimodule>
    <module>whoami-svcmodule>
    <module>web-appmodule>
    <module>faradaymodule>
modules>

整个项目的依赖关系

  • common-lib 依赖 父pom
  • 微服务 依赖 父pom 和 common-lib

整个有一个父pom,它规范了整个应用的依赖,这些微服务account、company等等,他们都依赖这个父pom,根据需要再引入一些自己定制的一些依赖。

有一个common-lib,也是依赖于父pom,它是我们的基础框架的一些封装类,所有的微服务也直接的依赖于common-lib,这就是总体的项目依赖关系。

接口和实现分离的组织方式

我们再看每一个微服务模块,每一个微服务都由两个子模块组成,一个是服务接口模块,一个是服务实现模块。

比方说account服务,对应的有 account-api 就是接口模块,account-svc就是服务实现模块。并且,account-svc也是依赖account-api的,这种接口和实现分离的组织方式,一方面代码结构比较清晰,另一方面也益于这个接口模块的重用。

一般我们会把这些这个接口模块 account-api 等这种接口模块上传到Maven仓库进行管理。

如果某个应用依赖于某个服务,只要引用对应的接口模块就可以了,因为这个接口模块它是强类型的,依赖方就可以直接使用强类型的接口直接调目标服务,不需要去手动的解析JSON。

大家知道手动解析JSON是非常繁琐和容易出错的,所以使用这个强类型客户端可以大大提升我们开发和测试的效率,是微服务开发的最佳实践之一。

再看项目代码组织,除了这些服务有两个子项目接口和实现,也有一些其他的服务,比方说ical-svc是日程表下载站点,还有web-app产品营销站点,这两个是web mvc应用,没有独立的接口模块。

前端资源应用

frontend:

  • app ,管理员排班应用
  • myaccount ,是雇员账户管理应用
  • … ,静态资源文件

这是前端相关的代码。

依赖管理


    UTF-8
    1.8
    2.1.18.RELEASE
    Greenwich.SR6



    
        
        
            org.springframework.boot
            spring-boot-dependencies
            ${spring.boot.version}
            pom
            import
        
        
        
            org.springframework.cloud
            spring-cloud-dependencies
            ${spring.cloud.version}
            pom
            import
        
    

项目依赖JDK1.8、Spring Boot 版本是2.1.2.RELEASE、Spring Cloud 的依赖是 Greenwich.RELEASE,这个项目当中也用到了 Spring Cloud ,主要是 OpenFeign 相关的一些依赖。

因为 Spring Boot 和 Spring Cloud 版本迭代比较快,如果你要开发或者运行项目的话,建议不要随意变更这些依赖版本。

Spring 提供了 spring-boot-dependencies、spring-cloud-dependencies 依赖管理的pom,这种依赖管理pom可以规范依赖版本,还有效地减少依赖版本冲突的问题。重用这些pom,是 Spring 应用开发的最佳实践之一。

到这,我们就把整个项目的代码组织梳理了一遍。

为何采用单体仓库

本项目虽然采用微服务架构,但是,所有代码都在一个仓库当中。这种组织方式也称为单体仓库 mono-repo 。

那么,什么是单体仓库?为什么谷歌采用单体仓库?

单体应用和微服务相比,单体应用最大的好处就是可以独立开发、测试、部署和扩展。单体应用一般采用单体仓库,但是微服务的代码仓库该如何组织呢?一定是每个服务一个仓库吗?其实也不一定。

针对微服务的代码组织,业界主要有两种主要的实践

  • 一种就是多仓库 Multi-Repo,也就是每一个服务一个仓库。
  • 另一种叫单体仓库 Mono-Repo,所有的服务都在一个仓库当中。

真实生产级云原生微服务项目实战-服务开发框架设计与实践_第1张图片

那么多仓库呢?就是把项目拆分成微服务以后,每个子项目都用一个git仓库,这叫多仓库。

那么什么是单体仓库呢?就是我们把微服务化后的这些子模块,它都放在一个git仓库里面。

多仓库的好处? 是显而易见的,每个服务独立仓库,职责单一、代码量和复杂性受控、服务由不同的团队独立维护,边界清晰、单个服务也益于自制开发,测试部署和扩展,不需要集中管理协调,这些基本上就是微服务的好处。

多仓库也有问题:

  • 首先,项目代码不容易规范,每个团队容易各自为政,随意引入依赖。Code review无法集中开展,代码风格会各不相同;
  • 第二个问题就是项目集成和部署会比较麻烦,虽然每个服务易于集成和部署,但是整个应用集成和部署的时候,由于仓库分散就需要集中的管理和协调;
  • 第三个问题就是开发人员缺乏对整体项目的总体认知,开发人员一般只关心自己的服务代码,看不到项目整体,造成缺乏对项目技术架构和业务目标的整体性的理解;
  • 第四个问题就是项目间冗余代码多,每个服务一个仓库,势必造成团队开发的时候走捷径,不断的重复造轮子,而不是去优先重用已有的其他团队开发的代码。

那么单体仓库可以部分的解决上面提到的多仓库的问题,它有下面的一些好处。

单体仓库的好处:

  • 第一个是易于规范代码,所有的代码在一个仓库当中就可以标准化依赖管理,集中开展code review,规范化代码的风格;
  • 第二点是易于集成和部署,所有的代码在一个仓库里面,配合自动化构建工具可以做到一键构建、一键部署,一般不需要特别的集中管理和协调;
  • 第三点是易于理解项目整体,开发人可以把整个项目加载到本地的IDEA里进行code review,也可以直接本地部署调试,方便开发人员把握整体应用的技术架构和业务目标;
  • 第四点就是易于重用,所有的代码都在一个仓库里面,开发人员在开发的时候比较容易发现和重用已有的代码,而不是去重造轮子,开发人员也容易对现有的代码进行重构,现在的IDEA自动支持这种重构的功能,可以抽取出一些公共功能,进一步提升代码的质量和重用度。

所以单体仓库和多仓库都是有利有弊的,那么在业界实际上采用单体仓库管理的公司其实并不少。像谷歌、facebook 等这些互联网的巨头,虽然这些公司系统庞大,服务众多,内部研发团队人数众多,一般过千的,但是他们还是采用单体仓库,而且都很成功。

当然了,单体仓库也是有弊端的,就是随着公司业务团队规模的变大,单一的代码库会变得越来越庞大,复杂性也急速上升,所以这些互联网巨头之所以能够玩转单体仓库,一般都有独立的代码管理和集成团队进行支持,也有配套的自动化构建工具来支持。比方说谷歌,他自研了面向单体仓库的构建工具bazel。

那么初创公司如果采用微服务架构,一般早期服务不是特别多,采用单体仓库会比较合适

Staffjoy 应用规模小,虽然采用微服务架构,但是服务数量不多。为了简化和规范化代码管理,也为了支持一键构建、一键部署这些云原生架构的特性,所以采用单体仓库

微服务架构并不是主张所有的东西都要独立自治,至少代码仓库就可以集中管理,而且这也是业界的最佳实践之一。

接口参数校验及实现统一异常处理

接口参数校验

开发人员必须重视参数校验,因为参数校验和具体的业务场景相关,一般无法完全做到自动化。因此参数校验主要是业务开发人员的职责,当然服务框架应该提供必要的支持,方便开发人员对请求参数进行必要的校验。

Hibernate-validator 可实现对数据的验证,它是对 JSR(Java Specification Requests) 标准的实现。

支持一整套基于标准 JSR 303 和 JSR 380 的参数校验注解。

主要是在 控制(Controller) 层对参数进行校验,以保证这些入参都是经过校验的。

统一异常处理

在 Spring Boot 框架中,对 RESTful 服务 支持 RestControllerAdvice 的方式,进行统一异常处理。

真实生产级云原生微服务项目实战-服务开发框架设计与实践_第2张图片

RestControllerAdvice 工作原理:在一个请求响应周期当中,如果 Controller/Service/Repository 这些层出现任何异常,都会被 RestControllerAdvice 机制所捕获,进行分类和统一处理,然后将处理后的响应返回给调用方。

@RestControllerAdvice
public class GlobalExceptionTranslator {

    @ExceptionHandler(MissingServletRequestParameterException.class)
    public BaseResponse handleError(MissingServletRequestParameterException e) {
        //...
    }
    
    @ExceptionHandler(Throwable.class)
    public BaseResponse handleError(Throwable e) {
        //...
    }
}

Spring MVC 应用统一异常处理与 RESTful 服务统一异常处理稍有不同,因为 RESTful 服务输出的主要是 JSON 消息,Spring MVC 当中输出的主要是 HTML 页面。

如果是 Spring MVC 应用当中,一般通过实现 ErrorController 来实现统一异常处理和捕获。

@Controller
public class GlobalErrorController implements ErrorController {

    @RequestMapping("/error")
    public String handleError(HttpServletRequest request, Model model) {
        Object statusCode = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        Object exception = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
        //...
        return "error";
    }
}

具体详细情况可参考:

来源:《如何基于 Spring Boot 实现接口参数验证及全局异常处理》https://mp.weixin.qq.com/s/KH6pH9iUPuKV9sn8iGsw5w

如何实现强类型接口设计

传统的RPC服务一般是强类型的,RPC通常采用定制的二进制协议对消息进行编码和解码,采用TCP传输消息

RPC服务通常有严格的契约,也就是 contract 这样一个概念;开发服务前,先要定义 IDL(Interface description language,接口描述语言) ,用 IDL 定义契约,再通过这个契约自动生成强类型的服务器端和客户端的接口,服务调用的时候直接使用强类型客户端,不需要再手动进行消息的编码和解码

GRPC 和 Thrif 是目前主流的两种强类型的 RPC 框架,而现代的 RESTful 服务一般是弱类型的

RESTful 服务 通常采用 JSON 作为传输消息,然后使用 HTTP 作为传输协议。 RESTful 服务一般没有严格的契约的概念,它使用普通的 http client 就可以调用,但是调用方通常需要对这个JSON消息进行手动编码和解码工作


强类型服务接口的优点: 是接口规范、自动代码生成、自动编码解码、编译期自动类型检查。

强类型服务接口的缺点:

  • 首先,是客户端和服务器端的强耦合,任何一方升级改动可能会造成另一方break;
  • 另外,自动代码生成需要工具支持,而开发这些工具的成本也比较高;
  • 还有,强类型接口开发测试不太友好,一般浏览器、Postman这样的工具无法直接访问这些强类型接口。

弱类型服务接口的优点: 是客户端和服务器端不强耦合,不需要开发特别的代码生成工具,一般的 http client 就可以调用;
开发测试友好,普通的浏览器、Postman可以轻松访问。

弱类型服务接口的缺点: 是需要调用方手动的编码解码消息;没有自动代码生成,没有编译期接口类型检查,代码不容易规范;开发效率相对低,而且容易出现运行期的错误。


问题: 那么我们在服务开发过程中如何设计强类型接口?有没有办法结合强弱类型服务接口各自的好处,同时又规避他们的不足呢?

方案: 在 Spring RESTful 服务弱类型接口的基础上,借助 Spring Cloud Feign 支持的强类型接口调用的特性,实现了强类型 REST 接口调用机制,同时兼备强弱类型服务接口的好处。

Spring Cloud Feign 本质上是一种动态代理机制,你只要给出一个 RESTful API 对应的 Java 接口,它就可以在运行期动态的拼装出对应接口的强类型客户端。

真实生产级云原生微服务项目实战-服务开发框架设计与实践_第3张图片

虽然我们开发出来的服务是弱类型的 RESTful 的服务,但是因为有 Spring Cloud Feign 的支持,我们只要简单的给出一个强类型的 Java API 接口(这个工作量不大的),就自动获得了强类型的客户端。

也就是说,利用 Spring Cloud Feign ,我们可以同时获得强弱类型的好处,包括:编译期自动类型检查、不需要手动编码解码、不需要开发代码生成工具、而且客户端和服务器端不强耦合。这样可以同时规范代码风格,提升开发测试效率

具体详细情况可参考:

来源:《如何基于 Spring Cloud Feign 实现强类型接口调用RESTful服务》https://mp.weixin.qq.com/s/bYZXhf9BfAfL5aC3bSyiHQ

分环境配置

多环境支持是现代互联网应用研发和交互的基本需求,通过规范多环境和对应的研发流程,可以同时提升交互质量和效率。

本项目所采用的环境规范:

  • DEV:开发环境;用于开发和调试,一般为开发人员本地开发机。
  • TEST:测试环境;独立隔离的,可以有一套或多套,多套可以按测试的功能进行划分,如功能测试环境、集成测试环境、性能测试环境等,也可以按不同的业务线再划分。
  • UAT:(user acceptance testing,用户接受测试环境),一般是独立隔离的,它是供上线前,最后一次集成测试的环境,也被称为准生产环境。国外称为 staging 环境。
  • PROD:生产环境,应用最终上线部署,接受用户实际流量的环境。

有了规范的环境就可以规范研发流程,从 DEV->TEST->UAT->PROD 上线的过程中,每个环境可以设置一些质量阶段门,如必须经过测试环境测试才能上到UAT环境,经过UAT环境测试才能上到PROD环境。

基于规范化的环境和研发流程,再配合自动化的集成、测试和发布工具,就可以构建持续集成和交互,即 CI/CD 流水线。

CI/CD 是现代互联网软件交互的最佳实践,它的基础标准化的环境、交互流程及自动化工具。

主流服务框架概览

目前比较流行的服务开发框架主要有:Spring Boot、Dubbo、Motan、gRpc 。

真实生产级云原生微服务项目实战-服务开发框架设计与实践_第4张图片

编程模型:

  • 代码优先:直接写代码,通过代码导出接口,不需要事先定义。
  • 契约优先:需要事先定义,再通过工具自动生成代码。

Spring Boot

  • Spring Boot 支持的公司是 Pivotal ;
  • 它的编程风格主要是 RESTful ;
  • 它编程模型倾向于代码优先;
  • 它的支持语言主要是 Java 。

Spring Boot 的亮点:就是社区生态好。因为 Spring 框架已经有十几年的历史了,整个社区生态非常好,然后它使用注解的方式,开发、测试都比较友好,效率比较高。


Dubbo

  • Dubbo 背后支持的公司是阿里;
  • 它主要是一种二进制的 RPC 框架,它也支持 RESTful,不过需要通过一些扩展,比方说 DubboX,也就是当当进行的一个扩展,可以认为它主要支持的是 RPC,同时也支持 RESTful;
  • 它的编程模型也是代码优先,先写代码;
  • 它支持的语言主要是 Java。

Dubbo的亮点: 是阿里背书,经过了阿里大流量的考验,另外它的服务治理做得也是比较有特色。


Motan

  • Motan 是新浪开发的 ;
  • 它的编程风格是 RPC ;
  • 编程模型是代码优先;
  • 支持的语言主要是 Java,对 golang 语言也有相应的支持。

Motan 其实是新浪模仿 Dubbo 开发的一个框架,可以认为是一个轻量版的 Dubbo ,如果你觉得 Dubbo 太重,想找一个轻量级的框架,类似于 Dubbo 的,那么可以考虑 Motan 。另外,它还支持 sidecar 的方式去对接其他的语言,这也是它的一个亮点。


gRpc

  • gRpc 它是谷歌公司支持的一个开源产品;
  • 它的编程风格是 RPC ;
  • 它的编程模型是契约优先,它必须先定义契约,然后再去生成代码;
  • 它支持的语言比较多,它本身框架设计的时候就是多语言的,因为它是基于契约的,有了一份契约以后,它可以生成各种语言的服务器端和客户端,所以它是跨语言的;

gRpc框架的亮点: 首先,它是谷歌背书,支持多语言;另外,它还支持一些高级的,像HTTP2 ,其它大部分语言都是支持HTTP或者TCP,gRpc 它是支持 HTTP2 的,在某些场景下它的性能会比较好。


综合选型

本项目在做技术选型的时候,我们不会去选 Dubbo ,因为我们的应用最后是要支持一键部署K8s环境里面,但 Dubbo 它自己有一套治理体系,然后有一个服务发现,跟 K8s 有点不太相容,你如果要去让 Dubbo 集成到 K8s 里面,你可能还要做一些改造。所以没有用 Dubbo 。

Motan 它跟 Dubbo 是类似的,也有一套自己的治理体系,也有自己的服务发现,所以也没有采用。

gRpc 其实是有重点考虑的,因为它整体的设计比较先进,多语言支持。而且是强类型契约驱动的开发方式,而且还支持HTTP2。原本考虑用gRpc来开发本项目的服务的。

gRpc的问题是:它最终是个 RPC 框架,它内部调用挺好的,通过强类型生成出来的客户端直接去调用。但是,如果要把 RPC 服务暴露成 HTTP/REST 服务,就要引入一个对应的 gRpc API Gateway 转换层服务,会多引入一层复杂性,需要做转换,所以就比较麻烦。

Spring Boot 它天生就是 RESTful的,它不支持契约优先的方式,也就说不能生成强类型的客户端。一般情况下,我们都是直接写代码,如果要开发强类型的客户端,只需要写一个接口并添加 Spring Feign 的注解,但这个工作量不大。

所以综合考虑,最后还是倾向于使用 Spring Boot RESTful 比较简单,我们也可以简单的做一些强类型的客户端,然后这个框架可以很容易的部署到 K8s 环境里,这就是最后选择 Spring Boot 的依据。

代码仓库

教学版 Java/Spring:https://github.com/spring2go/staffjoy

实践版 Java/Spring:https://gitee.com/chentian114/staffjoy-chen

公众号

知行chen

参考

《Spring Boot 与 Kubernetes 云原生微服务实践 ~ 全面掌握云原生应用的架构设计与实现》 杨波

你可能感兴趣的:(Java程序员进阶学习之路,架构,微服务,开发框架设计与实践,单体仓库,强类型接口,主流服务框架对比)