业务与框架分离利器

业务与框架分离利器

前言

会有一系列的文章介绍common-*.jar的各种用法,这些工具类jar包都已上传在maven中央库。可以直接通过maven坐标引入使用。源码可以参见:https://gitee.com/rjzjh/common

场景分析

现在互联网框架层出不穷,知识也是一个更新的过程,从传统行业的SSH,dubbo,grpc,springcloud,满足不同场景的业务需求,应该说框架技术没有好坏之分。但由于历史原因,我们经常性的有系统改造需求,如果是功能性的重新划分,连数据模型都推倒重来,那要考虑的问题倒少了,但如果修修补补,甚至只是切换一下底下的实现方式,把http改为rpc的,把原来的那种单体的改为微服务体系架构,如果之前没有把业务与框架做分离,那改起来也是一个较头疼的任务,单一个接口的输入输出就够喝一壶的。如果我们实现一个与业务无关的封装,把接口输入输出这部管控好,那对于以后的框架转换,系统升级那就是分分钟的事了。common/connector的工具模块就是解决这一痛点而来。

common-connector

这是一套把 xml文件定义的协议转换成统一的DynaBean的工具,这样在做接口的时候就能达到一致的输入和输出参数。那么下面的框架就是业务用来传输的一层包裹的壳而以。这是它的maven坐标:

1
2
3
4
5
<dependency>
<groupId>net.wicp.tams</groupId>
<artifactId>common-connector</artifactId>
<version>最后版本</version>
</dependency>

下面是它的DTD文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<!--动态Bean定义 #PCDATA-->
<!ELEMENT InterFaceMapping ((PropertyIn, PropertyOut))>
<!ELEMENT PropertyOut ((COL*))>
<!ELEMENT PropertyIn ((COL*))>
<!ELEMENT COL (#PCDATA|COL)*>
<!--
@attr type 字段类型
-->
<!ATTLIST COL
name ID #REQUIRED
alias CDATA #IMPLIED
type (string|integer|doubler|object|datetime|dynaBean|javaBean|bytes|enums) "string"
gtype (single|array|map |list) "single"
isnull (true|false) "true"
defaultValue CDATA #IMPLIED
length CDATA #IMPLIED
min CDATA #IMPLIED
max CDATA #IMPLIED
format CDATA #IMPLIED
className CDATA #IMPLIED
valueName CDATA #IMPLIED
strict CDATA #IMPLIED
convert CDATA #IMPLIED
>

它技术的数据类型一些基本数据类型,还有一些dynaBean,javaBean等复合类型。也就是说,connector会自动的把你传入的参数,转换为你定义好的这些字段类型,也支持array,map,list等集合。connector模块也会做一些简单的校验,如:min,max,isnull,如果你传的参数不满足,那么connector模块会自动挡住你的请求,并提供一致的错误提示。你的业务代码也就不会被这些普遍性的校验给淹没了,更为进一步的是connector提供了接口层面的缓存(可配置的),只要的输入的参数是一样的,第二次就直接拿缓存的结果返回给用户。这对于字典类型查询接口非常有帮助。还提供测试数据的自动生成,也就是说只要通过xml文件定义好了接口,那么后端就可以直接布署,前端就能马上开发了,前后端同时开发就有了有力保障,比用第三方生成测试数据来做前后端分隔开发的优势是:它不需要另外配置,也就不用担心文档与接口的不同步问题了。

接口示例

先定义好xml的接口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE InterFaceMapping PUBLIC "-//andy.zhou//dynabean desc//ZH"
"https://gitee.com/rjzjh/common/raw/master/common-connector/src/main/resources/conf/dynabean.dtd">
<InterFaceMapping>
<PropertyIn>
<COL name="format" alias="format" type="string" format="^[0-9]{4}\\-[0-9]{1\,2}\\-[0-9]{1\,2}">格式注意,号也要转义</COL>
<COL name="single" alias="single" type="string">简单单值</COL>
<COL name="len" alias="len" type="string" length="5">限长单值</COL>
<COL name="isnotnull" alias="isnotnull" type="string" isnull="false">不允许为空4444</COL>
<COL name="defaultValue" alias="defaultValue" type="string" defaultValue="平安健康">带默认值</COL>
<COL name="ary" alias="ary" type="string" gtype="array" length="10">数组</COL>
<COL name="map" alias="map" type="string" gtype="map" length="10">Map</COL>
<COL name="list" alias="list" type="string" gtype="list" length="10">List</COL>
</PropertyIn>
<PropertyOut>
<COL name="retMsg" type="string" convert="yesorno">返回信息</COL>
</PropertyOut>
</InterFaceMapping>

设值与取值示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@BeforeClass
public static void initCalss() {
try {
conf = ConfigClassXml.createConfigClassXml("TestString", dir,
"TestString.xml");
//dynabean = conf.parserInputNoCI().newInstance();
dynabean = conf.newInputBean();
} catch (ProjectException e) {
e.printStackTrace();
}
}

@Test
public void testSingle() {
dynabean.set("format", "1988-01-01");
Assert.assertEquals(dynabean.getStrValueByName("format"), "1988-01-01");

}

@Test
public void testArray() {
dynabean.set("ary", new String[] { "ary1", "ary2" });
dynabean.set("ary", 1, "ary3");
Assert.assertArrayEquals(new String[] { "ary1", "ary3" },
(String[]) dynabean.get("ary"));
}

@Test
public void testList() {
List<String> inputlist = new ArrayList<>();
inputlist.add("list1");
inputlist.add("list2");
inputlist.add("list3");
dynabean.set("list", inputlist);
dynabean.set("list", 2, "list4");
String str = (String) dynabean.get("list", 2);
Assert.assertEquals("list4", str);
}

@Test
public void testMap() {
Map<String, String> inputmap = new HashMap<String, String>();
inputmap.put("key1", "value1");
inputmap.put("key2", "value2");
inputmap.put("key3", "value3");
dynabean.set("map", inputmap);
dynabean.set("map", "key4", "value4");
// dynabean.set("map", "value5");
String str = (String) dynabean.get("map", "key4");
Assert.assertEquals("value4", str);
}

@Test(expected = IllegalArgumentException.class)
public void testNotNull() {
dynabean.set("isnotnull", null);
}

@Test
public void testDefaultValue() {
String defaultValue = (String) dynabean.get("defaultValue");
Assert.assertEquals("平安健康", defaultValue);
}

@Test(expected = IllegalArgumentException.class)
public void testLength() {
dynabean.set("len", "123456");
}

Executor调用

这个是主体,用于把业务与框架脱离,所有的业务接口,都必需实现IBusiApp接口,这个接口只有一个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
/***
* 业务必须实现的接口
*
* @param inputBean
* 输入参数
* @param outBeanOri
* 输出参数的原型,最后返回的结果是在原型基础上设置好返回值
*
* @return
*
* @throws ProjectException
*/
public CusDynaBean exe(CusDynaBean inputBean, CusDynaBean outBeanOri) throws ProjectException;

inputBean就是输入参数,outBeanOri就是输出参数用的动态Bean,它只是一个空壳子,没有数据,用于规则业务接口的输入参数。它的反回值就是业务接口拿业务数据填充好outBeanOri后把它返回出调用者。

一个接口示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<InterFaceMapping>
<PropertyIn>
<COL name="jobName" alias="jobName" type="string" isnull="false">任务名称</COL>
<COL name="jobGroup" alias="jobGroup" type="string" isnull="false">任务分组</COL>
<COL name="cronExpression" alias="cronExpression" type="string" isnull="false">cron表达式</COL>
<COL name="isActiv" alias="isActiv" type="enums" className="net.wicp.tams.common.constant.dic.YesOrNo" isnull="true">任务状态,是否活动任务,默认为是</COL>
<COL name="isConcurrent" alias="isConcurrent" type="enums" className="net.wicp.tams.common.constant.dic.YesOrNo" isnull="true">任务是否有状态,默认为无状态</COL>
<COL name="springName" alias="springName" type="string" isnull="true">spring管理的bean名字</COL>
<COL name="beanClass" alias="beanClass" type="string" isnull="true">不受spring管理的类名</COL>
<COL name="description" alias="description" type="string" isnull="true">任务描述</COL>
</PropertyIn>
<PropertyOut>
<COL name="jobId" type="object">结果</COL>
</PropertyOut>
</InterFaceMapping>

输入参数

这个接口也有自己的规则,下面以json格式来说明接口规则,connector模块也支持直接把json数据转成输入参数,看下面业务接口的输入参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"jobName": "test2",
"jobGroup": "demo",
"cronExpression": "0/5 * * * * ?",
"isActiv": "yes",
"beanClass": "net.wicp.tams.demo.springboot1.service.job.TestJob",
"ControlInfo": {
"requestCommand": "quartz.add",
"senderSystem": "IV",
"senderApplication": "Hammer",
"version": "1.0",
"senderChannel": "H5",
"msgId": "aaaaa"
}
}

ControlInfo是接口的控制参数,每个接口都需要有,其中:
requestCommand:接口唯一标识
senderSystem:发送系统
senderApplication:发送应用,它应该是发送系统中的一部分
senderChannel:发送渠道,它应该是发送应用的一部分
version:接口版本
msgId: 请求唯一标识,用于故障定位等场景
其它的参数就是业务参数,它们必须严格按照上面xml的参数定义,如果有任何不符合的都会被connector模块给自动挡回,省去了业务代码的参数检查。

输出参数

下面给出输出参数的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"jobId": "7",
"errorDesc": "没有异常",
"errorValue": "1000",
"errMsg": "操作成功",
"errorCode": "no",
"http": "200",
"respInfo": {
"receiptSystem": "IV",
"receiptApplication": "hammer",
"msgId": "aaaaa",
"msgIdResp": "aaaaa"
}
}

jobId 是业务接口需要返回值与connector无关
errorDesc 模块自动生成:错误描述,与errorValue是对应的
errorValue 模块自动生成:错误值。
errorCode 模块自动生成:错误代码
errMsg 模块自动生成:错误信息,用于开发人员定位异常。
http 模块自动生成:http返回编码。
respInfo 模块自动生成:返回消息元数据,有接收系统、接收应用,消息请求id,消息回复id

与springboot结合

需要引入jar包:

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/net.wicp.tams/common-spring -->
<dependency>
<groupId>net.wicp.tams</groupId>
<artifactId>common-spring</artifactId>
<version>最后版本</version>
</dependency>

executor统一调用的path:/connector eg: http://localhost:9090/connector

坚持原创技术分享,您的支持是我前进的动力!