TOC
Staffjoy项目代码组织
-
项目代码组织
-
依赖管理
微服务为什么采用单体仓库Mono-Repo
-
Multi-Repo vs Mono-Repo
-
单体仓库优势
- 易于规范代码,标准化依赖管理,易于Code Review,统一代码风格
- 易于集成和部署
- 易于开发人员理解项目整体
- 易于重用代码,易于重构
微服务接口参数校验为何重要
-
控制器接口参数校验
- javax.validation.constraints内的标注,如@NotBlank、@Min(0)
-
DTO参数校验
- javax.validation的标注,@Valid
- DTO类中,各field添加标注,如@NotBlank、@Email
-
自定义标注
- 自定义@PhoneNumber + 自定义PhoneNumberValidator类(实现ConstraintValidator接口)
如何实现统一异常处理
-
统一异常处理
- Controller、Service、Repository层发生的异常,都会被RestControolerAdvice机制处理
-
RestControllerAdvice
- 统一异常处理类GlobalExceptionTranslator使用@RestControllerAdvice标注
-
统一异常捕获
- 捕获Spring框架本身产生的异常,如MethodArgumentTypeMismatchException
- 捕获应用自定义异常,如自定义的ServiceException、PermissionDeniedException
- 捕获不到的异常归入Throwable中,返回INTERNAL_SERVER_ERRO
-
BaseResponse
- 统一返回的对象类型:message、code、isSuccess
- 使用错误封装消息,而不是直接使用HTTP返回code形式
-
Web MVC ErrorController
- Web MVC与Rest的统一异常处理有区别
- Spring Rest输出的是Json消息,Spring MVC输出的是HTML页面
- 统一异常处理类GlobalErrorController实现ErrorController接口
- handleError(request, model)从request中提取错误状态码和异常,判断是否需要定制的错误页面进行展示,使用Pagenotfound还是internalservererror页面
DTO和DMO为什么要互转
-
DTO和DMO
- DTO:Data Transfer Object,用于网络间传送,api接口输入输出的地方
- DMO:Data Model Object,用于表达应用业务模型,需持久化,应用在数据访问层经常使用DMO,有的叫Entity Object,也有Domain Object
- DTO相对DMO,可能会裁剪一些字段,也可能添加一些聚合字段
- 对于不同的api接口场景,即使使用同样的DTO,字段校验方式也可能不同,如:新建没有id字段,更新一定会有id字段
-
互转:数据字段的拷贝
- 使用modelmapper这样的字段处理工具,或BeanUtils
- 一般在service层处理,也可以在controller层做
如何实现强类型接口设计
-
强类型 vs 弱类型
- 语言
- 强类型+静态语言:类型安全的语言,编译器做强制类型检查,提前避免类型错误;服务器端开发。如Java、C#
- 弱类型+动态语言:解释型语言,类型问题在运行器才会暴露;前端界面开发。如PHP、JS
- 服务API接口
- 强类型,定制的二进制协议对消息进行编码和解码,采用TCP传输消息;开发服务前需先用IDL(Interface Definition Language)定义契约,再通过契约生成强类型的客户端和服务器端的接口。如gRPC、Apache Thrift。优点是接口规范、自动代码生成、自动编码解码、编译期自动类型检查;缺点是客户端和服务器端强耦合、任何一方升级改动可能造成另一方break,自动代码生成需工具支持,开发成本高;测试不友好,浏览器、postman无法直接访问强类型接口
- 弱类型,REST使用JSON传输消息,使用HTTP作为传输协议。REST没有契约概念,使用HttpClient就可以调用;但是调用方需要对Json消息进行手动编码和解码。如Spring Boot。优点:客户端服务器端松耦合、无需开发代码生成工具、一般的httpClient就可调用、开发测试友好;不足是调用方需手动编码解码消息,没有编译器接口类型检查,代码不易规范,容易出现运行器错误
- 语言
-
Spring Feign
- 项目中在Spring REST弱类型接口的基础上,借助Spring Feign支持的强类型接口特性,实现了强类型REST接口调用机制,同时兼备强弱类型接口的好处
- 项目中每个微服务都有两个子模块:一是api接口模块:强类型的Java api接口,包括请求响应、DTO,可以被Spring Feign引用并动态拼装出强类型的客户端;一是服务实现模块
-
强类型接口设计
- 响应对象都继承自BaseResponse对象
- 没有业务数据的则直接使用BaseResponse对象
-
Account Client
- @FeignClinet注解表示,运行期Spring会自动扫描和装配成强类型的客户端
-
客户端调用服务的范例
- 直接用强类型客户端(如accountClient)调用目标服务
- 若底层出现http异常,通过try-catch直接捕获
- 若获得正常http响应,通过BaseResponse中的状态码检查:如果成功,获取响应数据,进行后续处理;如果不成功,获取BaseResponse中的异常信息,直接抛出异常
- 直接用强类型客户端(如accountClient)调用目标服务
-
封装消息 + 捎带
- 将http响应码打包在一个Json消息中,即BaseResponse中,称为封装消息+捎带
- 目标是支持强类型的客户端,同时简化和规范错误处理
为什么框架层要考虑分环境配置
-
环境定义
- DEV:开发调试用,本地开发机或共享开发环境
- TEST:独立隔离的,一套或多套,多套可按测试种类进行划分,如功能测试环境、集成测试环境、性能测试环境等,也可按不同业务线进行划分
- UAT(User Acceptance Test):独立隔离的,供上线前最后一次集成测试的环境,也称准生产环境,staging环境,一套或两套
- PROD:应用最终上线部署,接受用户实际流量的地方
-
分环境
- 有了规范的环境,就可以规范研发流程:如每个环境可设置质量阶段门(Quality Gate),比如必须经过测试环境的测试才能上到UAT环境
- 基于规范化的环境和流程,再配合自动化的继承、测试和发布工具,就可以构建持续继承和交付,即CI/CD流水线,CI/CD是现代互联网软件交付最佳实践,它的基础就是标准化的环境、交付流程和自动化工具
-
环境配置
- EnvConfig类,定义环境是否支持debug、环境的根域名信息、是否启用https等
- 在框架层集成环境配置后,开发人员就可以根据不同环境灵活调整程序逻辑,让一套应用代码能适用多环境:
- 如针对集中日志输出,如果使用了第三方服务,考虑到成本和价值,在开发环境中可关闭这项服务,UAT生产环境开启这项服务
- 如针对短信发送,开发测试环境,只允许发送到公司开发测试人员的手机,不允许发送到普通用户,可通过定制环境逻辑来过滤
-
示例:在开发测试环境禁用Sentry异常日志
- 如果envConfig.isDebug(),则直接返回
异步调用处理
大部分场景下,微服务主要采用request、response同步调用风格;邮件、短信发送等场景下使用异步调用处理
-
ThreadPoolTaskExecutor
- Spring支持的线程池org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
- Task Submitter -> Task Queue -> Thread Pool
-
Spring中配置异步线程池的bean:AsyncExecutor配置
- new ThreadPoolTaskExecutor()
- 设置内部参数:corePoolSize、queueCapacity
-
Async标注
- 某个操作需要异步操作,就在方法上添加@Async注解,name与配置的bean name要一致
- 注意两点:
- 调用方和被调用方的异步操作不能在同一个bean内,如:在AccountService这个bean内,会异步调用trackEventAsync()这个操作,那么这个trackEventAsync()方法就不能再AccountService这个bean内,所以把trackEventAsync()放在单独建立的ServiceHelper这个bean内;这样才能正常的异步工作
- 引入异步调用之后,调用和被调用将由不同的线程执行,线程上下文会发生变化,有些线程相关的信息会被断开,需要做线程上下文的复制动作,如:用户认证信息、调用链监控tracing都是放在请求线程上下文中。
-
线程上下文拷贝
- 配置线程池bean时,将ContextCopyingDecorator配置到了线程池中
- ContextCopyingDecorator中,使用RequestContextHolder.setRequestAttributes将请求上下文复制到新的线程
- 线程结束运行后,返回的时候,使用RequestContextHolder.resetRequestAttributes恢复上下文
Swagger接口文档
-
Swagger配置
- 引入两个依赖:springfox-swagger2和springfox-swagger-ui
- 新增配置类SwaggerConfig,添加@EnableSwagger2的注解
- 配置UI基本信息、作用的包路径等
-
Swagger UI
- 简洁
- 提供了更细粒度的各种标注
-
Swagger JSON Doc
主流服务框架概览
支持公司 | 编程风格 | 编程模型 | 支持语言 | 亮点 | |
---|---|---|---|---|---|
Spring(Boot) | Pivotal | REST | 代码优先 | Java | 社区生态好 |
Dubbo | 阿里 | RPC/REST(Dubbo X) | 代码优先 | Java | 阿里背书+服务治理 |
Motan | 新浪 | RPC | 代码优先 | Java为主 | 轻量版Dubbo |
gRPC | 谷歌 | RPC | 契约优先 | 跨语言 | 谷歌背书+多语言支持+HTTP2支持 |
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付
