微服务开发规范

一. 项目说明

XXXX的所有微服务都基于springboot(1.5.7.RELEASE),提供dubbo的微服务基于dubbo(2.5.9)进行开发。为了保证这些第三方jar的版本统一,提供pom依赖,请不要直接引用springboot及dubbo的pom。

使用springboot的微服务,请配置pom的parent为如下:

<parent>

    <groupId>XXXX</groupId>

    <artifactId>infra-microservice-parent</artifactId>

    <version>1.0-SNAPSHOT</version>

</parent>

是用dubbo的微服务,请引用该pom:

<dependency>

    <groupId>XXXX</groupId>

    <artifactId>dubbo-springboot-starter</artifactId>

    <version>2.0-SNAPSHOT</version>

</dependency>

二. 微服务项目结构

1.微服务划分原则

把一个大系统划分为多个微服务,使模块间结构更加清晰,模块间更低耦合高内聚,有更好的扩展性和稳定性。微服务划分请按以下原则:
按功能模块划分微服务,尽量做到一个功能模块一个微服务。
微服务之间减少互相调用,做到低耦合高内聚。

2.微服务模块划分

针对单个微服务,采用maven模块化(module)拆分,具体拆分如下:

微服务名称

微服务说明

xxxx-xxxx单个微服务的总目录,仅包含一个pom,pom中有module的描述,包括该微服务的所有module,微服务名称中间使用【-】分割
xxxx-xxxx-parent

其余所有微服务pom的parent,使得子POM可以获得 parent 中的各项配置,可以对子pom进行统一的配置和依赖管理。

※该pom的parent统一使用infra-microservice-parent,这样可以保证springboot版本的统一

xxxx-xxxx-api

接口层,包括对外暴露的所有接口以及接口使用的model

※理论上来说该pom没有任何其他api或者jar的引用

xxxx-xxxx(-dubbo)-client

其他dubbo consumer引用的dubbo的client,包括dubbo reference的xml

※理论上来说该pom只引用同一个微服务下面的api

xxxx-xxxx-service微服务实现层,包括所有接口的实现
xxxx-xxxx-dubbo

实际微服务的springboot工程,将api暴露为dubbo服务。

※需要引用dubbo-springboot-starter

3. 聚合 VS 父POM

发现很多同事对maven 的parent(父Pom)和module(聚合)的概念混淆,导致pom的引用十分混乱。

父POM是为了抽取统一的配置信息和依赖版本控制,方便子POM直接引用,简化子POM的配置。

※其中relativePath元素不是必须的,指定后会优先从指定的位置查找父POM。

聚合(多模块)则是为了方便一组项目进行统一的操作而作为一个大的整体。

※在列出模块时,不需要自己考虑模块间依赖关系,即POM给出的模块排序并不重要。Maven将对模块进行拓扑排序,使得依赖关系始终在依赖模块之前构建。

所以要真正根据这两者不同的作用来使用,不必为了聚合而继承同一个父POM,也不必为了继承父POM而设计成多模块。

4.微服务gateway

因为微服务基于dubbo,所以需要将各个微服务的api以及client deploy到maven私服上以便其他微服务的dubbo consumer可以引用,另基于目前git的结构,会有若干个微服务放置到同一个git仓库中,所以需要一个额外的pom聚合,在微服务的git目录增加xxxx-xxxx-gateway module,用来做该git需要deploy的jar的聚合。例:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

 

    <modelVersion>4.0.0</modelVersion>

    <groupId>XXXX</groupId>

    <artifactId>platform-XXXX-gateway</artifactId>

    <version>1.0-SNAPSHOT</version>

    <packaging>pom</packaging>

    <modules>

        <module>../platform-XXXX/platform-XXXX-parent</module>

        <module>../platform-XXXX/platform-XXXX-api</module>

        <module>../platform-XXXX/platform-XXXX-client</module>

    </modules>

 

    <distributionManagement>

        <repository>

            <id>XXXX</id>

            <url>http://XXXX/nexus/content/repositories/supplychain</url>

        </repository>

    </distributionManagement>

</project>

※为了便于管理,只在xxxx-xxxx-gateway的pom中添加distributionManagement的描述

三. 微服务配置文件设置

为了减少环境变化时,外置yml文件每个环境都要修改的问题,现在采用通过设置环境变量的方式设置yml参数。

具体参照:微服务采用通过设置环境变量的方式设置yml参数

统一的逻辑为,所有应用根据环境不同,配置同一个setenv.sh文件,应用内部的配置文件都采用读取环境变量的方式。

这样应用在打包的时候与环境profile无关,安装程序时只要维护一个setenv.sh即可。具体参照:环境变量维护

 

四. 微服务端口号设置规范

为了避免同一台机器启动若干个微服务导致的接口冲突,需要统一分配微服务的端口号。

具体参照:微服务端口号分配

五. 微服务接口版本号设置规范

1.  dubbo协议接口约束

  • 参数及返回值需实现 Serializable 接口
  • 参数及返回值不能自定义实现 ListMapNumberDateCalendar 等接口,只能用 JDK 自带的实现,因为 hessian 会做特殊处理,自定义实现类中的属性值都会丢失。
  • Hessian 序列化,只传成员属性值和值的类型,不传方法或静态变量,兼容情况 :

数据通讯

情况

结果

A调用B类A多一种 属性(或者说类B少一种 属性)不抛异常,A多的那 个属性的值,B没有, 其他正常
A调用B枚举A多一种 枚举(或者说B少一种 枚举),A使用多 出来的枚举进行传输抛异常
A调用B枚举A多一种 枚举(或者说B少一种 枚举),A不使用 多出来的枚举进行传输不抛异常,B正常接 收数据
A调用BA和B的属性 名相同,但类型不相同抛异常
A调用BserialId 不相同正常传输

接口增加方法,对客户端无影响,如果该方法不是客户端需要的,客户端不需要重新部署。输入参数和结果集中增加属性,对客户端无影响,如果客户端并不需要新属性,不用重新部署。

输入参数和结果集属性名变化,对客户端序列化无影响,但是如果客户端不重新部署,不管输入还是输出,属性名变化的属性值是获取不到的。

总结:服务器端和客户端对领域对象并不需要完全一致,而是按照最大匹配原则。会抛异常的情况:枚举值一边多一种,一边少一种,正好使用了差别的那种,或者属性名相同,类型不同。

2. 接口版本号设置规范

按照1.接口约束的说明,暂定接口版本号设置规范如下:

  • 当接口无变化,接口实现有变化时,微服务api及client不用重新deploy,版本号不用升级。客户端不需要重新部署。
  • 当接口有变化,且增加方法或者增加接口时,微服务api及client需要重新deploy,版本号固定为本次release版本号。使用接口新方法的客户端需要更新pom,修改对应api的版本。未使用新接口或方法的客户端无需更新pom。
  • 当接口有变化,且修改接口,或者删除接口时,微服务api及client需要重新deploy,版本号固定为本次release版本号。所有客户端需要更新pom,修改对应api的版本。

3.dubbo服务化最佳实践

分包

建议将服务接口、服务模型、服务异常等均放在 API 包中,因为服务模型和异常也是 API 的一部分,这样做也符合分包原则:重用发布等价原则(REP),共同重用原则(CRP)。

如果需要,也可以考虑在 API 包中放置一份 Spring 的引用配置,这样使用方只需在 Spring 加载过程中引用此配置即可。配置建议放在模块的包目录下,以免冲突,如:com/alibaba/china/xxx/dubbo-reference.xml

粒度

服务接口尽可能大粒度,每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题,Dubbo 暂未提供分布式事务支持。

服务接口建议以业务场景为单位划分,并对相近业务做抽象,防止接口数量爆炸。

不建议使用过于抽象的通用接口,如:Map query(Map),这样的接口没有明确语义,会给后期维护带来不便。

版本

每个接口都应定义版本号,为后续不兼容升级提供可能,如: <dubbo:service interface="com.xxx.XxxService" version="1.0" />

建议使用两位版本号,因为第三位版本号通常表示兼容升级,只有不兼容时才需要变更服务版本。

当不兼容时,先升级一半提供者为新版本,再将消费者全部升为新版本,然后将剩下的一半提供者升为新版本。

兼容性

服务接口增加方法,或服务模型增加字段,可向后兼容,删除方法或删除字段,将不兼容,枚举类型新增字段也不兼容,需通过变更版本号升级。

各协议的兼容性不同,参见:服务协议

枚举值

如果是完备集,可以用 Enum,比如:ENABLEDISABLE

如果是业务种类,以后明显会有类型增加,不建议用 Enum,可以用 String 代替。

如果是在返回值中用了 Enum,并新增了 Enum 值,建议先升级服务消费方,这样服务提供方不会返回新值。

如果是在传入参数中用了 Enum,并新增了 Enum 值,建议先升级服务提供方,这样服务消费方不会传入新值。

序列化

服务参数及返回值建议使用 POJO 对象,即通过 settergetter 方法表示属性的对象。

服务参数及返回值不建议使用接口,因为数据模型抽象的意义不大,并且序列化需要接口实现类的元信息,并不能起到隐藏实现的意图。

服务参数及返回值都必需是传值调用,而不能是传引用调用,消费方和提供方的参数或返回值引用并不是同一个,只是值相同,Dubbo 不支持引用远程对象。

异常

建议使用异常汇报错误,而不是返回错误码,异常信息能携带更多信息,并且语义更友好。

如果担心性能问题,在必要时,可以通过 override 掉异常类的 fillInStackTrace() 方法为空方法,使其不拷贝栈信息。

查询方法不建议抛出 checked 异常,否则调用方在查询时将过多的 try...catch,并且不能进行有效处理。

服务提供方不应将 DAO 或 SQL 等异常抛给消费方,应在服务实现中对消费方不关心的异常进行包装,否则可能出现消费方无法反序列化相应异常。

调用

不要只是因为是 Dubbo 调用,而把调用 try...catch 起来。try...catch 应该加上合适的回滚边界上。

Provider 端需要对输入参数进行校验。如有性能上的考虑,服务实现者可以考虑在 API 包上加上服务 Stub 类来完成检验。

 

六.微服务日志输出规范

(待续)

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值