1、需求
某个电商系统,订单服务需要调用用户服务获取某个用户的所有地址。
我们现在 需要创建两个服务模块进行测试:
1)订单服务web模块:创建订单等
2)用户服务service模块:查询用户地址等
测试预期结果:订单服务web模块在A服务器,用户服务模块在B服务器,A可以远程调用B的功能。
2、工程架构
根据 dubbo《服务化最佳实践》
2.1、分包
建议将服务接口、服务模型、服务异常等均放在 API 包中,因为服务模型和异常也是 API 的一部分,这样做也符合分包原则:重用发布等价原则(REP),共同重用原则(CRP)。
如果需要,也可以考虑在 API 包中放置一份 Spring 的引用配置,这样使用方只需在 Spring 加载过程中引用此配置即可。配置建议放在模块的包目录下,以免冲突,如:com/alibaba/china/xxx/dubbo-reference.xml
。
2.2、粒度
服务接口尽可能大粒度,每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题,Dubbo 暂未提供分布式事务支持。
服务接口建议以业务场景为单位划分,并对相近业务做抽象,防止接口数量爆炸。
不建议使用过于抽象的通用接口,如:Map query(Map)
,这样的接口没有明确语义,会给后期维护带来不便。
2.3、版本
每个接口都应定义版本号,为后续不兼容升级提供可能,如: <dubbo:service interface="com.xxx.XxxService" version="1.0" />
。
建议使用两位版本号,因为第三位版本号通常表示兼容升级,只有不兼容时才需要变更服务版本。
当不兼容时,先升级一半提供者为新版本,再将消费者全部升为新版本,然后将剩下的一半提供者升为新版本。
2.4、兼容性
服务接口增加方法,或服务模型增加字段,可向后兼容,删除方法或删除字段,将不兼容,枚举类型新增字段也不兼容,需通过变更版本号升级。
各协议的兼容性不同,参见:服务协议
2.5、枚举值
如果是完备集,可以用 Enum
,比如:ENABLE
, DISABLE
。
如果是业务种类,以后明显会有类型增加,不建议用 Enum
,可以用 String
代替。
如果是在返回值中用了 Enum
,并新增了 Enum
值,建议先升级服务消费方,这样服务提供方不会返回新值。
如果是在传入参数中用了 Enum
,并新增了 Enum
值,建议先升级服务提供方,这样服务消费方不会传入新值。
2.6、序列化
服务参数及返回值建议使用 POJO 对象,即通过 setter
, getter
方法表示属性的对象。
服务参数及返回值不建议使用接口,因为数据模型抽象的意义不大,并且序列化需要接口实现类的元信息,并不能起到隐藏实现的意图。
服务参数及返回值都必须是传值调用,而不能是传引用调用,消费方和提供方的参数或返回值引用并不是同一个,只是值相同,Dubbo 不支持引用远程对象。
2.7、异常
建议使用异常汇报错误,而不是返回错误码,异常信息能携带更多信息,并且语义更友好。
如果担心性能问题,在必要时,可以通过 override 掉异常类的 fillInStackTrace()
方法为空方法,使其不拷贝栈信息。
查询方法不建议抛出 checked 异常,否则调用方在查询时将过多的 try...catch
,并且不能进行有效处理。
服务提供方不应将 DAO 或 SQL 等异常抛给消费方,应在服务实现中对消费方不关心的异常进行包装,否则可能出现消费方无法反序列化相应异常。
2.8、调用
不要只是因为是 Dubbo 调用,而把调用 try...catch
起来。try...catch
应该加上合适的回滚边界上。
Provider 端需要对输入参数进行校验。如有性能上的考虑,服务实现者可以考虑在 API 包上加上服务 Stub 类来完成检验。
3、创建模块
3.1)gmall-interface
public class UserAddress implements Serializable {
private Integer id;
private String userAddress; //用户地址
private String userId; //用户id
private String consignee; //收货人
private String phoneNum; //电话号码
private String isDefault; //是否为默认地址 Y-是 N-否
......
}
public interface UserService {
public List<UserAddress> getUserAddressList(String userId);
}
public interface OrderService {
public List<UserAddress> initOrder(String userId);
}
3.2)user-service-provider
先引入依赖:
<dependencies>
<dependency>
<groupId>com.wyq.gmall</groupId>
<artifactId>gmall-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 引入dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
<!-- 注册中心使用的是zookeeper,引入操作zookeeper的客户端端 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
</dependencies>
再写实现类:UserServiceImpl
public class UserServiceImpl implements UserService {
@Override
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座10层", "1", "王老师", "010-56253825", "N");
return Arrays.asList(address1,address2);
}
}
然后新建配置文件:provider.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 1、指定当前服务/应用的名字(同样的服务,名字相同,不要和别的应用同名) -->
<dubbo:application name="user-service-provider"></dubbo:application>
<!-- 2、指定注册中心的位置,两种方式都可以 -->
<!--
<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>
-->
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"></dubbo:registry>
<!-- 3、指定通信规则(通信协议?端口号) -->
<dubbo:protocol name="dubbo" port="20880"></dubbo:protocol>
<!-- 4、暴露服务: ref:指向服务的真正的实现对象 -->
<dubbo:service interface="com.wyq.gmall.service.UserService" ref="userServiceImpl"></dubbo:service>
<!-- 服务的实现 -->
<bean id="userServiceImpl" class="com.wyq.gmall.service.impl.UserServiceImpl"></bean>
<!-- 连接监控中心 -->
<dubbo:monitor protocol="registry"></dubbo:monitor>
</beans>
最后写启动类:ProviderMainApplication
public class ProviderMainApplication {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext ioc =
new ClassPathXmlApplicationContext("provider.xml");
ioc.start();
System.in.read(); // 按任意键退出,为了方便查看结果,所以先阻塞
}
}
先启动dubbo的环境,然后运行ProviderMainApplication,从浏览器来查看:http://localhost:7001/
可以看到,我们的服务启动成功!
3.3)order-service-consumer
先引入依赖:
<dependencies>
<dependency>
<groupId>com.wyq.gmall</groupId>
<artifactId>gmall-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 引入dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
<!-- 注册中心使用的是zookeeper,引入操作zookeeper的客户端端 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
</dependencies>
再写实现类:OrderServiceImpl
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
System.out.println("用户id:" + userId);
List<UserAddress> addressList = userService.getUserAddressList(userId);
addressList.forEach(address -> System.out.println(address.getUserAddress()));
return addressList;
}
}
然后新建配置文件:consumer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<context:component-scan base-package="com.wyq.gmall.service.impl"></context:component-scan>
<dubbo:application name="order-service-consumer"></dubbo:application>
<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>
<!-- 声明需要调用的远程服务的接口:生成远程服务代理 -->
<dubbo:reference id="userService"
interface="com.wyq.gmall.service.UserService"></dubbo:reference>
<!-- 配置当前消费者的同一规则:所有的服务都不检查 -->
<!-- <dubbo:consumer check="false"></dubbo:consumer> -->
<!-- 连接监控中心 -->
<dubbo:monitor protocol="registry"></dubbo:monitor>
</beans>
最后写启动类:ConsumerMainApplication
public class ConsumerMainApplication {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("consumer.xml");
OrderService orderService = applicationContext.getBean(OrderService.class);
orderService.initOrder("1");
System.out.println("调用结束...");
System.in.read();
}
}
先启动dubbo的环境,然后运行ProviderMainApplication,从浏览器来查看:http://localhost:7001/
再来看一下控制台:
完成调用!