文档章节

前后端分离实践(EOS篇)

ulyn
 ulyn
发布于 2014/10/07 14:23
字数 3857
阅读 1184
收藏 11

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。

Property Type Default Description
url String "/remote" RpcServlet配置的请求地址
serviceId String '' 调用服务id
method String '' 调用服务的方法
mock String "" 指定要获取的模拟的数据值,对应接口文件的@return的${}
data Object null 方法入参值
beforeSend function function (XHR) {     } 发送请求前
success function function (data, textStatus) {     } 请求成功后
error function function (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>

注:

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

  2. RpcServletContext.getRequest()的使用,它是对ThreadLocal进行简单的封装,对于请求前端从RpcServlet请求的服务,使用此静态方法可以取得请求的HttpRequest。如果是java直接调用那么将得到空值。                         

© 著作权归作者所有

ulyn
粉丝 56
博文 16
码字总数 18070
作品 1
厦门
程序员
私信 提问
加载中

评论(2)

ulyn
ulyn 博主

引用来自“黄勇”的评论

感谢博主的分享!我基本上看明白了,只不过有一个小问题:
在“服务端开发”中,首先“在uddi管理中进行接口java文件上传”,然后“实现接口类”,这意味着接口是不能变更的,否则实现类也需要做相应的调整。
我的问题是,如果说某服务已经被其它系统调用,此时需要对改服务进行改造(接口需要变更),那么可能会影响到调用该服务的系统,这类情况应该如何保证系统的可用性呢?
我的建议是,能否增加“服务版本号”这个概念?客户端调用该服务时,需要指定具体的版本号,当部署新的服务时,可以保留旧的服务,在系统中可以查看旧服务的调用情况,如果此时不再有任何的系统调用旧服务,则可以动态将该旧服务卸载掉,同理,新服务的安装也是动态的,整个服务平台不会重启。
感谢您的关注! “(2)对于注解需要配置version参数,接口升级需要升级version参数 ”在定义的接口中是必须指定版本号的。在eos部署为产品模式pro(配置文件的eos_mode参数)的系统中,服务版本是共存的。而eos部署为开发模式dev的系统中,服务版本要求是最新的。所以实际生成环境中,新旧服务的更替基本不会有此顾虑。
黄勇
黄勇
感谢博主的分享!我基本上看明白了,只不过有一个小问题:
在“服务端开发”中,首先“在uddi管理中进行接口java文件上传”,然后“实现接口类”,这意味着接口是不能变更的,否则实现类也需要做相应的调整。
我的问题是,如果说某服务已经被其它系统调用,此时需要对改服务进行改造(接口需要变更),那么可能会影响到调用该服务的系统,这类情况应该如何保证系统的可用性呢?
我的建议是,能否增加“服务版本号”这个概念?客户端调用该服务时,需要指定具体的版本号,当部署新的服务时,可以保留旧的服务,在系统中可以查看旧服务的调用情况,如果此时不再有任何的系统调用旧服务,则可以动态将该旧服务卸载掉,同理,新服务的安装也是动态的,整个服务平台不会重启。
极速上手进阶大神的 5 门 SpringBoot 课程

SpringBoot 因其大大简化了配置。可快速启动一个应用的开发环境,把时间和精力更多的投入到实现业务需求上去。成为了Java 服务器端近几年特别火爆的框架。 如此火爆的技术,怎能不抢先了解一...

实验楼
08/16
0
0
前后端分离,最佳实践

文章目录 1.前后端分离是什么 2.为什么需要前后端分离 3.前后端分离,最佳实践 3.1.简单分离模式 3.2.服务端渲染模式 3.2.1.方式一,JSP渲染 3.2.2.方式二,静态页渲染 3.3.Node.js渲染模式 ...

郭恩洲_OSC博客
2016/11/21
375
1
佛山巨量数据技术有限公司/bd-shop

BDShop ##交流QQ群:107304892 2017-10-03 半年第一次更新了 修复所有bug 请大家更新一下 BDShop是国内少有前后端完全分离的java商城项目. BDShop 系统简介 BDShop商城系统是B2C模式的电子商城...

佛山巨量数据技术有限公司
2017/04/24
0
0
细说 Django—web 前后端分离

本周为大家带来【51Reboot】运维自动化公开课分享,4 月 26 日(周四)晚九点由我们的咸鱼老师为大家细说 Django — web 前后端分离。 讲师介绍 咸鱼老师:曾担任互联网二手车公司运维开发负...

xjtuhit
2018/04/25
0
0
前后端分离的思考与实践(一)

原文出处:淘宝UED - 常胤 也谈基于NodeJS的全栈式开发(基于NodeJS的前后端分离) 前言 为了解决传统Web开发模式带来的各种问题,我们进行了许多尝试,但由于前/后端的物理鸿沟,尝试的方案...

淘宝UED - 常胤
2014/04/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

python学习10.04:Python list列表使用技巧及注意事项

前面章节介绍了很多关于 list 列表的操作函数,细心的读者可能会发现,有很多操作函数的功能非常相似。例如,增加元素功能的函数有 append() 和 extend(),删除元素功能的有 clear()、 remo...

太空堡垒185
14分钟前
3
0
新手插画学习的方法?教你如何自学?

插画学习的方法?教你如何自学? 从小喜欢画一些漫画头像随笔画,但是其实没有基础。个人偏好小清新手绘风的插画(如下图),每每看到都希望自己能画出这样的作品。 我其实很想说画这种美术功...

huihuajiaocheng
20分钟前
4
0
面试题

1、实现clone();

gtandsn
31分钟前
4
0
CentOS 7 部署 tesseract-ocr

官方地址 github yum-config-manager --add-repo https://download.opensuse.org/repositories/home:/Alexander_Pozdnyakov/CentOS_7/ 若提示 yum-config-manager: command not found 执行以......

阿白
31分钟前
3
0
JAVA比较器中comparator的使用

一个专用的比较器Comparator Comparator是一个专用的比较器,当一个不支持自比较或者自比较函数不能满足要求时,可写一个比较器来完成两个对象之间大小的比较。Comparator体现了一种策略模式...

daxiongdi
32分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部