IBM 最近收购了 Initiate,这是一家专注于医疗行业主数据管理 (MDM) 的公司。它的旗舰产品 Initiate Patient 提供了许多围绕患者主索引 (EMPI) 的先进的行业领先功能。EMPI 在医疗设置中被用于识别不同的患者记录何时指的是同一个人。此功能对建立患者的综合视图至关重要,例如用于创建从不同设施上的治疗结果汇编而成的时序健康记录。它形成了大部分健康信息交换 (HIE) 实现的基础。
有多种将 Initiate Patient 与医疗企业系统集成的方法。在集成时常常需要的一种功能,即向外部系统异步通知在 Initiate 内发生的事件,使它们能够执行适当的调整操作。Initiate 的 Message Broker Suite 附带了一个 Outbound Broker 组件,可以通过配置它与 Initiate 中心内的特定事件相关联,在这些事件发生时发送消息来通知外部系统。
本文将介绍 Outbound Broker 的一项新功能,该功能允许使用基于 HTTP 的 SOAP 消息,以 Web 服务的形式向外部系统发送通知。在此增强功能可用之前,只能通过 TCP 套接字发送通知,这使得与基于 Web 的系统的集成变得更加复杂。这种套接字通信形式适用于操作 HL7 V2 消息的早期医疗系统,HL7 V2 消息是作为基于 TCP/IP 的 LLP 实现的。但是,随着 HL7 V3 消息标准(通常使用基于 HTTP 的 SOAP 将它实现为 Web 服务)的引入,更现代的医疗机构需要使用基于 Web 的技术进行集成。
本文假设您对 Initiate Patient 及其数据结构有基本的了解。
要执行本文中的指令和示例,您应该安装以下软件:
- Initiate Patient V9.2.x
- Initiate Message Broker Suite V9.2.0.178 或更高版本
- WebSphere® Application Server V6.1 或类似的 servlet 容器
本节将从大体上概述 Outbound Broker。更具体的实现细节已在 Message Broker 文档 中详细介绍过。
安装 Message Broker Suite 之后,该中心就可以将事件广播到外部系统了。当设置 Outbound Broker 实例时,可以将它配置为与 4 种具体的事件相关联。当所选的事件发生在中心内部时,会触发一系列步骤,最终得到一条关于该事件已发送到外部系统的消息。消息的格式以及它的内容可根据每个 Outbound Broker 实例的需要进行配置。消息传输也有多种选项,包括 TCP、SSL 和基于 HTTP 的 SOAP。
可以通过配置 Outbound Broker 实例来监听的 4 种事件是:
- Add Member:在将一个成员添加到 Initiate 时触发
- EID Updates:在成员所属的条目更改时触发
- Has Shadow:在一个成员属性通过 Initiate Inspector 更改并需要来自源系统的确认才能激活时触发
- PreMerge:在 Inspector 中的潜在重复任务通过合并两个成员记录得以解决,并且需要来自源系统的确认,一个成员才可以废弃它时,才会触发该事件。
为所创建的每个 Outbound Broker 实例安装了两种服务。Outbound Broker 服务在中心内发生触发事件时将消息放入传出的队列中。Message Sender 服务负责从传出的队列获取消息,然后将它们传送给适当的外部系统。Message Sender 会等待回执(来自外部系统的确认或拒绝),以决定将消息移动到成功队列还是拒绝队列中。
我们将通过一个常见场景来演示设置和集成 Outbound Broker 实例与基于 Web 的系统的步骤。假设一家医疗企业构建了一个基于 Java™ EE 的应用程序,在将患者记录添加到 Initiate 中心时,需要通知该应用程序,以便可以使用中心记录的链接填充患者记录的内部表示形式。请注意,该示例仅演示了将 Initiate 与外部系统集成的一种方法。还有一些可供替代的架构,应该让具备所有相关可用知识的专家对它们进行评估。
对于本示例,有两部分内容需要集中在一起。第一部分是一个 Outbound Broker 实例,它将在将新成员添加到中心时使用基于 HTTP 的 SOAP 发送 Web 服务消息。第二个是基于 Java-EE 的应用程序的一个端点,它能够接收和处理实例的 Message Sender 所发送的消息。如果选择了基于 TCP 套接字的方法作为示例的消息传输方法,则需要构建一个独立的适配器组件,将原始的套接字消息转换为 HTTP 传输消息,后者可供基于 Web 的应用程序使用。
以下概述了集成两个系统的关键步骤:
- 安装 Message Broker Suite(如果还未安装)。请参阅 Message Broker 文档了解详细的说明。
- 创建一个数据源。请参阅 Message Broker 文档了解详细的说明。
- 创建一个会在将某个成员添加到中心时通知外部系统的传出实例。请参阅 Message Broker 文档,了解有关的详细说明。请留意文档中的以下选项:
- 发送主机名:外部系统的主机名
- 发送端口号:将与外部系统进行 HTTP 通信的端口
- 在询问 Message Sender 是否将调用 Web 服务时输入
y- 对于 To URL 选项,请键入将从 Message Sender 接收消息的 servlet 的完整 URL 端点
- 对于 message type 选项,请键入
xml - 在要求在将成员添加到中心时创建消息时输入
y
- 创建传出的实例后,需要配置它,使发送的消息中包含正确的数据。这通过编辑传出实例主目录的 config_<instance name> 目录中的 OutboundEIDAddXML.ini 配置文件来完成。此外,此文件被指定包含在从 Message Sender 发送到外部系统的消息中。清单 1 中提供了一个简单示例的一部分,其中包含基本的人口信息。清单 2 中给出了一个相应的示例消息,演示了该配置如何指导消息的创建。请注意,可能需要对示例配置进行编辑,以便能够与中心内的数据名称相匹配。
清单 1. 配置文件摘录[Global-Data] 0||MSGHEADER|||||| evtType||ADD|/EmpiMsg/MsgHeader/evtType evtId|||/EmpiMsg/MsgHeader/evtId evtInitiator|||/EmpiMsg/MsgHeader/evtInitiator evtCtime|||/EmpiMsg/MsgHeader/evtCtime 1||MEMHEAD|||||| srcCode|||/EmpiMsg/Member/MemHead/srcCode memIdnum|||/EmpiMsg/Member/MemHead/memIdnum entRecno|||/EmpiMsg/Member/MemHead/entRecno 2||MEMNAME|PATNAME||||| recStat|||/EmpiMsg/Member/MemName[attrCode="PATNAME"][@deleteInd] onmFirst|||/EmpiMsg/Member/MemName[attrCode="PATNAME"]/onmFirst onmMiddle|||/EmpiMsg/Member/MemName[attrCode="PATNAME"]/onmMiddle onmLast|||/EmpiMsg/Member/MemName[attrCode="PATNAME"]/onmLast onmPrefix|||/EmpiMsg/Member/MemName[attrCode="PATNAME"]/onmPrefix onmSuffix|||/EmpiMsg/Member/MemName[attrCode="PATNAME"]/onmSuffix onmDegree|||/EmpiMsg/Member/MemName[attrCode="PATNAME"]/onmDegree onmTitle|||/EmpiMsg/Member/MemName[attrCode="PATNAME"]/onmTitle
清单 2. 示例传出代理消息<?xml version="1.0" encoding="ISO-8859-1" standalone="no" ?> <!DOCTYPE EmpiMsg SYSTEM "initiate.dtd"> <EmpiMsg> <MsgHeader> <evtType>ADD</evtType> <evtId>103-466</evtId> <evtInitiator></evtInitiator> <evtCtime>20110222133649</evtCtime> </MsgHeader> <Member> <MemHead> <srcCode>OUTP</srcCode> <memIdnum>1782^^^&2.16.840.1.113883.3.18.104&ISO</memIdnum> <entRecno>205</entRecno> </MemHead> <MemName> <attrCode deleteInd="A">PATNAME</attrCode> <onmFirst>Blake</onmFirst> <onmMiddle></onmMiddle> <onmLast>Griffin</onmLast> <onmPrefix></onmPrefix> <onmSuffix></onmSuffix> <onmDegree></onmDegree> <onmTitle></onmTitle> </MemName> </Member> </EmpiMsg>
- 在 Java EE 中为基于 HTTP 的 SOAP 设置 Web 服务端点的最简单方式是:硬编码一个分析 SOAP 负载,并相应地处理其 servlet。一种更复杂的设置方法可能是:使用您最喜爱的 Java EE IDE 中的可用 Web 服务工具来自动生成将消息解组为 Java 对象的代码。
- 全面实现该 servlet 端点需要一个示例 SOAP 消息。获得此消息的一种方法是通过编码创建一个对向它发送的 SOAP 消息进行回复的端点,以便开始实现您的 servlet。从这里,您可以构建代码来解析和进一步处理消息。
- 为了将消息发送到端点,需要启动与实例对应的 Outbound Broker 服务和 Message Sender 服务。请注意,第一次启动它们时,需要为中心内存在的每个成员发送一条消息。在这之后,只在添加新成员时才发送消息。
- 使用 XPath 从传入的消息中解析出数据的示例 servlet 实现如清单 3 中所示。它包含一个 XML 响应的示例,该响应通知 Message Sender 成功创建了一条消息,并导致 Message Sender 将该消息移动到成功队列中。
清单 3. Servlet 实现package com.ibm.initiate.broker.sample; import java.io.IOException; import java.util.Enumeration; import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.soap.MessageFactory; import javax.xml.soap.MimeHeaders; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPConstants; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPFault; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPPart; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; public class MemberAddedEndpoint extends HttpServlet { private static final long serialVersionUID = 1L; private static final String CLASS_NAME = MemberAddedEndpoint.class.getName(); private static Logger logger = Logger.getLogger(CLASS_NAME); private static final String CONTENT_TYPE_XML = "text/xml"; private static final String CHAR_ENCODING_UTF_8 = "utf-8"; // success Ack message for Outbound Broker Sender private static final String OUTBOUND_ACK_MSG = "<EmpiResp><MsgHeader><code>ok</code></MsgHeader></EmpiResp>"; private static final String XPATH_PREFIX_MEMBER = "//EmpiMsg/Member"; private static final String XPATH_PREFIX_MEMHEAD = XPATH_PREFIX_MEMBER + "/MemHead"; private static final String XPATH_SRCCODE = XPATH_PREFIX_MEMHEAD + "/srcCode"; private static final String XPATH_MEMIDNUM = XPATH_PREFIX_MEMHEAD + "/memIdnum"; private static final String XPATH_ENTRECNO = XPATH_PREFIX_MEMHEAD + "/entRecno"; private static final String XPATH_PREFIX_MEMNAME = XPATH_PREFIX_MEMBER + "/MemName"; private static final String XPATH_FIRSTNAME = XPATH_PREFIX_MEMNAME + "/onmFirst"; private static final String XPATH_MIDDLENAME = XPATH_PREFIX_MEMNAME + "/onmMiddle"; private static final String XPATH_LASTNAME = XPATH_PREFIX_MEMNAME + "/onmLast"; private static final String XPATH_NAMESUFFIX = XPATH_PREFIX_MEMNAME + "/onmSuffix"; public MemberAddedEndpoint() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { MessageFactory messageFactory = null; SOAPMessage responseMsg = null; try { messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); SOAPMessage soapMsg = messageFactory.createMessage( getMimeHeaders( request), request.getInputStream()); SOAPBody body = soapMsg.getSOAPBody(); logger.fine( "Received SOAP Message from Initiate Outbound Broker for Member Add: " + body); // just parses message data and emits to log file // this is where business logic for the endpoint could be implemented displayParsedMessageData( body); responseMsg = messageFactory.createMessage(); response.setContentType(CONTENT_TYPE_XML); response.setCharacterEncoding(CHAR_ENCODING_UTF_8); // send HTTP OK & custom XML message Ack back to Outbound Broker sender response.setStatus( HttpServletResponse.SC_OK); response.getWriter().write( OUTBOUND_ACK_MSG); } catch ( Exception e) { try { // send HTTP error and SOAPFault back to Outbound Broker sender addSOAPFault(responseMsg); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, responseMsg.toString()); } catch (SOAPException soape) { // couldn't generate SOAPFault, so just send HTTP error back to sender response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } logger.severe( "An unexpected error occurred: " + e.getLocalizedMessage()); e.printStackTrace(); } } private void displayParsedMessageData( SOAPBody body) throws XPathExpressionException { StringBuffer buff = new StringBuffer(); buff.append( "Message received with contents: ["); buff.append( "Member ID Number=").append( getDataValue(body,XPATH_MEMIDNUM)); buff.append( ", Entity Record Number=").append( getDataValue(body,XPATH_ENTRECNO)); buff.append( ", Source Code=").append( getDataValue(body,XPATH_SRCCODE)); buff.append( ", First Name=").append( getDataValue(body,XPATH_FIRSTNAME)); buff.append( ", Last Name=").append( getDataValue(body,XPATH_LASTNAME)); buff.append( "]"); logger.info(buff.toString()); } private String getDataValue( SOAPBody body, String xpathExpr) throws XPathExpressionException { XPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); String value = null; Object result = xpath.evaluate( xpathExpr, body, XPathConstants.STRING); if ( result != null) { value = result.toString(); } return value; } private MimeHeaders getMimeHeaders( HttpServletRequest request) { MimeHeaders mimeHeaders = new MimeHeaders(); Enumeration mimeHeaderNames = request.getHeaderNames(); String headerName = ""; String headerVal = ""; while (mimeHeaderNames.hasMoreElements()) { headerName = (String)mimeHeaderNames.nextElement(); headerVal = request.getHeader(headerName); mimeHeaders.addHeader(headerName, headerVal); } return mimeHeaders; } private void addSOAPFault( SOAPMessage msg) throws SOAPException { SOAPPart part = msg.getSOAPPart(); SOAPEnvelope env = part.getEnvelope(); SOAPHeader head = msg.getSOAPHeader(); SOAPBody body = msg.getSOAPBody(); head.detachNode(); SOAPFault soapFault = body.addFault(); soapFault.addNamespaceDeclaration(env.getPrefix(), env.getNamespaceURI()); soapFault.setFaultCode(env.createName("Receiver", env.getPrefix(),env.getNamespaceURI())); String faultMsg = "System is unable to process the Initiate Outbound Broker Message"; soapFault.setFaultString(faultMsg); soapFault.setFaultActor("Add Member Outbound Broker Endpoint"); } }
能够从中心发送消息而不向其中实际添加新成员(此活动有时可能很耗时),这常常很有用。有一种简单方法可以完成此任务,即从您的中心数据库运行以下 SQL 语句:
update mpi_eidtrigger_<entity name> set complete = 0 where memrecno = <existing member’s MemRecNo> |
要更好地调试传出实例中的问题,可以针对 Outbound Broker 和 Message Sender 单独调整日志记录级别。可以通过编辑 services.ini 文件中对应于您希望提高其日志级别的服务部分来是实现此目的。设置 MAD_TRACE=1 将导致启用最详细的日志级别,然后是 MAD_DEBUG。提高日志级别会导致性能降级,因为需要协调额外的 I/O 操作,以便将它们写入日志文件。出于此原因,在生产环境中应该谨慎使用跟踪和调试日志级别。services.ini 文件的位置是通过 MAD_CONFNAME 环境变量来配置的。每个服务的日志文件单独位于传出实例主目录的日志目录中。
有 3 个队列用于处理发送到外部系统的消息。input、success 和 failure 队列都在文件系统上,通过一系列相互关联的文件来维护。它们位于实例主目录的 data/interface/outbound_<instance name> 目录中。为了清除队列,应该停止 Outbound Broker 和 Message Sender 服务,然后便可以安全地删除目录中的所有文件。有关队列工作原理的更多细节已在 Message Broker 文档中详细介绍。
Message Broker Suite V9.2.0.178 中已修复了一个错误,在 Message Sender 等待从外部系统收到回执时,该消息可能导致问题。所有执行 Web 服务消息传递的 Message Sender 都只等待 1 秒钟的时间,如果它们没有在该时间内收到成功回执,则会重新尝试修复错误消息并最终将它移动到失败队列中。此问题已修复,现在可通过调整 services.ini 文件中相应部分中的 MAD_SOTIMEOUT 设置,配置 Message Sender 等待来自外部系统的回执的时间长度。
较新的 Initiate Outbound Broker 版本中包含使用基于 HTTP 的 SOAP 发送 Web 服务消息的功能。与以前通过 TCP 套接字通信提供的支持相比,此新功能支持更加无缝地集成基于 Web 的系统:
- 请参阅 清单 1 了解摘录自示例 Outbound Broker 配置文件的内容。
- 请参阅 清单 2 了解从 Outbound Broker 发送的示例 XML 消息。
- 请参阅 清单 3 了解接收 Outbound Broker 消息的示例 Java Servlet 端点。
学习
- 获取有关 IBM
Initiate Patient 的更多信息。
- 从 Initiate Message Broker Suite Reference 指南 获取有关 Initiate Patient 编程的更多细节。
-
查阅 Bootcamp 培训课程。
- 在 developerWorks 上的 InfoSphere 专区 中,获得您提升主数据管理和其他 InfoSphere 技能所需的资源。
- 在 developerWorks 中国网站 Information Management 专区 了解有关 Information Management 的更多信息。查找技术文档、操作文件、培训、下载、产品信息等等。
- 随时关注 developerWorks 技术活动 和 网络广播。
- 在 Twitter 上关注 developerWorks。
获得产品和技术
- 使用可直接从 developerWorks 下载获得的 IBM 产品评估试用版软件 构建您的下一个开发项目。
讨论
- 参与论坛讨论。
- 查阅
developerWorks 博客 并加入 developerWorks 中文社区,developerWorks 社区是一个面向全球 IT 专业人员,可以提供博客、书签、wiki、群组、联系、共享和协作等社区功能的专业社交网络社区。
