第一次亲密接触--系统集成
项目业务背景:该项目是XX公司为YY能源公司创建的企业内容管理(ECM,Enterprise Content Manager)解决方案。该项目分为两大部分,CM和BPM,其中CM创建并保存YY公司在生产及运营过程中产生的文档,而BPM则负责处理YY公司中的业务流程。同时,该项目还与YY公司现有的企业系统EAM进行集成,将EAM系统中的一些文档ECM进行存储。
项目开发背景:ECM系统包含了三个子系统,分别是CM系统,BPM系统与COMMON系统。CM负责文档的创建与保管;BPM调用DM中的文档,进行业务处理,BPM在业务流程中也可能会产生一些文档到BPM中;COMMON为DM和BPM提供业务参数配置,也完成外部系统(EAM)与内部系统的交互工作。我是CM中的一名开发工程师,前期负责几个CM中几个模块的开发工作,后期主要负责与其它系统(如BPM、EAM等)的集成。
CM中独立模块的开发工作就不多说了,根据需求分析说明书与需求跟踪矩阵,还有业务状态切换图来确认系统中每个业务的用例,从而完成该功能的开发。难度不大,最重要的是理解业务。我们主要讲解在系统集成过程中发生的一些问题。
对每一个未接触过集成的人来说,都觉得系统集成是一个高深莫测的东西。因为他们只是在系统内部的框架内完成接口的调用,不懂得换了框架之后,接口的实现会发生什么样的变化。对框架的依赖性太强,这是现代开发人员的一个弊病,其实与外部系统的集成从底层看来,就是使用其它框架的实现。
在BPM与CM进行集成时,既可能有BPM调用CM,也可能有CM调用BPM,但是无论怎么调用,归根到底都是为对方提供可行性的接口。我们以BPM调用CM为例,在代码中讲述集成。在系统集成中,有一个很重要的安全问题,那就是单点登录,由于此部分内容较多,这里不做详细介绍。
我们知道Servlet通过HTTP协议,在Request获取数据时,有GET、POST、PUT几种方式,我们(CM)在为BPM提供接口时,就需要考虑这几种常用的情况,考虑到BPM对CM的调用比较频繁,以及系统的可扩展性,我们提供一个抽象类,来实现支持这几种方式。为保证数据的正确性,我们在里面使用事务进行封装。
public abstract class AbstractRestController {private static final int POST = 0;private static final int GET = 1;private static final int PUT = 2;//使用Log4j记录日志protected static final Logger logger = Logger.getLogger(AbstractRestController.class);protected ThreadLocal<HttpServletRequest> request = new ThreadLocal<HttpServletRequest>();protected String body;private BackLoginModule module = null;/*** 使用注解处理POST请求*/@RequestMapping(method = RequestMethod.POST)public void handlePOSTRequest(@RequestBody String body,HttpServletRequest request, HttpServletResponse response)throws ServletException {this.body = body;process(request, response, POST);}/*** 使用注解处理GET请求*/@RequestMapping(method = RequestMethod.GET)public void handleGetRequest(HttpServletRequest request,HttpServletResponse response) throws ServletException {process(request, response, GET);}/*** 使用注解处理PUT请求*/@RequestMapping(method = RequestMethod.PUT)public void handlePUTRequest(@RequestBody String body,HttpServletRequest request, HttpServletResponse response)throws ServletException {this.body = body;process(request, response, PUT);}private void process(HttpServletRequest request,HttpServletResponse response, int type) {this.request.set(request);//使用事务,发生错误时可以回滚UserTransaction tx = null;try {tx = TransactionUtil.getUserTransaction();tx.begin();Object result = null;if(type == POST)result = processPost();else if(type == PUT)result = processPut();else if(type == GET)result = processGet();writeResponse(result, response);tx.commit();} catch (EcmException ex) {try {tx.rollback();} catch (Exception e) {e.printStackTrace();}logger.error("Error occurred when invoking remoting services.", ex);BizResponse br = new BizResponse();br.setErrorCode(ex.getCode());br.setErrorDescription(ex.getErrorDescrption());Map<String, String> map = new HashMap<String, String>();map.put(RestConstants.RESPONSE_BODY_CONTENT, body);br.setItem(map);writeResponse(br, response);} catch (Exception ex) {try {tx.rollback();} catch (Exception e) {e.printStackTrace();}logger.error("Error occurred when invoking remoting services.", ex);BizResponse br = new BizResponse();br.setErrorCode(ErrorCode.UNKNOW_ERROR);br.setErrorDescription(ErrorMessageHelper.getErrMessage(ErrorCode.UNKNOW_ERROR));Map<String, String> map = new HashMap<String, String>();map.put(RestConstants.RESPONSE_BODY_CONTENT, body);br.setItem(map);writeResponse(br, response);} finally {this.request.set(null);if(module != null){try {module.logout();} catch (Exception e) {// TODO: handle exception}}//if (EcmConfig.useTomcat()) if (EcmConfig.useSSO == false) {UserContextUtils.popSubject(); }// SubjectUtil.clear();ObjectStoreHelper.clear();}}protected Object processPost() throws EcmException {return null;}protected Object processGet() throws EcmException {return null;}protected Object processPut() throws EcmException {return null;}protected void writeResponse(Object bmpResp, HttpServletResponse response) {String body = null;if (bmpResp instanceof String) {body = (String) bmpResp;} else {body = JsonUtil.toJson(bmpResp);}response.setContentType("application/json");try {PrintWriter out = response.getWriter();out.print(body);} catch (IOException ioe) {logger.error("Error occurred when setting HTTP response via JSON.",ioe);}}protected HttpServletRequest getRequest(){return request.get();}}CM与BPM继承,并为提供接口时,新的接口(类)继承AbstractRestController抽象类,就可以通过几种不同的HTTP请求来进行数据传递,同时也保证了事务性。下面是为BPM提供的一个借阅接口,在这里我们使用了PUT方式。
@Controller@RequestMapping("/borrowProcessUpdate.do")public class UpdateBorrowStatusController extends AbstractRestController {//此处使用PUT方式@Overridepublic Object processPut() throws EcmException {UpdateBorrowReq req = JsonUtil.fromJson(body, UpdateBorrowReq.class);BorrowDocument[] borrowDocumentList = new BorrowDocument[req.getBorrowDocumentList().length];//...此处省去部分代码boolean flag = IntegrateBorrowProcess.executeProcess(obj,borrowDocumentList);ResetUniversalResponse results = new ResetUniversalResponse();results.setBsuccessed(flag);return results;}其实,集成并不是像我们想象中的那么可怕,重要的是要找到它的突破口。我们这里所讲的集成,最重要的是AbstractRestController,它为外部提供了几种方式HTTP请求方式,给了BPM一个入口。