前言
已第二次使用AngluarsJS来进行业务开发.虽然队友们对此报有一些情绪与意见.认为比较难用,主要集中在性能方面的考虑.但是我却认为我们从根本上改变了整个前端开发的模式.更多引出了许多前的系统设计方案.起码从开发来说,开发效率,代码可维护性,简易性,条理性都有本质上的提升.
其中AngluarsJS提供了在前端MVC分层的可行性.(其实未必一定使用AngluarJS,只要能让项目以MVC的模式分层则可)改变以往使用jQuery之类的库时,大量的业务代码与业务逻辑代码有着高度的耦合.基本上每个页面的js代码只服务一个业务,无法有效的进行复用业务内容.然而这些情况已经被AngluarJS改变,通过框架提供的Service服务,IOC,Controller,数据双向绑定等,已经可以有效的划分业务代码与操作逻辑,其实也就是MVC,由此业务代码可以独立编写,带出了很多专于某业务的业务代码
.
最终,在前端已有MVC框架情况下(常规来说不叫MVC,准确地应是MV*,但这里先此当作MVC来进行讨论),我想直接将这种思路发展得更极限与彻底:服务端是一个可以持久化的json对象,前端仅需要与持久化json对象进行交互。
思路说明
将服务端抽象成一个持久化Json对象存储器.前端通过ajax来对持久化对象来进行操作,包括增加,修改,删除,查询等方式。
服务端操作则是通过对前端对象操作事件的一观察者,并且所有的输出直接返回给前端,而与前端一样,都是是持久化对象。
最终是将数据模型(MODEL)向前移,前端可直接访问。
最终是将数据模型(MODEL)向前移,前端可直接访问。
详细说明
上文可能觉得比较难理解。现在详细说说。
以往开发会在服务端定义若干个服务接口,调用具体业务的接口方法,返回对应的结果数据。各方法间是独立的。每个接口都会定义一套具体的参数。返回结果的具体形式,各结果具体的意义是什么。严格说,这种写法属于接口驱动型,或是业务方法驱动型。
然而现在模式不同, 不是独立地提供接口,而是直接提供一个对象,准确地说是一个虚似的json对象。服务端说明这个对象中各成员变量的意义则可。
分析实例
在微博平台中
功能1:一个用户向一微博进行评论。
功能2:收藏一个微博。
前端面向接口做法
定义一个评论接口:
Response
setCommentInTwitter(Long
TwitterId, String context);
方法会定义一个微信的id,与文档的参数。然而为了灵活调整参数,会定义如下方法
Response
setCommentInTwitter
(CommentAddForm commentAddForm
);
通过CommentAddForm类来封装需要提交的参数,如果需要增加参数而直接在Form类里头增加成员。
如果需要增加功能话,则服务端需要增加业务方法
例如现在需要增加用户收藏微博功能,则需要如下
Response collectTwitter(Long twId);
这里参数就比较简单,但是需要服务端增加对应的方法。
*前端面向对象的做法
假设现在服务端的存储数据结构如下:
{
twitters:{
"1":{
context: "我快没有水啦,各位赶紧给我加水!!!",
createTime:"2015-12-01",
comments:{
"1":{
context:"你是个水桶?",
createUser:{$ref:"user.1"},
createTime:"2015-12-01"
},
"2":{
context:"头脑里头是有水的",
createUser:{$ref:"user.3"},
createTime:"2015-12-01"
}
}
}
}
users:{
"1":{
account:"zhengmx",
email:"tooze2003@163.com",
nickname:"代码牧",
favor:{
"1":{$ref:'twitters.1'}
},
createTime:"2015-12-01"
},
"3":{
account:"yangsl",
email:"xxys@163.com",
nickname:"杨圣琳",
favor:{},
createTime:"2015-12-01"
}
}
$login:{
curUser:{$ref:'users.3'}
}
}
例如现在增加评论,则可以在前端js代码如下操作:
var twitter = ...//传入微博对象,假设id为1
var twitterComment = {
context:"的确是一个大水桶"
}
//传入的微博评论内容
var objSer = ObjService.getService(); //提到与服务端对象对接的封装方法,简化前端的使用
//用户提交评论
objSer.push(twitters_id+'.comments',twitterComment,function(resp){
console.info('resp',resp);//将返回结果打印。
} );
此时服务端数据如下:
"twitters.1":{
context: "我快没有水啦,各位赶紧给我加水!!!",
createTime:"2015-12-01",
comments:{
"1":{
context:"你是个水桶?",
createUser:{$ref:"user.1"},
createTime:"2015-12-01"
},
"2":{
context:"头脑里头是有水的",
createUser:{$ref:"user.3"},
createTime:"2015-12-01"
},
"3":{
context:"的确是一个大水桶",
createUser:{$ref:"user.3"},
createTime:"2015-12-01"
}
}
}
//收藏一个微博
objSer.push('$login.curUser.favor',twitter,function(resp){
console.info('resp',resp);//将返回结果打印。
} );
此时服务端数据如下:
"usesr.3":{
account:"yangsl",
email:"xxys@163.com",
nickname:"杨圣琳",
favor:{
"1":{$ref:'twitters.1'}
},
createTime:"2015-12-01"
}
实例分析
通过后者来开发的话,服务端则不需要增加接口。也就是说,前端基本上可以直接操作数据库(json对象),不需要后端程序编写加入评论,收藏业务。
在当前场景下,直接操作当前用户的信息,不需要做统计类的信息数据时,可以直接操作业务对象, 在前端进行业务方法的编写。
其中配合AngluarJS,可以将大量方法以Service的方式进行封装。
如TwitterService,UserService。
而Controller则负责将数据分解,将Service的返回的数据转成对应的VO对象。此时可以大量减少前后端之间的代码开发,前端开发时可以直接考虑数据模型的对称,不需要太多地与后端接口进行沟通与对接。如果需要简单修改代码时,只需要更新前端代码,将对应的js代码更换则可,不需要重新布署服务端代码。
整体模式梳理
服务通过抽象出一个可以持久化的实体对象,前端可以直接操作对象,通过此来保存,查询数据。通过设置对象来建立数据之间的关系。
通过AngluarJS等框加,可以在前端对部份业务方法进行封装。
一些业务方法并非都是在前端开发,部分业务方法实则在服务端编写,而编写是围绕抽象的对象进行操作,通过监听的操作来实现后端的业务。
后端不直接向前端提供业务方法,而是通过对象的操作来向前端反馈信息。而后端的逻辑最终输出也是设入对象中,然后供前端进读取。
其实此架构方案并非说完全不需要后端代码,而是将一部份后端
业务逻辑代码往后移,放在实体的后端,不直接与前端交互。
原模式:
js -> Action -> Service -> DAO -> DB(数据库)
现在型式:
js -> Obj <-> Listen(Service)
前端后端代码结合例子:
用户登录:
前端代码:
var = loginInfo{
curUsr: {
account:"zhengmx",
password:"123456"
}
};
objServer.push('login.',loginInfo,function(resp){
$scope.loginMsg = resp.data.loginMsg;
if(resp.data.loginCode==1){//登录成功
page.toIndex();//跳首页
}else{
}
});
后端代码:
//监听某个对象的写入,业务完成成将输出写入对象中,而非直接返回给前端,此方法与前端无保直接交互
@ListenSet(id="login");//注解代表的是监听set入login.curUser的动作,而非直接提供一个接口方法
public Obj login(Obj rt, Obj newValue, Obj oldValue){
List<Obj> rs =
rt.find("users.name="+newValue.get("curUser.account")+"&users.password="+newValue.get("curUser.password"));
if(rs.size()>1){
newValue.get("curUser").ref(rs.get(0));//将新值置为引用持久化对象中用户的对象。
login.put("sessionid", WebConf.getSession());//取得session,其实会通过cookie将session传送到浏览器中。
login.put("loginCode",1);
login.put("loginMsg","登录成功");
}else{
login.put("loginCode",2);
login.put("loginMsg","登录不成功");
}
return newValue;//返回的是前端set入的对象,而是不响应的对象.
}
响应数据如下:
$login:{
curUser:{$ref:'users.3'}
sessionid:"D092A8DE4ED756EDFEAFE04F038B4086",
loginCode:1,
loginMsg:"登录成功"
}
服务端提供的服务汇总
服务端仅提供对持久化对象的编辑,查询操作。
编辑包括:
set:对指定的id位置直接赋值。
push:新增对象,其中由服务端来生成key值。
get:通过id得到需要的对象。
find:通过查询表达式查询对象。
理论上这些方法基本与业务无关,因为在性能优化上可以针对这些方法优化,主要都是走key/value的方式,与减少了后端的业务逻辑处理。因此在性能上会有较大提升。
持久化对象的设计
持久化对象的数据结构为json,一个服务则仅提供一个对象,此对象称为根,简称为rt。
对象的id为对象相对于
如果一个字段是引用其他对象,则使用{$ref:"objectid"}
后端js化
以上例子的监听方法是java代码。为了进一步轻量化系统开发的负担。则可以将监听方法js化。
开发者使用js写好监听方法后,事件触发时调用js代码,传入对应的dom对象。其中包括有rt对象。
登录后端代码改写成js后如下:
var $listen_login = function(rt,newValue,oldValue,webConf){
var rs =
rt.find("users.name="+newValue.get("curUser.account")+"&users.password="+newValue.get("curUser.password"));
if(rs.length>1){
newValue.curUser=rs[0];//将新值置为引用持久化对象中用户的对象。
login.sessionid= weConf.session);//取得session,其实会通过cookie将session传送到浏览器中。
login.loginCode=1;
login.loginMsg="登录成功";
}else{
login.loginCode=2;
login.loginMsg="登录不成功";
}
return newValue;//返回的是前端set入的对象,而是不响应的对象.
}
js化后,修改业务无需更去重启服务端,适合需要快速迭代开发的模式,尽早地将功能上线,而不需要走冗长的升级程序,编译等。
数据权限方案
<待编写>
架构优势
提高前端的业务范围开发能力,减少服务端的复杂性。
缓存优化难度减少,服务端性能可以快速得升,得更加轻量。
升级系统成本减少,部份业务可以不需要重新升级程序,时改时生效。
适用场景分析
操作的数据简单,针对当用户数据操作较多,且不需要提交大量数据;
用户对实时应响要求高,操作反馈多,逻辑复杂。
功能更新频繁,迭代次数高;
事务要求较少;
无做大量统计的统计操作;
数据模型无需要大量互相引用;
。
不适用场景
大量实时统计需求;
对事务要求较高;
权限要求细度高;
前端设备差,兼容要求高。
结语
目前这个思路还未得到充份的实践与验证,也是小弟在近段时间开发过程中想到的一些决解方案。
这种方案更多地偏像于基mongodb这种NoSQL模式的开发。然而仅仅是"像",mongodb数据库与理想中的Model的目标实现有一定的距离,在业务数据管理时并非一个较好的解决方案。
从这种思路来开发程序时,只是思路转改过来,开发企业级的业务管理系统的效率会非常的高,更多时间都是集中在前端用户体验,与业务流程上的考虑,轻量的服务端开发,更少了去考虑关系数据与服务端之间的交互,更少地去关心实体模型结构,更少地去定义接口,从而减少接口定义带来额外的沟通成本。
如果需要某某数据,则在前端代码增加需要的成员,有种“你要我就马上给你的感觉”。
后续我会对这个思路涉及到的代码不段时间,找出对应的实现方案,会开发出一个样例项目放在github中。
如果对此有兴趣,可以评论,可以私信我,更多地交流。