前言
本文主要是以实战方式来介绍微服务下多团队多服务多功能模块下的项目工程结构设计,希望读者通过参考此文章的设计方案后可以自己设计一套满足自己企业的可扩展灵活性较高的项目工程层次结构。
读者在阅读此文之前应该具备哪些前提知识呢?笔者简要的列了一下如下内容:
- 了解 Gradle 基本知识、 Gradle 父子工程管理、Gradle复合构建等相关知识。
- 了解 Spring Boot自动配置、Spring IOC 自动注入特性、Spring Cloud 工程项目和框架特性。
- 了解 Spring Cloud 微服务下 Feign Client 进行服务之间通信的使用和原理,特别是@ Feign Client 注解的 name 和 url 属性特性。
读者在阅读此文之后会有哪些收获呢?笔者希望读者能有如下收获:
- 了解在微服务体系下工程如何命名、工程统一依赖版本如何控制、Git仓库怎么设计来满足多团队多服务的需要和扩展。
- 了解具体服务功能模块的代码工程如何拆分设计来实现用同一套代码库设计出既支持构建成微服务体系,又支持构建成分布式,还可以构建成单体系统,做到工程间的灵活组配。
- 读者阅读后可以自己搭建一套可扩展的项目工程结构。
背景与目标
背景
本文以项目demo来演示工程设计,这样会便于读者理解,这里假设定出如下项目场景:
爪哇留声机公司(以下简称爪哇公司)是一家专门为第三方企业做业务系统的,对于不同第三方企业的规模大小,爪哇公司在开发部署系统的时候可能会有所变化,其中爪哇公司的业务系统包含的如下三个功能:
- 订单功能,其中订单含有国内订单和国外订单两大业务模块(公司小王团队负责)。
- 支付功能(公司小李团队负责)。
- 物流功能(公司小刘团队负责)。
背景一:第三方企业A是一家规模非常大的企业,用户也很庞大,而且也不差钱,那么爪哇公司在给A企业做产品时可能为了性能扩展等需要,会将现有产品化系统微服务化成三个服务(订单服务、支付服务、物流服务)分别部署来满足A企业需求,其中订单服务后续可能会因国内外订单量太大而进一步拆分成国内订单服务和国外订单服务。
背景二:第三方企业B是一家规模小的企业,用户量并不大,那么爪哇公司在给B企业做产品时只部署一个服务(此服务同时包含订单、支付、物流三项功能)即可满足B企业需求,大大减少企业运维等成本。
目标
针对以上场景设计出一套 Gradle 工程结构在同样的代码库情况下来满足其需求,减少研发多次开发成本。
工程结构设计
工程命名规范
project 命名规范
项目-服务-功能-模块,如:javalsj-order-foreign-api、javalsj-order-foreign-impl。
project package 命名规范
项目.服务.功能.模块.领域,如:javalsj.order.foreign.api、javalsj.order.foreign.api.vo、javalsj.order.foreign.api.dto等。
project 工程结构
工程结构层次图
工程结构图层次文字描述
javalsj
整个爪哇项目根目录。
javalsj-frontend
爪哇前端根目录。
javalsj-frontend-vue
爪哇vue前端项目工程。
javalsj-backend
爪哇后端根目录。
javalsj-commom
后端服务公用模块目录。
javalsj-common-base
后端服务无容器概念公用模块工程,如:工具常量类、响应实体等。
javalsj-common-web
后端servlet容器公用模块工程,依赖javalsj-common-base,如:每个servlet服务需要的跨域过滤器配置、web异常拦截器等。
javalsj-common-webflux
后端webflux容器公用模块工程,依赖javalsj-common-base,如:每个webflux服务(网关)需要的webflux异常拦截器等。
javalsj-gateway
后端网关服务目录。
javalsj-gateway-app
后端网关应用工程。
javalsj-auth
后端授权认证服务目录。
javalsj-auth-app
后端授权认证独立启动工程,分布式部署。
javalsj-auth-app-microservice
后端授权认证微服务启动工程,微服务部署。
javalsj-auth-api
api工程,后端支付服务api接口工程,外部工程调用时注入统一依赖api工程。
javalsj-auth-impl
impl工程,后端支付服务api的接口实现工程,依赖javalsj-auth-api工程实现controller的具体逻辑。注:api和impl组合可以用来构建单体服务架构下的程序。
javalsj-auth-client
client工程,后端支付服务提供给外部微服务调用的客户端工程,依赖javalsj-auth-api工程实现 Feign Client 服务调用客户端,其他微服务工程依赖该client工程实现微服务调用。
javalsj-order
后端订单服务目录。
javalsj-order-foreign-api
api工程,后端国外订单服务api接口工程,外部工程调用时注入统一依赖api工程。
javalsj-order-foreign-impl
impl工程,后端国外订单服务api的接口实现工程,依赖javalsj-order-foreign-api工程实现controller的具体逻辑。注:api和impl组合可以用来构建单体服务架构下的程序。
javalsj-order-foreign-client
client工程,后端国外订单服务提供给外部微服务调用的客户端工程,依赖javalsj-order-foreign-api工程实现 Feign Client 服务调用客户端,其他微服务工程依赖该client工程实现微服务调用。
javalsj-order-foregin-app-microservice
app-microservice工程,后端国外订单服务的微服务启动工程,依赖javalsj-order-foreign-impl,若需要调用其他微服务,则依赖其他微服务的client工程代码。
javalsj-order-foregin-app
app工程,后端国外订单服务的独立启动工程,可用于分布式调用。
javalsj-order-internal-api
api工程,后端国内订单服务api接口工程,外部工程调用时注入统一依赖api工程。
javalsj-order-internal-impl
impl工程,后端国内订单服务api的接口实现工程,依赖javalsj-order-internal-api工程实现controller的具体逻辑。注:api和impl组合可以用来构建单体服务架构下的程序。
javalsj-order-internal-client
client工程,后端国内订单服务提供给外部微服务调用的客户端工程,依赖javalsj-order-internal-api工程实现 Feign Client 服务调用客户端,其他微服务工程依赖该client工程实现微服务调用。
javalsj-order-internal-app-microservice
app工程,后端国内订单服务的启动工程,依赖javalsj-order-internal-impl,若需要调用其他微服务,则依赖其他微服务的client工程代码。
javalsj-order-internal-app
app工程,后端国内订单服务的独立启动工程,可用于分布式部署。
javalsj-pay
后端支付服务目录。
javalsj-pay-api
api工程,后端支付服务api接口工程,外部工程调用时注入统一依赖api工程,包结构:javalsj.pay.api、javalsj.pay.api.vo、javalsj.pay.api.dto。
javalsj-pay-impl
impl工程,后端支付服务api的接口实现工程,依赖javalsj-pay-api工程实现controller的具体逻辑。注:api和impl组合可以用来构建单体服务架构下的程序,包结构:javalsj.pay.impl.controller、javalsj.pay.impl.do、javalsj.pay.impl.service、javalsj.pay.impl.service.impl、javalsj.pay.impl.dao、javalsj.pay.impl.dao.impl。
javalsj-pay-client
client工程,后端支付服务提供给外部微服务调用的客户端工程,依赖javalsj-pay-api工程实现 Feign Client 服务调用客户端,其他微服务工程依赖该client工程实现微服务调用,工程包结构:javalsj.pay.client、javalsj.pay.client.fallbackfactory。
javalsj-pay-app-microservice
app-microservice工程,后端支付服务的启动工程,依赖javalsj-pay-impl,若需要调用其他微服务,则依赖其他微服务的client工程代码。
javalsj-order-pay-app
app工程,后端支付服务的独立启动工程,可用于分布式部署。
javalsj-logistics
后端物流服务目录。
javalsj-logistics-api
api工程,后端物流服务api接口工程,外部工程调用时注入统一依赖api工程。
javalsj-logistics-impl
impl工程,后端物流服务api的接口实现工程,依赖javalsj-logistics-api工程实现controller的具体逻辑。注:api和impl组合可以用来构建单体服务架构下的程序。
javalsj-logistics-client
client工程,后端物流服务提供给外部微服务调用的客户端工程,依赖javalsj-logistics-api工程实现 Feign Client 服务调用客户端,其他微服务工程依赖该client工程实现微服务调用。
javalsj-logistics-app-microservice
app-microservice工程,后端物流服务的启动工程,依赖javalsj-logistics-impl,若需要调用其他微服务,则依赖其他微服务的client工程代码。
javalsj-logistics-app
app工程,后端物流服务的独立启动工程,可用于分布式部署。
javalsj-app
后端构建单体启动服务工程。
工程结构图层次设计说明
为了便于理解设计,现在进行工程结构层次设计说明,通过上面的一段描述,读者可以看到每个服务工程都被拆分成了app、app-microservice、api、impl、client 5个 project,读者可以试先按文字描述来预想以下几个问题:
- 拆分的这5个工程分别是个什么东东,它们是分别负责干什么的?
- 为什么要这样拆分,这样拆分的好处在哪里呢?
- 怎样去使用这5个工程进行组配来满足单体、分布式、微服务的工程构建呢?
1. 拆分的这几个工程分别是负责干什么的?
app
独立服务部署应用启动工程。通过 app 的 build.gradle 来构建当前独立服务需要的工程依赖,依赖主要为 api、impl 工程。若该服务需要调用其他服务则依赖再加其他服务的client工程。通过 app 的 application.yml 配置文件设置 Feign Client 设置 name 、url 直连属性。
app-microservice
微服务部署启动工程。通过 app-microservice 的 build.gradle 来构建当前独立服务需要的工程依赖,依赖主要为 api、impl 工程。若该服务需要调用其他服务则依赖再加其他服务的client工程。通过 app 的 application.yml 配置文件设置 Feign Client 只设置 name 属性。
api
服务 api 接口工程,工程模块互相依赖时统一依赖其他服务模块的api接口工程,然后再通过构建依赖 impl 或者 client 工程,利用 Spring IOC 自动注入机制来决定该接口最终是调用服务内部的 controller,还是调用其他服务的client客户端。
impl
服务 api 的接口实现工程,依赖api工程。该工程只用于服务内部构建,不允许其他服务做依赖,工程内容主要是包含 controller、service、dao等业务逻辑模块。
client
服务提供给外部服务调用的 client 客户端工程,依赖 api 工程。该工程只用于其他服务构建依赖,不允许服务内部做依赖,工程内容主要是包含 Feign Client 的通信模块,单独拎出来是为了减少其他服务调用该服务时都写一份client的冗余操作。
2. 为什么要这样拆分,这样拆分的好处在哪里呢?
读者可以通过上面工程结构发现,这样拆分工程的是会增加大量的工程数量,对工程规范性要求也变得较高这是其中的一个缺点。但是在多团队工程规模较大的情况下,这种做法又带来了很大的优点和灵活性,具体如下:
工程随意组合来适应项目需求
在实际工作中,拆分微服务的业务边界其实是一个比较费劲的工作量,而且随着项目的不断扩大,本身拆分的边界已经不满足性能需求了,此时可能会做出如下两种场景改造。
1.对已拆分的服务再做二次拆分。
场景:一开始只是把订单业务拆分成独立的服务,但是后续发现订单业务服务扛不住了此时可能就需要再拆分成国内和国外订单两个服务)。
2.对已拆分的服务做合并。
场景:一开始拆分了国内和国外订单两个服务,但是后续发现订单业务量不大,此时为了降低运维成本可能就会把这两个服务合并成一个订单业务)。以上两个场景均可通过该工程方案解决。
接口工程多实现可以横向扩展
现有api接口工程提供 impl 和 client 两种实现,其中 client 为 feign client 组件实现,如果后需有 Dubbo 或者 Webservice 等实现,也可以 扩展添加client-webservice或者client-dubbo工程,具体使用哪个工程,只需要在app启动工程做依赖即可。
3. 怎样去使用这5个工程进行组配来满足单体、分布式、微服务的工程构建呢?
读者可以参考下列不同服务进行 Gradle 组合构建来理解组配方案。
单体服务模式
启动工程:javalsj-app
Gradle 依赖:javalsj-common-base、javalsj-common-web、javalsj-auth-api、javalsj-auth-impl、javalsj-order-foreign-api、javalsj-order-foreign-impl、javalsj-order-internal-api、javalsj-order-internal-impl、javalsj-pay-api、javalsj-pay-impl、javalsj-logistics-api、javalsj-logistics-impl
分布式服务模式
授权认证服务
启动工程:javalsj-auth-app
Gradle 依赖:javalsj-common-base、javalsj-common-web、javalsj-auth-api、javalsj-auth-impl
订单服务
启动工程:javalsj-order-app
Gradle 依赖:javalsj-common-base、javalsj-common-web、javalsj-order-foreign-api、javalsj-order-foreign-impl、javalsj-order-internal-api、javalsj-order-internal-impl、javalsj-pay-api、javalsj-pay-client
支付服务
启动工程:javalsj-pay-app
Gradle 依赖:javalsj-common-base、javalsj-common-web、javalsj-pay-api、javalsj-pay-impl、javalsj-logistics-api、javalsj-logistics-client
物流服务
启动工程:javalsj-logistics-app
Gradle 依赖:javalsj-common-base、javalsj-common-web、javalsj-logistics-api、javalsj-logistics-impl
备注:分布式构建app依赖时是不需要依赖服务注册发现和配置中心组件。
微服务模式
授权认证微服务
启动工程:javalsj-auth-app-microservice
Gradle 依赖:javalsj-common-base、javalsj-common-web、javalsj-auth-api、javalsj-auth-impl
国内订单微服务
启动工程:javalsj-order-internal-app-microservice
Gradle 依赖:javalsj-common-base、javalsj-common-web、javalsj-order-internal-api、javalsj-order-internal-impl、javalsj-pay-api、javalsj-pay-client
国外订单微服务
启动工程:javalsj-order-foreign-app-microservice
Gradle 依赖:javalsj-common-base、javalsj-common-web、javalsj-order-foreign-api、javalsj-order-foreign-impl、javalsj-pay-api、javalsj-pay-client
支付微服务
启动工程:javalsj-pay-app-microservice
Gradle 依赖:javalsj-common-base、javalsj-common-web、javalsj-pay-api、javalsj-pay-impl、javalsj-logistics-api、javalsj-logistics-client
物流微服务
启动工程:javalsj-logistics-app-microservice
Gradle 依赖:javalsj-common-base、javalsj-common-web、javalsj-logistics-api、javalsj-logistics-impl
备注:微服务构建app依赖时是需要依赖服务注册发现和配置中心组件。
附加信息
上面分布式和微服务的依赖工程一样,又是如何做到划分的呢?这是利用了 Feign Client 的组件特性。我们知道 @Feign Client 有两个属性: name 和 url,其中name属性是必要的,url 属性非必须,在处理时有下列特性。若 url 属性存在,feign会直连url地址调用,此时不会走服务发现,相当于分布式调用,生产时可以把url配置集群的nginx节点地址来达到分布式的负载均衡策略。 若 url 属性是空的时候,feign会按name从服务发现找对应服务名的服务集群,并按负载均衡策略选择其中的一个节点进行调用。两者在工程级别上只是在 app 和 app-microservice 的 application.yml 做配置url参数及是否依赖服务注册与发现等组件的区别。
工程Git仓库设计
针对上述工程结构层次来设计Git仓库,现列出的解决方案有两种,分别是整个项目只使用一个Git仓库和每个团队服务拥有各自的Git仓库,那么两者有什么区别呢?简单列出如下区别供参考:
仓库数
一个Git仓库,1个。
多个Git仓库,N个。
依赖版本管理
一个Git仓库,则 Gradle 版本控制也是统一的,不用研发人员特别关注。
多个Git仓库,则 Gradle 版本控制需要研发人员特别关注,为了版本一致,实际使用时会把依赖包版本放在统一的一个公共目录下使用,每个Git仓库在做依赖时统一引用公共版本模块来达到一致性。
使用灵活性
一个Git仓库,则直接Clone仓库到本地即可,不用关注工程之间依赖的层次文件目录位置放的对不对。
多个Git仓库,则需要Clone多个仓库到本地,且多个仓库之间有引用的话,还需要特别注意仓库目录位置是否放的对不对,若不对的话在build时就会报未找到依赖包的错误。
团队灵活性
一个Git仓库,每次都会拉取整个服务代码,若多团队的情况下则拉取的代码库可能会比较大,即时当前团队可能不会关注的其他团队代码也会被拉取下来,在提交代码需要Merge的概率也会大大提高。
多个Git仓库,每个团队只拉取自己团队的代码,拉取的代码相对较小,便于整个多团队的Git权限管理等。
友情提示:针对以上区别,笔者在这里推荐第二种每个团队服务拥有各自的 Git 仓库的方式,这也是我们实际生产中使用的方式,其实两者差异不是太大,只用做好仓库的管理和规范化即可。笔者为了方便简化的说明工程代码内部的核心内容,此处采取整个项目使用一个 Git 仓库,希望不会影响读者的思路。
Git仓库结构层次图
仓库目录简介
javalsj-app
是整个单体服务的app服务目录。
javalsj-common
是整个多团队多服务公用的依赖工程,比如所有微服务都公用的包工具,当前其存放的包主要有以下:
- javalsj-common-base:无服务容器概念的公用工程,主要存放工具类、统一响应体对象等。
- javalsj-common-web :Servlet 容器概念的公用工程,主要存放跨域过滤器、web统一上下文拦截过滤器、异常拦截器等,如:各Web容器微服务依赖该工程。
- javalsj-common-webflux:WebFlux 容器概念的公用工程,主要存放 WebFlux公用的类等,如:Spring Cloud Gateway网关依赖该工程。
此目录可扩展加入其它公用工程,我们知道 Servlet 和 WebFlux 两种容器在 spring boot 体系下是无法共存的,这也是拆分成两个公用工程的原因之一。
javalsj-gradle
是整个多团队多服务公用的Gradle构建依赖目录。其下存放 Gradle 统一版本控制 version.gradle 文件、发布程序到maven仓库的 push2maven.gradle 文件,方便对整个项目的依赖版本做控制。
javalsj-order
是订单业务团队工程存放的目录。
javalsj-pay
是支付业务团队工程存放的目录。
javalsj-logistics
是物流业务团队工程存放的目录。
工程实例讲解
Gradle 安装
从 Gradle 官网 https://gradle.org/ 下载新版 Gradle 版本并解压,并设置环境变量,安装后在D:/SoftFile/gradle-5.6.2路径下新建文件夹 userhome,用于存放 Gradle 下载的依赖文件,如图。
Git仓库代码拉取
通过 Git Clone 实例项目(地址:https://gitee.com/wangjihong/...),并切换分支为develop, 如图。
各服务模式实例演示
演示场景
如图所示,我们本次实例主要业务是让用户访问获取订单 rest api 请求来获取对应的订单、支付、物流信息。订单模块提供订单的id、code信息,支付模块提供payId、payCode信息,物流模块提供logisticsId、logisticsCode信息。
演示服务设计
单体服务
服务设计
如图所示,单体服务由于只集成了个模块impl工程,所以订单内部注入到PayApi和LogisticsApi的实现类即为PayController和LogisticsController,调用时也是代码类本身方法的调用,不依赖于Feign组件。
启动
IDEA导入单体启动 app 工程: W:/Workspace/git_workspace/javalsj/javalsj_backend/javalsj-app,然后启动单体服务如图:
使用Postman工具模拟访问单体服务订单的rest接口地址:http://localhost:8001/api/order/v1/internal ,结果如下:
由上可见,单体服务集成订单、支付、物流模块后均正常访问。
原理说明
在单体启动工程 javalsj-app 我们可以看到因为单体不涉及服务之间的调用所以 build.gradle 构建脚本只依赖了各模块的 impl 工程:
- 订单业务模块库 javalsj-order-foreign-impl、javalsj-order-internal-impl 工程。
- 支付业务模块库 javalsj-pay-impl 工程。
- 物流业务模块库 javalsj-logistics-impl 工程。
Gradle 构建脚本
buildscript {
ext {
springBootGradlePluginVersion = '2.2.0.RELEASE'
dependencyManagementPluginVersion = '1.0.8.RELEASE'
junitPlatformGradlePluginVersion = '1.2.0'
buildGradleVersion = '3.1.0'
}
repositories {
mavenLocal()
maven { url "http://maven.aliyun.com/nexus/content/groups/public" }
mavenCentral()
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/spring/' }
}
dependencies {
classpath "org.junit.platform:junit-platform-gradle-plugin:${junitPlatformGradlePluginVersion}"
classpath "io.spring.gradle:dependency-management-plugin:${dependencyManagementPluginVersion}"
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootGradlePluginVersion}"
classpath "com.android.tools.build:gradle:${buildGradleVersion}"
}
}
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'eclipse'
apply plugin: 'java-library'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'org.junit.platform.gradle.plugin'
apply from: "../javalsj-gradle/push2maven.gradle"
apply from: "../javalsj-gradle/version.gradle"
dependencies {
// 订单模块库
implementation commonLib.javalsj_common_web
implementation orderLib.javalsj_order_foreign_impl
implementation orderLib.javalsj_order_internal_impl
// 支付模块库
implementation payLib.javalsj_pay_impl
// 物流模块库
implementation logisticsLib.javalsj_logistics_impl
}
application.yml 配置没什么特别的
server:
port: 8001
servlet:
context-path: /
session:
timeout: 10800
分布式服务
服务设计
如图所示,分布式服务由于集成了模块impl工程和支付物流的client工程,所以订单内部注入到PayApi和LogisticsApi的实现类即为Pay Feign Client 和Logistics Feign Client ,加上启动配置文件中设置了 Feign Client 的url属性,所以调用时是采用的直连url的方式,达到分布式调用的效果。
启动
- IDEA导入分布式下订单服务 app 工程: W:/Workspace/git_workspace/javalsj/javalsj_backend/javalsj-order/javalsj-order-app并启动。
- IDEA导入分布式下支付服务 app 工程: W:/Workspace/git_workspace/javalsj/javalsj_backend/javalsj-pay/javalsj-pay-app并启动。
- IDEA导入分布式下物流服务 app 工程: W:/Workspace/git_workspace/javalsj/javalsj_backend/javalsj-logistics/javalsj-logistics-app并启动。
如图。
原理说明
在分布式订单启动工程 javalsj-order-app 我们可以看到因为单体涉及服务之间的调用所以 build.gradle 构建脚本依赖了自身模块的 impl 工程以及支付、物流模块的 client 工程。client工程供订单服务使用Feign Client 对支付、物流服务接口 url 进行调用,:
- 分布式订单服务,依赖模块库 javalsj-order-foreign-impl、javalsj-order-internal-impl、javalsj-pay-client、javalsj-logistics-client工程。
- 分布式支付服务,依赖模块库 javalsj-pay-impl 工程。
- 分布式物流服务,依赖模块库 javalsj-logistics-impl 工程。
分布式与单体区别如下,由于支付、物流服务没什么特殊,所以只拿订单服务的脚本和配置看下。
Gradle 构建脚本区别
app启动工程增加其他模块的client工程依赖。
buildscript {
ext {
springBootGradlePluginVersion = '2.2.0.RELEASE'
dependencyManagementPluginVersion = '1.0.8.RELEASE'
junitPlatformGradlePluginVersion = '1.2.0'
buildGradleVersion = '3.1.0'
}
repositories {
mavenLocal()
maven { url "http://maven.aliyun.com/nexus/content/groups/public" }
mavenCentral()
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/spring/' }
}
dependencies {
classpath "org.junit.platform:junit-platform-gradle-plugin:${junitPlatformGradlePluginVersion}"
classpath "io.spring.gradle:dependency-management-plugin:${dependencyManagementPluginVersion}"
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootGradlePluginVersion}"
classpath "com.android.tools.build:gradle:${buildGradleVersion}"
}
}
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'eclipse'
apply plugin: 'java-library'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'org.junit.platform.gradle.plugin'
apply from: "../../javalsj-gradle/push2maven.gradle"
apply from: "../../javalsj-gradle/version.gradle"
dependencies {
implementation orderLib.javalsj_order_foreign_impl
implementation orderLib.javalsj_order_internal_impl
// 支付接口客户端
implementation payLib.javalsj_pay_client
// 物流接口客户端
implementation logisticsLib.javalsj_logistics_client
}
application.yml 配置区别
增加了@ Feign Client 的url属性值配置(上文也提到了分布式部署方式利用了 Feign Client url 属性值若存在,则 Feign 会直连url 进行地址调用,此时不会走服务发现,相当于分布式调用的组件特性。):
custom:
service:
name: order-service
service-name:
pay: pay-service
logistics: logistics-service
service-url:
// 增加支付URL直连地址
pay: http://localhost:9003
// 增加物流URL直连地址
logistics: http://localhost:9004
management:
endpoints:
web:
exposure:
include: '*'
server:
port: 9002
servlet:
context-path: /
session:
timeout: 10800
feign:
httpclient:
enabled: false
okhttp:
enabled: true
微服务
准备
微服务情况下,我们首先需要搭建服务注册与发现,此处使用 Nacos 开源组件,下载安装启动过程如图。
服务设计
如图所示,微服务由于集成了模块impl工程和支付物流的client工程,所以订单内部注入到PayApi和LogisticsApi的实现类即为Pay Feign Client 和Logistics Feign Client ,启动类加了服务发现注解@EnableDiscoveryClient,启动配置文件中没有设置 Feign Client 的url属性,只设置了name属性,所以调用时会走 Nacos 服务发现找到对应name的服务进行负载均衡调用,达到微服务调用的效果。
启动
- IDEA导入国内订单微服务 app 工程: W:/Workspace/git_workspace/javalsj/javalsj_backend/javalsj-order/javalsj-order-internal-app-microservice并启动。
- IDEA导入支付微服务 app 工程: W:/Workspace/git_workspace/javalsj/javalsj_backend/javalsj-pay/javalsj-pay-app-microservice并启动。
- IDEA导入物流微服务 app 工程: W:/Workspace/git_workspace/javalsj/javalsj_backend/javalsj-logistics/javalsj-logistics-app-microservice并启动。
如图。
原理说明
在微服务启动工程 javalsj-order-app 我们可以看到因为单体涉及服务之间的调用所以 build.gradle 构建脚本依赖了自身模块的 impl 工程以及支付、物流模块的 client 工程。client工程供订单服务使用Feign Client 对支付、物流服务接口 url 进行调用,:
- 订单微服务,依赖模块库 javalsj-order-foreign-impl、javalsj-order-internal-impl、javalsj-pay-client、javalsj-logistics-client、spring-cloud-starter-alibaba-nacos-discovery工程。
- 支付微服务,依赖模块库 javalsj-pay-impl 、spring-cloud-starter-alibaba-nacos-discovery工程。
- 物流微服务,依赖模块库 javalsj-logistics-impl、spring-cloud-starter-alibaba-nacos-discovery 工程。
微服务与分布式的区别如下:
Gradle 构建脚本:
app-microservice启动工程增加了 Nacos 服务注册与发现依赖。
buildscript {
ext {
springBootGradlePluginVersion = '2.2.0.RELEASE'
dependencyManagementPluginVersion = '1.0.8.RELEASE'
junitPlatformGradlePluginVersion = '1.2.0'
buildGradleVersion = '3.1.0'
}
repositories {
mavenLocal()
maven { url "http://maven.aliyun.com/nexus/content/groups/public" }
mavenCentral()
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/spring/' }
}
dependencies {
classpath "org.junit.platform:junit-platform-gradle-plugin:${junitPlatformGradlePluginVersion}"
classpath "io.spring.gradle:dependency-management-plugin:${dependencyManagementPluginVersion}"
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootGradlePluginVersion}"
classpath "com.android.tools.build:gradle:${buildGradleVersion}"
}
}
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'eclipse'
apply plugin: 'java-library'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'org.junit.platform.gradle.plugin'
apply from: "../../javalsj-gradle/push2maven.gradle"
apply from: "../../javalsj-gradle/version.gradle"
dependencies {
implementation orderLib.javalsj_order_internal_impl
implementation payLib.javalsj_pay_client
implementation logisticsLib.javalsj_logistics_client
// 服务注册与发现
implementation pluginLib.spring_cloud_starter_alibaba_nacos_discovery
}
application.yml 配置区别
去掉了@ Feign Client 的url属性值配置(上文也提到了微服务部署方式利用了 Feign Client url 属性值若不存在,则 Feign 会通过name属性进行服务发现负载均衡到目标服务,此时会调用微服务模式的组件特性。):
custom:
service:
name: order-internal-service
service-name:
pay: pay-service
logistics: logistics-service
spring:
application:
name: ${custom.service.name}
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
management:
endpoints:
web:
exposure:
include: '*'
server:
port: 9002
servlet:
context-path: /
session:
timeout: 10800
feign:
httpclient:
enabled: false
okhttp:
enabled: true
启动Application类区别
增加了服务发现注解@EnableDiscoveryClient。
package com.javalsj.order.internal.app.microservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.Enable Feign Client s;
// 增加服务发现注解
@EnableDiscoveryClient
@SpringBootApplication
public class JavalsjOrderInternalAppMicroserviceApplication {
public static void main(String[] args) {
SpringApplication.run(JavalsjOrderInternalAppMicroserviceApplication.class, args);
}
}
总结
本文主要介绍了同一套代码库工程结构设计以及在适配单体服务、分布式、微服务各种模式下的工程设计思路,笔者为了让读者脱离纯理论来便于理解文章,通过写的一个Demo工程实例来演示各服务模式下的配置区别,读者可以通过实例代码自行本地运行测试以便更易理解。(PS:如有设计疑问,可以在文章的评论区发表,笔者看到后会及时回复,互相学习,谢谢)。