业务与框架分离利器

前言

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

场景分析

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

common-connector

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

		<dependency>
			<groupId>net.wicp.tams</groupId>
			<artifactId>common-connector</artifactId>
			<version>最后版本</version>
		</dependency>

下面是它的DTD文件

<?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的接口文件

<?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>

设值与取值示例

	@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接口,这个接口只有一个方法

/***
	 * 业务必须实现的接口
	 * 
	 * @param inputBean
	 *            输入参数
	 * @param outBeanOri
	 *            输出参数的原型,最后返回的结果是在原型基础上设置好返回值
	 * 
	 * @return
	 * 
	 * @throws ProjectException
	 */
	public CusDynaBean exe(CusDynaBean inputBean, CusDynaBean outBeanOri) throws ProjectException;

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

一个接口示例

<?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数据转成输入参数,看下面业务接口的输入参数:

{
    "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模块给自动挡回,省去了业务代码的参数检查。

输出参数

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

{
    "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包:

<!-- 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

偏锋书生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值