Web 服务在卫生保健 HL7 上的应用 - Web 服务基础实现框架
Mauro Regio
本文想为卫生保健解决方案的设计者和开发者提供一个参考的体系结构和实现方法,在web服务平台上,这种体系结构和实现方法能够使信息在卫生保健应用中进行交换,这与HL7规格说明及WEB服务范围是一致的。
本文主要解决使应用软件能与WEB服务接口连接,按契约优先方法来设计WEB服务,以及基于信息类型与应用交互软件的正式规格说明书的代码自动化生成,这些信息类型和应用交互软件具体的保健范围之外也是相关联的。 (50 printed pages)
应用于:
• |
企业体系结构 |
• |
基层体系结构 |
• |
方法体系结构 |
• |
Web服务 |
• |
HL7卫生保健应用软件 |
• |
工厂 |
本页内容
介绍
今天,由于商业与法律的需要—--就像在美国的健康保险便利和义务法案(HIPAA)—卫生保健组织机构很清楚要与他们的商业结合起来,遗憾的是,大多数的健康信息系统一直是私人所有,而且在一个卫生保健行业他们只为一个部门服务。这显示了对综合行业的极大阻碍。更复杂的是,病人的健康信息可能要通过好多部门才能够得到,部门之间不协同工作,这就会对治疗的预防、质量、成本产生不良的影响。
不能灵活而规范的进行处理,没有卫生保健行业的准确定义,不能平衡成本效益,不能对外开放,不能在健康信息系统之间进行可操纵的基层组织通信,我们就不能解决这个问题。
Health Level Seven (HL7)是美国国家标准化协会(ANSI)认可的标准化开发组织中的一个,它正在全世界保健行业里运行着。 ("Level Seven" 引用了开放系统互连模型OSI最高层----应用层) ,传统上,它从事临床建模与数据的管理工作,最近的一个版本,HL7 3.0版本扩展到了了各种卫生保健行业,比如:制药业、医疗设备及成像设备。
HL7 v3.0 版本相对于广泛应用的2.x版本来说有一很重要的进展, HL7 v3.0 版本改进了,因为它使用一套严密的方法学来指导,健康标准软件开发平台是基于标准参考信息模型(RIM) 的一套严格定义的应用编程接口, 因此,它提供的标准规格说明书是很精确的并且可测试。这就提供了能够验证客户是否与标准一致的能力。
假设在一个特定的卫生保健领域,HL7 v3.0版本说明书定义相应的事先设想好的案例,大多数软件系统的规则完全支持这些设想,它允许应用软件间的交互以及信息的交换。
HL7标准也指定了一些适当的信息基层组织,比如:Web Services,它就适合传送HL7信息,并且在应用软件之间对于如何确保这个信息的传送的交互性,它提供了一个说明性的向导。将HL7应用软件应用在Web Services上意味着首先设计一个正确的体系结构,其次是提供一个可执行的满足Web Services的环境。 (注意:本文只是涉及HL7 Web Services Basic profile [HL7WSP].)
在构件和设计能够共同使用的应用软件时,卫生保健方案的提供者必须要调控HL7的标准定义,就像以前所定义的一系列需求。架构师也需要设想一下软件的组织结构,因为这种结构连接着应用软件与所选的信息基层组织。这就需要完全理解应用软件与通信基层组织的职责。最后,实现必须要满足HL7提供的信息传送的约束条件。
在这些设想下,应用软件就可以辨认出HL7 v3.0版本, 能够处理XML数据和信息,并且也能与Web Services基层组织进行互操作,本文将说明HL7规格说明书如何能够有效地用于设计和开发一个严格应用于Web Services的参考实现模型。
这篇文章也会展现一个设想的HL7参考实现模型,这个模型是在Microsoft .NET平台之上应用Microsoft Visual Studio 和 Microsoft Web Services Enhancements (WSE) 进行开发的。
HL7 模型概念
通过对HL7标准规格说明书以及本文以外的一些工具的描述,在这部分,我们将介绍一些主要的HL7模型概念和人工制品,这些都与我们的讨论相关的。
参考信息模型
对于一个给定的卫生保健领域, HL7 v3.0版本说明书是基于参考信息模型的 (RIM), 这是一种公共的模型框架,它包括:病例模型、信息模型、交互模型、消息模型和实现信息说明书。
HL7的参考信息模型是一个静态的卫生保健信息模型,它代表了至今为止负责HL7标准发展行为的卫生保健领域的各个方面 。HL7 v3.0版本标准开发过程定义了一些规则,这些规则用于从参考信息模型中获取一些具体领域信息模型,从而在HL7规格说明书中使这些模型更精确,最后产生XML表单定义(XSD)与一个具体的消息类型联合起来。
由HL7提供给标准开发商的各种工具都支持这个过程。例如:参考信息模型内容本身就被存储在一个自定义的库中,并且对于那些标准和开发商都是有用的,因为开发商想要得到一个模型信息或是想要创建另一个工具。
消息结构
HL7应用软件之间的交互行为是通过消息的交换来完成的。这样,在提供envelopes来支持应用程序之间的消息交换期间,这个标准就提供了一个真实的功能水准。HL7消息的封装被称为wrappers, 最初是通过RIM中类的定义和关联模型化的。然后这些说明书被用来为消息wrappers创建 XML表单, 接下来,在HL7消息开发框架中所列的过程在图1中有所描述。
图 1. HL7 消息发展体系—引自HL7 V3 Ballot 7 [BALLOT]
所有的HL7消息都被放在Transmission Wrapper,Wrapper的目的是支持应用软件之间消息的传输(和确认)。Wrapper的重要的部分是一些元素,比如:消息标志符、消息的创建时间、交互标志符、发送者和接收者标志符、确认编码和消息序列号(可选)。 认为HL7消息是在合理的HL7应用软件之间进行交换这一点是很重要的;那就是说,特殊的软件应用或是组成成分 (像 "顺序实体")都代表着有组织的或是可管理的实体(象西部医院登记一样)。所以,在传输层,发送者和接收者概念不会被看成是一个规格说明书的一部分。
例如,Accept Acknowledge 代码将详细说明一个应用层确认信息是否应该被返回到发送消息的应用端, 或者需要更多的面向传输的信息接收 (称为Accept Acknowledge)。
例如,Accept Acknowledge 代码将详细说明一个应用层确认信息是否应该被返回到发送消息的应用端, 或者需要更多的面向传输的信息接收 (称为Accept Acknowledge)。
Transmission and Control Act Wrappers被看成一个袋子来包装消息体(如图2)。
同时, Control Act Wrapper 和 HL7 消息体组成了完整的HL7消息的语义结构 。
图 2. HL7 抽象的消息结构
交互
一次 HL7交互就是信息特殊转移过程中的一次联合; 一个触发事件就开始了消息的转移,应用软件进行接收和发送消息。在HL7里,一个触事件是引起信息在应用软件之间进行转移的一系列精确条件,它也代表着一个真实的事件,比如:实验室顺序的安排或是一个病人的登记。
在概念层次上,一次交互表明了唯一的,单向的信息转移。它包含:
• |
一个系统是如何知道它在什么时候发送一个具体的消息类型。 |
在实践层次上,理解交互的动力要比一次简单的信息转移更复杂是很重要的,它取决于在消息的Transmission and Control Wrappers里什么被指定。事实上,通信层可以使用多种信息模式,包括 (不限定): 发送消息负载----没有确认,带有接收确认的发送消息负载,带有请求确认的(立即或是延迟)发送请求消息,等等.
应用程序角色
图 3.交互图—---引自 HL7 V3 Ballot 7
HL7里的每一个应用属于一个具体的 应用程序角色。根据一个应用程序提供给其它应用程序的服务或是一个应用程序为了获得特定的服务而发送给其它应用程序的消息,这样一个角色就体现了应用程序的职责。
在一个确定的交互作用里,一个应用程序角色为了发送信息它也被分配一定的职责(我们可以将这个应用程序角色看成是一个应用程序客户)—--发送者—--同时一个应用程序角色也要被分配一定职责来接收—--接收者(这个应用程序角色就被当作一个应用程序服务器)—相互作用且采取适当的应答。
Storyboard
像消息类型、交互作用和应用程序角色这些概念都集合在了一个HL7 Storyboard里,它是用来指定在HL7标准化行为范围内与任意卫生保健领域相关联的用例。
一个Storyboard是由一小段记叙了它本身的目的及交互作用图表的描述所组成的--—在应用层—--应用程序角色间相互作用的级数。就象图3中的一个那样,交互作用的图表指明了相应交互作用的职责(就是应用程序角色)、交换信息的类型以及期望的信息交换的顺序。
从图3中可以很明显地看出,在HL7中,每个甚至任意一个概念模型都有一个描述性的名字(例如:实验室观察预订全球投资者) 和一个唯一的标识符(例如:POLB_AR002900)来形成一个简单而有结构性的命名习惯。例如,在实验室领域里所有的实体都以"POLB"作前缀。Storyboard 的名字是以"ST"作前缀, 然而"AR" 和 "IN" 这个两前缀为用作应用程序角色和交互作用的标识符。
因此,就象图3中的那样, 参与交互的Laboratory_Observation_Order_Global_Placer (POLB_AR002900) 和 Laboratory_Observation_Order_Global_Fulfiller (POLB_AR002942)都被命名为Laboratory_Observation_Order_Activate_Fulfillment_Request (或者POLB_IN002120). 当第一个应用程序角色是一个消息请求的发送者时,第二个就是一个接收者,实际上就是服务提供者。
有一点很重要,尽管在交互图表中没有直接地显示出操纵HL7参考信息模型,也没有显示从交互标识符开始(POLB_IN002120),但是它允许信息类型的恢复 (XML 表单),这些消息在交互发生时必须交换。
体系结构
基于刚刚介绍的HL7概念模型,现在我们能更精确地定义出HL7应用"Web enablement." 这些都是在支持应用程序角色软件组成中的设计与实现,这些角色是作为交互行为中的一部分来实现发送者/接收者的职责的,通过使用Web 服务通信基层结构来满足HL7 Web 服务的。
这部分描绘出了相应的抽象结构层和透视图,同时指定了与HL7相适应的结构组成的职责。
从体系结构这一观点来讲,注意到HL7应用程序、WEB服务基层结构这一点是很重要的,传输是处在不同的层里,如图4所示。
为了发送一个HL7消息,依照交互的商业规则、信息类型定义和通信的模式,HL7发送者应用软件需要提前准备出来,表明HL7消息在接收------不论确认信息需要与否,接下来把他们传递给WEB服务基层结构。回过头来,WEB服务基层结构将会应用由传输层所提供的服务把消息转移到网络终端上。
在接收方,消息将会在各种抽象层组成部分里被处理,最后将HL7消息送到HL7接收应用软件那里。
在应用层,发送/接收HL7消息、与WEB服务基层结构的信息交互这是两个截然不同的功能。当然,把他们看成发送者与接收者应用是正确的。
在图4所示的结构里,我们能够抽象出HL7发送者/接收者内部的这两组功能:商业逻辑和 Web服务适配器。 (需要强调的是:这里商业逻辑的范围是在HL7 应用进行他们的发送者的角色和/或信息的接收者内部,那就是说,它支持一种具体的通信模式。应用层商业逻辑,消息的产生,或是为了响应需求而提供的具体服务这些都是在范围之外的。)
至于HL7消息的扩展,我们需要关注一下,商业逻辑的任务包括:
• |
发送端
• |
创建一种具体HL7消息类型的XML描述,消息类型包含:消息体、Transmission and Control Wrappers。 |
• |
将消息传送到WEB服务适配器,适配器负责传送到接收应用端。 |
图4 参考体系结构 |
• |
接收端
• |
"找回" 由WEB服务适配器接收的HL7消息,同时从接收到的XML消息那里打开Transmission Wrapper、Control Wrapper和消息体. |
• |
验证HL7消息是否满足用来交互的商业规则和约束。 |
• |
核实发送应用端是否需要一个应用层的确认信息(HL7消息类型MCI)----如果是那样的话----发送那个消息。 | |
Web服务适配器的功能主要是用来处理消息的分发和确认信息的。因此,主要包括:
• |
发送端
• |
读取接收到的HL7消息的Transmission Wrapper以便决定如何到达WEB服务基层结构上的发送容器(例如接收应用软件),从而配置SOAP 。 |
• |
基于HL7消息类型、应用配置和规则(如:安全性)来准备一个SOAP消息,包括作为一个SOAP消息体部分的HL7 XML消息,这个消息被发送到WEB服务基层组织。 |
• |
把SOAP消息传递到WEB服务代理通过网络进行传输。 |
• |
无论发送端什么时候请求,都准备接收并存储来自接收端的相应的信息或是应用层的确认消息。 |
接收端
• |
从WEB服务站处接收SOAP消息。 |
• |
验证接收到的SOAP消息满足应用配置和一些约束条件(如:安全性)。 |
• |
或者将这些接收到的消息在内存中以永久的形式保留。 |
• |
有选择性在从SOAP消息里打开HL7 XML消息,同时核对接收到的HL7消息是否与期望的HL7消息类型相符合。 |
• |
验证是否任意通信层的确认信息都需要被执行,在那种情况下资金积累一个合适的消息发送到源消息发送端。 |
• |
传递HL7消息给接收应用端。 | |
被选择用来实现一个具体HL7交互的通信模式影响着WEB服务适配器的功能、他们的体系结构,以及他们是怎么“使用”WEB服务基层组织所提供的低层信息交换模式(MEP)的。理解这些是很重要的。
例如,当HL7通信模式“发送消息负载----没有确认信息”被使用时,发送端只是发送一个单行道的消息给接收端。换句话说,通信模式能够被映射到单行道消息扩展模式。在这种情况下,WEB服务适配器必须完全嵌入普通的WEB服务代理和组成元素,这些组成元素的各自职责是通过WEB服务基层组织进行发送/接收SOAP消息。
如果被选的HL7通信模式需要一个同步确认信息的话,象“发送一个需要接收确认的消息负载----立即”或是“发送一个需要请求确认的消息负载----立即”,发送端向接收端发送一个消息期望立即得到回应,同时携带接收或请求确认信息。在这种情况下,WEB服务代理/站体系结构会一直工作,尽管如此,这些通信模式将被映射到请求/应答消息扩展模式,而不是单行道传输模式。正如下一章我们看到的,这也会影响到WEB服务合同说明书里定义的运行方式,比如WSDLs。
如果所用的通信模式需要一个来自接收端的延迟反应,象“发送一个需要接收确认的消息负载----延迟”或是“发送一个需要请求确认的消息负载----延迟”,那么接入到适配器结构端的代理/ Stub就需要被“复制”。一个代理/ Stub对有必要支持消息的传递(从发送端到接收端),另一方面也需要从接收端发送一个反馈确认信息给发送端。在WEB服务基层组织上,对于单向信息的传递,代理/ Stub都可能会用到单行道方式或请求/应答消息扩展模式。
认识到下面这点很重要,HL7版本3消息基础说明书里定义了其他的、更复杂通信模式,这种模式随着应用层的处理(核对/应答)阻碍了消息的传递(分发/确认)。例如,一个发送端可能发送一个需要得到应用层回应的HL7消息负载。然而,这个发送端也可能会声明它能够处理应用层的应答来代替一个应用层确认给源消息。
在适配器这层,这些情况都能够当作多个单行道方式或是请求/就答消息扩展模式来实现。同时,继续应用—--应用协作的这种职责应该会被业务逻辑的应用所取代。
当我们宣称这不在本文范围时,在一个真正的实施过程中,适配器的结构也需要处理综合性应用和互操作能力;例如,如果一个应用业务逻辑不能直接与一个WEB服务环境进行交互或是它被搭建在一个与以前实现时不同的平台上。
开发 HL7 Web服务适配器
原则上,尤其是当范围被限制在只是支持HL7 Web服务时,开发HL7 Web 服务就与开发普通的WEB服务相类似了。事实上, RIM的标准化模型的有效性, 消息类型的说明书,通信模式及WEB服务都在一定程序上影响着过程。
处在一个很高的水准上,为了高效地开发HL7 Web 服务适配器,我们需要按如下步骤来做。
1.消息和数据类型的设计—在一个象HL7这样面向消息的环境里开发一个WEB服务,必须首先设计可交换的消息、已用的数据类型以及XSD表单里他们的说明书。这项活动完全受益于HL7(使XSD表单自动化产生)所构造的消息和数据类型工具。
2.适配器模式的选择—在创建WEB服务适配器的下一步是选择哪一个适配器结构模式能够最好地适合HL7通信模式,这个通信模式是由步骤1中所获得的消息类型来指定的。这一步要定义,比如说,如果一个(仅仅一个)代理/ Stub 组成成份是必要的。
3.HL7 Web服务契约开发—从一个普通的角度考虑,在创建一个面向消息的WEB服务的下一步就能够定义它的契约了,用一种标准化的可用计算机处理的语言称作WEB服务描述语言(WSDL),或者在支持Web 服务标准的编程语言里实现它的开发。
在服务契约(接口)和它的实施之间的分离是一种Web服务范例的基本特性。服务契约里详细说明了服务的语义;那就是说:“what”是指服务是与实现的具体细节相对应的,“how”是指一个具体的服务是如何被构建的。
Web 服务契约和实现可能以任意一种顺序发展。更有趣的是,一旦一个基于契约的WSDL是有效的,那么WEB服务的实现框架就会自动产生。这种方法被称作契约先于WEB服务开发 。反过来,一个契约可以自动地(完整地)从WEB服务实现那里得到。
不幸的是,虽然简单而全面,但是这种方法驱动的实现可能会引入一些互用性的问题[CF-INT],因为实现是建立在具体的WEB服务平台上,比如Microsoft .NET ,用于产生WSDL契约的工具也是一个具体的平台,同时它可能会引入一些具体平台的元素,尤其是数据类型,可以产生WSDL契约。
更重要的是,这种契约优先方法总是要比由那种生效的服务说明书所驱动的实现要好的多,就如同在HL7说明书里一样。进一步来讲,拥有一个有效服务的抽象说明书和在一个服务契约里用于传输的描述性指导会允许我们自动地将交互说明书转移到一个WSDL契约里。在下一节我们会更详细地讨论这些。
4.产生WEB服务Stub和代理的实现—一旦WSDL契约完成,它就可能创建使用一些工具的WEB服务Stub和代理服务器,这些工具是由象WSDL.exe这样的开发平台所提供的。我们将会在WEB服务代码的生成那节里做详细的介绍。
5.开发适配器业务逻辑—这一步是建立在前一步代码生成的基础上的,添加了必要的逻辑来支持适配器的功能,这些功能在Architecture这节里已描述过了。
HL7 Web 服务契约发展
一个普通的WSDL契约都详细说明了一个WEB服务的名字和端口,通过这些端WEB服务器可以和客户端应用程序进行通信。一个端指定了网络中服务生效的位置。 每个端口也指定了端口上的一群有用的操作(portTypes)和客户与服务器在那个端口上进行通信的协议间的一个绑定。端口类型代表了暴露在WEB服务上的各种接口。操作是接口的方法;他们定义了客户端请求服务端的输入信息,以及定义了服务器用于应答客户的输出信息。消息的格式也是基于WSDL契约中所定义的类型的格式(XML表单)。
包含在HL7 WEB服务Basic Profile 里的说明性向导主要集中在如何与HL7应用程序提供的功能进行交互。那就是说,向导定义了标准化的WEB服务契约,以及如何在一个SOAP消息里存放一个HL7消息的XML描述。
大多数的profile都提供了一个关于怎么构建一个WSDL契约的说明性向导。同时,对于给定的具体应用程序角色、这个应用程序角色所支持的交互以及信息类型,通过简单的应用profile提供的规则(声明)来构建一个WSDL契约就变得相当容易了。
然而,还有少量的比如端口类型、绑定和服务问题没有解决,因此在这里需要更进一步的讨论。
端口类型
基本的 Profile里面要求有4个声明必须要满足端类型:
1.HL7-WS220—一个端口类型命名{Application Role Artifact Id}_PortType 被声明。
2.HL7-WS221—一个请求/应答操作由两个交互作用组成,他们都被一个基于触发事件的交互所链接。
3.HL7-WS222—单向操作适合于每一个交互行为,每个交互作用不会引起基于触发事件的交互作用。
4.HL7-WS223—每个操作都有一个唯一的名字{Application Role Artifact Id}_{Operation Name}。
可以断言的是WS220 和WS223 的实现都是简单的。例如,下面是一个带有端口类型部分的WSDL语言片段,这个端口类型是用来作为 实验室 _ 观察 _ 预订 _ 行动 _ 履行 _请求的应用程序角色,在这里输入输出元素已经被忽略掉了。
代码例子1. PortType 部分 <portType name="POLB_AR002942_PortType">
<operation name=
"POLB_AR002942_Laboratory_Observation_Order_Activate_Fulfillment_Request">
<input ..."/>
<output ..https://www.yeec.com/>
</operation>
</portType>
完成操作的说明书需要定义哪个消息从模式到实现进行的调换,或者单向,或者请求/应答,或者基于被选择用来传递具体消息类型的HL7通信模式和由profile所提供的指导的一个结合。
当使用一种简单的单向消息交换模式时,你需要在WSDL契约里声明一个单向操作。例如,接下来的WSDL代码片段就提供了一个用来执行"Laboratory Observation Event Global Tracker" 应用程序角色的端口类型的一个例子, 在这个例子中,操作POLB_AR004922(即:Laboratory Observation Event Activate Notification)已经被当作一个单向操作实现了。
当一个紧急确认应答信息被请求时,这就要求在WSDL里一个请求/应答信息交换模式需要伴随请求/应答操作来实现。 简单地说,一个请求/应答操作包含了两个消息:一条输入信息和一条输出信息。在WSDL契约中这两条信息都需要被详细说明。例如,在POLB_AR004922中,"Laboratory Observation Event Preliminary Notification" 和 "Laboratory Observation Event Complete Notification"实现了一个请求/应答模式,在这里应答信息是一个应用层确认的信息(MCCI_MT00300)。
当一个HL7延迟应答确认信息被请求时,那么它就需要一个请求/应答信息交换模式。然而,在这种情况下,它就必须被当作一对单向信息来实现。因此,最好是每一个单向操作都在WSDL契约中定义出来(那就是说,每一个都是关于发送者和接收者的)。
代码例子2. 在WSDL契约中定义的请求/应答信息交换模式 <portType name="POLB_AR004922_PortType">
<operation name="POLB_AR004922_Laboratory_Observation_Event_
Activate_Notification">
<input message=
"hl7:POLB_AR004922_Laboratory_Observation_Event_
Activate_Notification_POLB_IN004110.Message"/>
</operation>
<operation name="POLB_AR004922_Laboratory_Observation_Event_
Preliminary_Notification">
<input message=
"hl7:POLB_AR004922_Laboratory_Observation_Event_Preliminary_
Notification_POLB_IN004310.Message"/>
<output message=
"hl7:POLB_AR004922_MCCI_MT00300.Message"/>
</operation>
<operation name="POLB_AR004922_Laboratory_Observation_Event_
Complete_Notification">
<input message=
"hl7:POLB_AR004922_Laboratory_Observation_Event_
Complete_Notification_POLB_IN004410.Message"/>
<output message=
"hl7:POLB_AR004922_MCCI_MT00300.Message"/>
</operation>
</portType>
当然,在指定操作里使用的所有信息都是事先在WSDL契约的信息片段中声明好了的。
绑定
一旦操作和端口类型被确定,我们就能够指定他们绑定到一个或更多的传输器上。Basic Profile 's adoption of SOAP is articulated in the following four assertions:
1.HL7-WS230—绑定命名{Application Role Artifact Id}_Binding 被声明。
2.HL7-WS231—一个soap:operation/@soapAction属性,值是 urn:hl7-org:v3/{Application Role Artifact Id}_{Operation Name} 被提供。
3.HL7-WS232—HL7 WSDL 使用SOAP 绑写。
4.HL7-WS240—用于HL7的SOAP信息使用文档/文字的风格。
代码例子3是一个WSDL语言片段,这段代码是用来实现那些声明“Laboratory Observation Event Global Tracker”应用程序角色的,在这段程序中,最后两个操作支持请求/应答,就象前面PortType 例子中定义的那样。
代码例子3。绑定WDSL契约中的说明书 <binding name="POLB_AR004922_Binding" type="hl7:POLB_AR004922_PortType">
<soap:binding style="document"
transport="https://schemas.xmlsoap.org/soap/http"/>
<operation name="POLB_AR004922_Laboratory_Observation_Event_
Activate_Notification">
<soap:operation soapAction="hl7:POLB_AR004922_POLB_IN004110"/>
<input>
<soap:body use="literal"/>
</input>
</operation>
<operation name="POLB_AR004922_Laboratory_Observation_Event_
Preliminary_Notification">
<soap:operation soapAction="hl7:POLB_AR004922_POLB_IN004310"/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
<operation name="POLB_AR004922_Laboratory_Observation_Event_
Complete_Notification">
<soap:operation soapAction="hl7:POLB_AR004922_POLB_IN004410"/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
服务
最后,在契约中被声明的是WEB服务,接下来的声明确定了服务是怎样根据HL7 Basic Profile来发生的。
1.HL7-WS250—服务命名 {Application Role Artifact Id}_Service 被声明。
HL7-WS251—端口命名 {Application Role Artifact Id}_Port 被声明。
例如,这里定义了服务POLB_AR004922,即使具体服务的位置还没有提供。
代码例子4. 声明Web 服务 <service name="POLB_AR004922_Service">
<port name="POLB_AR004922_Port" binding="hl7:POLB_AR004922_Binding">
<soap:address location="https://servicelocation/POLB_AR004922"/>
</port>
</service>
当WSDL契约设计完后服务的位置还不能确定。当服务代码被创建时服务的位置就应该确定下来。
使契约发展自动化
当然,确实能够自动化地创建HL7 WSDL契约。这里排除代码和错误。
事实上,HL7 WEB 服务Basic Profile里的这些声明会产生一般的WSDL契约模板,在模板里大部分都是契约所支持的应用程序角色的功能,还有一些信息的标识符和描述符,这些信息能够被应用程序角色接收并处理。
消息被包含在HL7参考信息模型里,更确切地说是在一个基于关系型数据库的仓库里,整个RIM都被存储在这个库中。这个仓库对于所有的HL7成员都是可以用的。
随着对那个仓库的处理以及对包含在HL7 profiles中声明性质的理解,写一个WSDL契约就象在以前提到的WSDL模板上写一个XSLT传输一样容易,或者为应用程序开发者所用的具体开发环境开发一个附加项,例如 Microsoft Visual Studio。
这种附加项的方法更可取,因为这种方法更灵活并且能够被扩展来为已定义WEB服务的Proxies 和Stubs自动产生代码,或者更一般的来讲,是为那些组成WEB服务发送与接收适配器各种组成部分来自动产生代码。
代码例子5是一个完整的WSDL契约的例子,自动为应用程序角色POLB_AR004922 从HL7 参考信息模型中产生的,即图3中的"Laboratory Observation Event Global Tracker",假设所有的通信模型都被映射到单向信息交换模式里。 注意:XML 注释是如何被用来突出WSDL里Basic Profile 声明的。
代码例子5. 自动化产生WSDL契约例子 <?xml version="1.0" encoding="utf-8"?>
<!--HL7-WS203) The targetNamespace
of the WSDL MUST be "urn:hl7-org:v3"-->
<definitions xmlns:soap="https://schemas.xmlsoap.org/wsdl/soap/"
xmlns="https://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="https://www.w3.org/2001/XMLSchema"
xmlns:hl7="urn:hl7-org:v3" targetNamespace=
"urn:hl7-org:v3" name="POLB_AR004922">
<!--HL7-WS204) Names (as opposed to Id's)
of Artifacts SHOULD be included in XML comments wherever appropriate-->
<documentation>WSDL implementation of Laboratory Observation Event
Global Tracker (POLB_AR004922)</documentation>
<types>
<!--HL7-WS205) XML Schema's MUST be included with an XML Schema <include>
inside a <schema> element in the WSDL <types> section-->
<xsd:schema elementFormDefault="qualified"
targetNamespace="urn:hl7-org:v3" xmlns:tns="
urn:hl7-org:v3">
<!--HL7-WS206) The schema for each Interaction this Application
Role participates in is included-->
<xsd:include schemaLocation="POLB_IN004110.xsd"/>
<xsd:include schemaLocation="POLB_IN004310.xsd"/>
<xsd:include schemaLocation="POLB_IN004410.xsd"/>
<!--HL7-WS207) A unique element name MUST be declared
for each message in the storyboard-->
<!-- The elements MUST be named {Application
Role Artifact Id}_{Operation Name}_{Interaction Artifact Id}.Message. -->
<xsd:element
name ="POLB_AR004922_Laboratory_Observation_Event_
Activate_Notification_POLB_IN004110.Message"
type ="tns:POLB_IN004110.Message"/>
<xsd:element
name ="POLB_AR004922_Laboratory_Observation_Event_
Preliminary_Notification_POLB_IN004310.Message"
type ="tns:POLB_IN004310.Message"/>
<xsd:element
name ="POLB_AR004922_Laboratory_Observation_Event_
Complete_Notification_POLB_IN004410.Message"
type ="tns:POLB_IN004410.Message"/>
</xsd:schema>
</types>
<!--HL7-WS210) Each message in the HL7 storyboard becomes a WSDL
<message>-->
<!--HL7-WS211) Each message is named {Application Role
Artifact Id}_{Operation Name}_{Interaction Artifact Id}-->
<!--HL7-WS212) Parts are named "body". Since each SOAP Body
is fully described by a single Schema, using named parts
is superfluous-->
<message name="POLB_AR004922_Laboratory_Observation_Event_
Activate_Notification_POLB_IN004110.Message">
<documentation>POLB_IN004110 Laboratory Observation Event
Activate, Notification)</documentation>
<part name ="body" element="hl7:POLB_AR004922_Laboratory_Observation_Event_
Activate_Notification_POLB_IN004110.Message"/>
</message>
<message name="POLB_AR004922_Laboratory_Observation_Event_
Preliminary_Notification_POLB_IN004310.Message">
<documentation>POLB_IN004310 Laboratory Observation Event
Preliminary Notification)</documentation>
<part name ="body" element="hl7:POLB_AR004922_Laboratory_Observation_Event_
Preliminary_Notification_POLB_IN004310.Message"/>
</message>
<message name="POLB_AR004922_Laboratory_Observation_Event_
Complete_Notification_POLB_IN004410.Message">
<documentation>POLB_IN004410 Laboratory Observation Event
Complete, Notification)</documentation>
<part name ="body" element="hl7:POLB_AR004922_Laboratory_Observation_Event_
Complete_Notification_POLB_IN004410.Message"/>
</message>
<!--HL7-WS220) A PortType named {Application Role
Artifact Id}_PortType is declared-->
<portType name="POLB_AR004922_PortType">
<!--HL7-WS221) A request/response operation consists
of two interactions, which are linked by an Interaction Based
Trigger Event-->
<!--HL7-WS222) A one-way operation is made for each interaction
that does not lead to an Interaction Based Trigger Event-->
<!-- (Note: Trigger Events may be unspecified,
in which case the operations have to be discovered
by other means.) -->
<!--HL7-WS223) Each operation is given a unique name
{Application Role Artifact Id}_{Operation Name}-->
<operation name="POLB_AR004922_Laboratory_Observation_Event_
Activate_Notification">
<input message="hl7:POLB_AR004922_Laboratory_Observation_Event_
Activate_Notification_POLB_IN004110.Message"/>
</operation>
<operation name="POLB_AR004922_Laboratory_Observation_Event_
Preliminary_Notification">
<input message="hl7:POLB_AR004922_Laboratory_Observation_Event_
Preliminary_Notification_POLB_IN004310.Message"/>
</operation>
<operation name="POLB_AR004922_Laboratory_Observation_Event_
Complete_Notification">
<input message="hl7:POLB_AR004922_Laboratory_Observation_Event_
Complete_Notification_POLB_IN004410.Message"/>
</operation>
</portType>
<!--HL7-WS230) A Binding named {Application Role Artifact Id}
_Binding is declared. -->
<binding name="POLB_AR004922_Binding" type="hl7:POLB_AR004922_PortType">
<!--HL7-WS232) HL7 WSDL uses the SOAP Binding
described in WSDL 1.1 Chapter 3-->
<soap:binding style="document"
transport="https://schemas.xmlsoap.org/soap/http"/>
<!--HL7-WS223) Each operation is given a unique name
{Application Role Artifact Id}_{Operation Name}-->
<!--We assumed One Way operations, not Request/Response-->
<operation name="POLB_AR004922_Laboratory_Observation_Event_
Activate_Notification">
<soap:operation soapAction="hl7:POLB_AR004922_POLB_
IN004110"/>
<input>
<soap:body use="literal"/>
</input>
</operation>
<operation name="POLB_AR004922_Laboratory_Observation_Event_
Preliminary_Notification">
<soap:operation soapAction="hl7:POLB_AR004922_POLB_
IN004310"/>
<input>
<soap:body use="literal"/>
</input>
</operation>
<operation name="POLB_AR004922_Laboratory_Observation_Event_
Complete_Notification">
<soap:operation soapAction="hl7:POLB_AR004922_POLB_IN004410"/>
<input>
<soap:body use="literal"/>
</input>
</operation>
</binding>
<!--
<service name="POLB_AR004922_Service">
<port name="POLB_AR004922_Port" binding="hl7:POLB_AR004922_Binding">
<soap:address location="https://servicelocation/POLB_AR004922"/>
</port>
</service>
-->
</definitions>
Web服务代码生成
一旦你为一个应用程序角色创建一个WSDL契约,那么你就能够为WEB服务站和代理服务器自动产生代码,这些服务器使用由Microsoft .NET开发平台所提供的WSDL.exe 工具。当然了,两种执行都被需要用来创建代码成份----用一种选择好的语言----为服务器端和它的客户端。此外,你必须指定<namespace>参数,也可参考合适的HL7命名空间里的服务,通过HL7 Web服务表明基本的外形轮廓。
作为一个例子,这有一个代码片段,它是为应用程序角色“Laboratory Observation Order Global Fulfiller" POLB_AR002942而执行服务站的。
注意 粗体代码表示在稍后的一个例子中这个位置会被指定。
代码例子 6. Service Stub 实现的例子 [...]
//
// This source code was auto-generated by wsdl, Version=1.1.4322.2032.
//
namespace LIS {
using System.Diagnostics; using System.Xml.Serialization;
using System; using System.Web.Services.Protocols;
using System.ComponentModel; using System.Web.Services;
/// <remarks/>
[System.Web.Services.WebServiceBindingAttribute(Name="POLB_
AR002942_Binding", Namespace="urn:hl7-org:v3")]
[WebService(Namespace="urn:hl7-org:v3")]
public class POLB_AR002942_Service : System.Web.Services.WebService
{
/// <remarks/>
[System.Web.Services.WebMethodAttribute()]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute(
"hl7:POLB_AR002942_POLB_IN002120", OneWay=true,
Use=System.Web.Services.Description.SoapBindingUse.Literal,
ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Bare)]
public void POLB_AR002942_Laboratory_Observation_Order_
Activate_Fulfillment_Request(
[System.Xml.Serialization.XmlElementAttribute(
"POLB_AR002942_Laboratory_Observation_Order_Activate_
Fulfillment_Request_POLB_IN002120.Message",
Namespace="urn:hl7-org:v3")] POLB_IN002120Message
POLB_AR002942_Laboratory_Observation_Order_Activate_
Fulfillment_Request_POLB_IN002120Message)
{
}
/// <remarks/>
[System.Web.Services.WebMethodAttribute()]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute
("hl7:POLB_AR002942_POLB_IN002121", OneWay=true,
Use=System.Web.Services.Description.SoapBindingUse.Literal,
ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Bare)]
public void POLB_AR002942_Laboratory_Observation_Order_Activate_
Fulfillment_Request_No_App_Ack
([System.Xml.Serialization.XmlElementAttribute(
"POLB_AR002942_Laboratory_Observation_Order_Activate_
Fulfillment_Request_No_App_Ack_POLB_IN002121.Message",
Namespace="urn:hl7-org:v3")] POLB_IN002121Message
POLB_AR002942_Laboratory_Observation_Order_Activate_
Fulfillment_Request_No_App_Ack_POLB_IN002121Message)
{
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(Namespace=
"urn:hl7-org:v3")]
public class POLB_IN002120Message {
/// <remarks/>
[System.Xml.Serialization.XmlAnyElementAttribute()]
public System.Xml.XmlElement[] Any;
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(Namespace=
"urn:hl7-org:v3")]
public class POLB_IN002121Message {
/// <remarks/>
[System.Xml.Serialization.XmlAnyElementAttribute()]
public System.Xml.XmlElement[] Any;
}
}
在这个代码片段里,请注意一下WEB服务的分类定义,POLB_AR002942_Service 是基于相应HL7应用程序角色说明书的,它包含两个方法: POLB_AR002942_Laboratory_Observation_Order_Activate_Fulfillment_Request 和POLB_AR002942_Laboratory_Observation_Order_Activate_Fulfillment_Request_No_App_Ack, 通过使用SOAP文字风格来准备接收两条单向消息[…] POLB_IN002120 和 […] POLBIN002121。
在同一个代码段里,这两个类POLB_IN002120Message 和POLB_IN002121Message 已经被创建了。这两个类是对WSDL契约类型部分所定义的信息和数据类型基本信息的描述----用一种程序设计语言。
必须要注意,在这个例子中,我们定义两种XML信息为“Any”型。换句话说,我们确实还不能为那些信息分配任何类型。故意那样做是为了保证stub代码例子简短而具有可读性。在其它的资料里我们将会看到这种方法是“Untyped”。
事实上,如果在WSDL契约里我们已经为相关的HL7信息类型插入完整而正确的XML表单的话,那么产生的代码就会更长而且复杂,因为通过使用一种程序设计语言所有的信息元素和类型的声明都被扩展到了相应的类里。
这些类会在各自产生的服务命名空间的上下文里定义,命名空间使这些类有唯一的实体,在应用的对象模型里,即使当这些类确实涉及到契约里声明的相同的HL7数据类型。因此,在代码的生成过程中,不同信息里所使用的相同的HL7信息元素或数据类型可能会通过翻译成不同的类而终止,这就需要大量的手工代码,从而降低了自动代码产生的好处。
很有趣的是,WEB服务代理和Stub 都不需要知道他们要处理的HL7信息的完整结构。实际上,在代理和Stub层上可能不需要----- 也没有这个必要-----来操纵HL7信息的内容,但是很有必要将他们包/不包含在SOAP信息里同时转移到WEB服务基层组织/从WEB服务基层组织那里接收。在这个层上,将信息类型声明成"Any"的优点是Stubs 和代理只使用少量的代码,这要比使用HL7信息类型少的多。
很有趣的是,WEB服务代理和Stub 都不需要知道他们要处理的HL7信息的完整结构。实际上,在代理和Stub层上可能不需要----- 也没有这个必要-----来操纵HL7信息的内容,但是很有必要将他们包/不包含在SOAP信息里同时转移到WEB服务基层组织/从WEB服务基层组织那里接收。在这个层上,将信息类型声明成"Any"的优点是Stubs 和代理只使用少量的代码,这要比使用HL7信息类型少的多。
事实上,我们能为只出现在Transmission Wrapper和Control Act Wrapper固定元素里的HL7信息使用固定的信息类型,用一种不透明的方式(也就是,只有这些元素被声明成"Any")来封装大部分的Control Wrapper 和信息体。我们将这种方法称为"Surrogate" 信息类型方法。代码例子7就是被称作MCCI_MT000100.Message的程序片段,MCCI_MT000100.Message在接下来的代理信息类型方法中被定义,在附录A中有完整的表单程序。
代码例子7。HL7信息代理类型表单程序片段 <!--Embedded in Transmission Wrapper...
[...]
-->
<xs:complexType name="MCCI_MT000100.ControlActProcess">
<xs:sequence>
<xs:any namespace="##other" processContents="lax"
maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="type" type="Classes" default="ControlAct"/>
<xs:attribute name="classCode" type="ActClass"/>
<xs:attribute name="moodCode" type="x_ActMoodIntentEvent"/>
<xs:attribute name="templateId" use="optional">
<xs:simpleType>
<xs:list itemType="oid"/>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="typeID" use="optional">
<xs:simpleType>
<xs:list itemType="oid"/>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="realmCode" use="optional">
<xs:simpleType>
<xs:list itemType="cs"/>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="nullFlavor" type="cs" use="optional"/>
</xs:complexType>
<!--Transmission Wrapper continues...
[...]
-->
当使用代理信息类型这种方法时,代码例子8就是通过使用WSDL.exe 工具而产生的服务代理代码的一部分。读者可以和例子6中出现类似的粗体代码进行一下比较。
代码例子8。为代理信息类型而产生的服务代理代码。 //
// This source code was auto-generated by wsdl, Version=1.1.4322.2032.
//
//[...] <-code omitted here
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(TypeName="MCCI_MT000100.
Message", Namespace="urn:hl7-org:v3")]
public class MCCI_MT000100Message {
/// <remarks/>
public II id;
/// <remarks/>
public TS creationTime;
/// <remarks/>
public ST securityText;
/// <remarks/>
public MessageversionCode versionCode;
/// <remarks/>
public II interactionId;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("profileId")]
public II[] profileId;
/// <remarks/>
public MessageprocessingCode processingCode;
/// <remarks/>
public MessageprocessingModeCode processingModeCode;
/// <remarks/>
public MessageacceptAckCode acceptAckCode;
/// <remarks/>
public INT sequenceNumber;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("attachmentText")]
public ED[] attachmentText;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("attentionLine")]
public MCCI_MT000100AttentionLine[] attentionLine;
/// <remarks/>
public MCCI_MT000100ControlActProcess controlActProcess;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("receiver")]
public MCCI_MT000100Receiver[] receiver;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("respondTo")]
public MCCI_MT000100RespondTo[] respondTo;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("sender")]
public MCCI_MT000100Sender[] sender;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
[System.ComponentModel.DefaultValueAttribute(Classes.Message)]
public Classes type = Classes.Message;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string[] templateId;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string[] typeID;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute(DataType="token")]
public string[] realmCode;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute(DataType="token")]
public string nullFlavor;
}
当在代理/ Stubs(产生于多重WSDL契约)中使用类MCCI_MT000100Message时,这个类所描述的HL7信息将是相同的, 同时它还一直允许代理/Stub消耗他们能够见到的Transmission Wrapper的元素。
同时,通过使用传统的类继承方法,我们可以从封装包含了各自HL7消息类型那里继承子类。因此,我们能够调控以前列出的优点,在实现过程中,没有妥协我们使用HL7说明书的能力。
|