您的位置:首页 > 其它

前后端分离实践(EOS篇)

2014-10-07 14:23 232 查看

1.简介

作为后端,在整个java的环境中,服务的RPC操作是分布式应用中至关重要的部分,如何让开发人员无成本的接入分布式架构,这是我们在思考的范畴。引入它让我们使用java开放服务轻而易举,规范服务接口标准以及跨语言的服务调用提供了快捷途径~

1.1 EOS设计原则

它的核心是接口,希望能有效减少开发资源等待的弊端。

规范接口,接口要求前端,后端,测试人员都能看懂,能用。而且接口是直接引用到我们的程序中,任何更改都有相关的记录,相关人员的审核。

接口的可测试性。由测试人员直接介入到接口的测试中来,首先保证接口的正确性,给后面的联调工作带来无法估量的好处,理想情况一次通过。而且测试人员可以直接判断错误的来源,到底是前端还是后端

前端人员不用等待后端的开发,接口中就规定了模拟报文,前端人员直接开发,而且后面联调时代码也不需要调整,只需要更改一个参数,就可以自动切换到后端程序。

前端,后端,测试各司其职,互不影响,很好体现了解耦的思想

后端开发人员技术上跟现在没有变化,技术门槛低,直接开发到Spring的服务层就可以了,采用单元测试,更快提高效率

1.2 角色职责

定义的职责是大部分跟接口相关的,得接口得天下。

java后端开发程序员:和前端人员沟通,定义接口;不能私自更改接口,有更改必须更相关的前端人员沟通;对一些重要的调整,请跟小组长联系;

前端开发人员:参与定制接口;

小组长:检查程序员接口的规范性,是否存在没必要的冗余;协助程序员和前端人员的沟通;直接参与一些重要模块的接口定制;切实起到小组长的作用,在做审批接口的时候,最好能够做代码审查,包括单元测试是否完备。

测试人员:参与到接口的测试中来;以后技能提升了以后,可以直接参与单元测试(我们考虑开发一些工具(API),实现自动测试);有一个不成熟想法,前端,后端各自提交成果测试,测试完成后,由测试这边统一联调(个人认为基本上工作量不会很大了)。

1.3 系统组成

eos包含以下几部分:

服务提供端Server:后端开发者重点关注,根据提供的eos-server.jar包开发应用。

中心管理端:包含控制端eos程序和界面配置端uddi程序。也是直接部署即可。

客户端Client:前端开发者重点关注,根据eos-client.jar包和jquery.eosremote.js开发应用。

zookeeper:此程序为第三方应用直接下载部署即可,它是整个系统的强依赖,提供服务端服务信息注册,提供eos管理端在线注册。

系统的组成部署图如下:


1.4 系统执行流程

服务端发布服务,挂接到eos,客户端取得挂接有需要调用的服务的eos地址进行服务请求,eos对请求进行过滤代理请求服务端服务数据,返回给客户端,具体图解如下:


可见,多了模拟数据获取的支持,eos轻松实现前后端分离开发,两端不需要同步等待。并且,系统采用分布式思想,可以部署多台服务端和eos中心端以及客户端,轻松实现平行扩展。

2.快速开始

2.1 核心部件部署

(1)zookeeper:此程序为第三方应用直接下载部署即可。

(2)中心管理端:包含控制端eos程序和界面配置端uddi程序。也是直接部署即可。注意根据配置文件说明更改对应参数即可。

eos程序的配置eos.properties:

#zookeeper服务端的ip地址
zookeeper_ip=192.168.0.224
zookeeper_port=2181
#eos的标识,服务端挂接服务需要知道此id
eos_id=ulyn
#client能够访问到eos的本机ip地址
local_ip=192.168.0.60
#服务请求的端口号,默认值为5555
eos_port=5555
#eos的模式,默认是为pro部署模式,当为dev开发模式,开发模式允许mock模拟,否则不管客户端是否指定mock请求都直接调用真实服务
eos_mode=dev


2.2 服务端开发

(1)申请应用接入:访问uddi管理界面,注册应用,取得appid,例如:ihome

(2)可以使用eos-server-example工程

(3)制定服务接口:根据项目功能需求制定java接口类Test.java,见3.2.1

(3)在uddi管理中进行接口java文件上传,由小组长审核

(4)实现接口类

(5)修改配置文件eos-server.properties参数,启动系统

#zookeeper服务端的ip地址
zookeeper_ip=192.168.0.224
zookeeper_port=2181
#挂接的eos_id,挂接多个eos请使用逗号隔开
eos_id=ulyn
#eos能够连接到server的本机ip
local_ip=192.168.0.60
#netty服务器的端口,默认是5555
netty_server_port=10085
#应用id
app_id=ihome


2.3 客户端开发

(1)在uddi管理中下载接口java文件Test.java,下载得接口文件如下:

package com.sunsharing.component.test;
import com.sunsharing.eos.common.annotation.ParameterNames;

import com.sunsharing.eos.common.annotation.EosService;

@EosService(version="1.0",appId="ihome",id="test")
public interface Test {
/**
* 说hello
* @param abc
* @return
* ${ulyn}
* ulyn
*/
@ParameterNames(value = {"abc"})
String sayHello(String abc);

}


(2)使用eos-client-example工程,将接口放对应的包路径com.sunsharing.component.test,即下载的java文件的package。

(3)java使用者直接使用接口:

Test test = ServiceContext.getBean(Test.class);
test.sayHello("hello");


(4)前端,使用js辅助插件jquer.eosremote.js,依赖jquery.js和json2.js

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script type="text/javascript" src="/js/jquery-1.7.2.min.js"></script>
<script type="text/javascript" src="/js/json2.js"></script>
<script type="text/javascript" src="/js/jquery.eosremote.js"></script>
<script>
$(document).ready(function () {
$.eosRemote({
url: "/remote",
serviceId: "test",
mock: "hexin",
method: "sayHello",
data: {"abc": "hello"},
success: function (data) {
alert("返回结果:" + data);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert(errorThrown);
}
});
});
</script>
</body>
</html>


(5)修改配置文件eos-client.properties参数,启动系统

zookeeper服务端的ip地址
zookeeper_ip=192.168.0.224
zookeeper_port=2181
#联调服务端ip,当有配置次参数时,可以指定从此server取得数据,不配置则走负载均衡随机取得一台服务
#debugging_server_ip=192.168.100.60
#全局控制是否使用mock,默认值为false
use_mock=false


3.使用说明

3.1 中心管理端

3.1.1 控制端eos

对于此系统,部署启动即可。jar包执行类为com.sunsharing.eos.manager.main.Eos,可以使用发包提供的批处理命令启动。

它作为客户端桥,调用服务端接口,主要实现以下流程:

(1)判断是否审批通过,不通过则不继续往下处理,直接返回服务未通过审核异常

(2)判断是否需要测试模拟,是模拟则直接返回模拟测试数据,不继续往下处理

(3)调用实际server服务

(4)调用监控逻辑

在此特别需要强调的是配置文件eos.properties, 约定配置文件的第一行请不要配置参数(因为使用校验框架resvalidate,下面的server和client也遵循此约定)。重点关注下eos_mode配置项,它是配置eos的模式,不配置则使用默认值pro部署模式,dev为开发模式,开发模式允许mock模拟。也就是说,当客户端client处指定请求mock数据时,我们eos会根据eos_mode来最终决定给模拟数据还是实际调用数据。假如eos_mode=pro,无论client是否指定mock,都返回实际调用数据。

3.1.2 界面配置端uddi

对于此系统,部署启动即可。可以使用发包提供的批处理命令启动也可以部署在web容器使用。

提供服务接口的上传发布并进行审核、下载。

直观显示服务状态

此部分使用一看系统界面便懂,略过。。。

3.2 服务提供端Server

3.2.1 制定服务接口

根据项目功能需求制定java接口类,按照如下规约:

(1)接口类有@EosService的注解

(2)对于注解需要配置version参数,接口升级需要升级version参数

(3)对于注解,id参数默认不需要配置,值为类名第一个字母小写,一个应用不能有相同的id的服务

(4)请不要使用方法的重载,也就是说方法函数名不要重复

(5)请使用常用java类型,int,boolean,String,Map,List等,请不要使用自定义POJO类,如User、Animal....

(6)接口方法详细根据javadoc进行注释

(7)配置mock参数,作为模拟测试用的返回值。写在javadoc注释的@return,每一种mock使用${}紧接描述,接着空一行写模拟值,模拟值使用json格式(简单类型直接写值,对象用{}格式,数组对应[]格式)。如:

/**
* 取得num条List
*
* @param num
* @return ${success}当入参name="criss"为成功输出
*         [{"success":"成功了2",
*         "haha":"haha2"}]
*         ${error}当入参为其他时为错误输出
*         [{"error":"错误了2"}]
*/
List getList(int num);


3.2.1 暴露服务端

在服务端系统启动处加入启动代码:

com.sunsharing.eos.server.EosInit.start(ctx,"com.sunsharing");


ctx为Spring的ApplicationContext,也就是说服务接口如果是有Spring实现的,必须在启动初始化时候入参传入。上述代码意思是将在com.sunsharing扫描服务接口并进行注册等事件。

服务端开发在于对接口的实现,当接口有多种实现时,系统只默认取一个实现方式,所以,请尽量 不要有接口的多种实现。如果确实有多种接口,请指定,否则可能取的不是你想要的实现方式。指定可以使用配置文件EosServiceConfig.xml,主要在于impl,也就是服务的实现类,当实现类有多种,可以指定一种实现。格式如下:

<beans>
<bean id="beanId" impl="com.sunsharing.eos.server.test.TestService"/>
</beans>


3.3 客户端Client

对于客户端的使用,支持两种方式,java client和js client。当然,使用的接口均是服务端制定出来的,从uudi系统下载的接口文件。将下载的服务接口文件放在工程相应的位置,对于要快速开发使用的可以直接使用eos-client-example工程,请确保服务接口一定是从uudi系统下载下来的,并且不去更改它。

3.3.1 基本功能使用

在客户端端系统启动处加入启动代码进行接口初始化:

com.sunsharing.eos.client.EosInit.start("com.sunsharing");


java使用者直接使用接口:

Test test = ServiceContext.getBean(Test.class);
test.sayHello("hello");


前端,使用js辅助插件jquer.eosremote.js进行接口调用,依赖jquery.js和json2.js,系统需要在web.xml文件中配置Servlet提供js请求,配置代码如下:

<servlet>
<description>remote servlet</description>
<display-name>remote servlet</display-name>
<servlet-name>remoteServlet</servlet-name>
<servlet-class>com.sunsharing.eos.client.RpcServlet</servlet-class>
<init-param>
<param-name>scanPackage</param-name>
<param-value>com.sunsharing</param-value>
</init-param>
<init-param>
<param-name>sysParamVar</param-name>
<param-value>com.sunsharing.component.sys.ParamVarImpl</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>remoteServlet</servlet-name>
<url-pattern>/remote</url-pattern>
</servlet-mapping>


下面对上述配置进行说明:

(1)scanPackage:表示eos-client初始化时候扫描的服务接口的路径,同EosInit.start("com.sunsharing");。所以当有进行RpcServlet配置时,在系统启动不需要再写EosInit.start进行初始化。当然,写了也不会重复初始化。

(2)sysParamVar:配置系统变量获取com.sunsharing.eos.client.sys.SysParamVar的实现类,该实现类需要开发者自行编写,可以根据变量名取得系统缓存的变量的值。此类是为了支持js入参值为${}变量形式获取的接口,当入参为后台变量形式,如入参userId=${userId},那么后台java端将使用该实现类取得userId的值。

jquery.eosremote.js使用参数说明:它借鉴jquery ajax使用的格式,特别的,前端提出的一个需求,前端开发使用跨域取值请求,而实际整合联调项目时走正常同域请求,因此开发时候可以修改下jquery.eosremote.js文件的dataType参数为jsonp即可实现跨域,当实际联调时候再改回json。

PropertyTypeDefaultDescription
urlString"/remote"RpcServlet配置的请求地址
serviceIdString''调用服务id
methodString''调用服务的方法
mockString""指定要获取的模拟的数据值,对应接口文件的@return的${}
dataObjectnull方法入参值
beforeSendfunctionfunction (XHR) { }发送请求前
successfunctionfunction (data, textStatus) { }请求成功后
errorfunctionfunction (XMLHttpRequest, textStatus, errorThrown) { if (console) { console.info(XMLHttpRequest); } }请求异常时

3.3.2 使用数据模拟

eos在设计上一个重要功能就是分离前后端,可以使用模拟的数据返回,使得前后端不需要同步等待。

(1)上述前端调用的js插件入参mock即可指定服务的模拟参数

(2)还可以在配置文件EosServiceConfig.xml设置模拟参数,如下:bean上的mock表示此服务接口所有服务都是指定success的模拟,但是当配置具体方法的mock时,则使用具体方法的mock,下面服务的sayHello走的是error的模拟。

<beans>
<bean id="testService" mock="success">
<methods>
<sayHello mock="error"/>
<getList mock="error"/>
</methods>
</bean>
</beans>


(3)关注配置文件eos-client.properties的use_mock:默认不配置时为false。当配置use_mock=false时,任何的mock配置都无效,服务请求直接走真实服务调用。

总结上述三点,mock参数的指定需要有优先级关系,use_mock是全局性的控制,优先级最高。当use_mock为false时,任何mock都无效,这可以节省实际部署时候前端去除mock参数的事情。js接口指定mock参数优先级次之。xml配置的mock参数优先级最低。

3.3.2 服务AOP(后续可能改造重构)

有时候,我们在使用服务接口时候,我们需要在调用服务前或者调用服务后做一些事情,结合aop设计思想,我们提供了简单的aop功能。下面我们以一个例子来说明。

例子:我们需要在调用用户登录服务后,将用户记录到Session

(1)实现Advice的实现类LoginAdvice,它有两个方法,分别是调用前和调用后。

public class LoginAdvice implements Advice {
@Override
public AdviceResult before(ServiceMethod method, Object[] args) {
System.out.println(method.getMethodName() + "被执行前,入参为" + Arrays.toString(args));
System.out.println("RpcServletContext.getRequest=" + RpcServletContext.getRequest());
return new AdviceResult(false, null);
}

@Override
public AdviceResult after(ServiceMethod method, Object[] args, Object returnVal) {
System.out.println(method.getMethodName() + "被执行后,returnVal=" + returnVal);
//设置session
RpcServletContext.getRequest().getSession().setAttribute("user",returnVal);
return new AdviceResult(true, returnVal);
}
}


(2)配置EosServiceConfig.xml,指定方法login,采用loginAdvice进行切面

<beans>
<bean id="loginService">
<methods>
<login advice="com.sunsharing.component.test.LoginAdvice"/>
</methods>
</bean>
</beans>


注:

所有的切面类均要实现Advice接口。

RpcServletContext.getRequest()的使用,它是对ThreadLocal进行简单的封装,对于请求前端从RpcServlet请求的服务,使用此静态方法可以取得请求的HttpRequest。如果是java直接调用那么将得到空值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: