在上一篇文章《互操作消息统一管理系列:Message Bank》中,我们了解到在Message Bank中,消息均以半结构化(XML)或非结构化(Stream)的形式保存,因此无法与客户端的结构化消息一样,直接支持基于索引的检索。为此,需要在Message Bank中定义Search Table以支持查询。关于Search Table的定义和作用,请查阅https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.UI...。https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.UI...。
一. 在Message Bank中查询消息的特殊之处
大家如果使用过消息查看器,则能够了解IRIS自动持久化消息并提供界面让大家能够根据消息头中(如发生事件、来源、目标等)或消息体中(如患者姓名、诊断名称等消息的具体属性)来查询消息。
而在Message Bank上进行查询时,如果直接使用消息查看器,能够查询的是Message Bank的Production中传输的消息,而若不是在源系统中出现的消息,这一点一定不要混淆。
Message Bank提供了消息仓库查看器供大家查询源系统中的消息
![image](https://i-blog.csdnimg.cn/blog_migrate/1fcfae13f5a201e45a84c396fe3980eb.png)
查询界面尽管在风格与功能上与消息查看器非常相似,但查询的目标是在Message Bank中转储的消息(存储格式为虚拟文档或字符流,见上一篇文章)。因此,查询索引与原来的消息截然不同,需要单独创建。其中,虚拟文档在IRIS中有原生支持,我们先来看自定义消息类型的处理过程。
先看这样一个案例。
二. 测试系统
假设我们有一个REST接口实现的添加患者业务,如下所示:
![image](https://i-blog.csdnimg.cn/blog_migrate/4f03f59adbbbb4650286e949591a4d2f.png)
实现了REST接口,接收如下格式的POST请求,在该实例数据库中创建患者:
![image](https://i-blog.csdnimg.cn/blog_migrate/cebdac0ae613da4f055c00f3889ff1f0.png)
注意,从外部JSON传入的报文,在经过IRIS处理的过程中,会被转换为XML格式的消息,如下:
![image](https://i-blog.csdnimg.cn/blog_migrate/302d6621ce6a01ed1a9470f44b4c95c2.png)
在被转储到Message Bank后,以同样结构的XML字符流存储
![image](https://i-blog.csdnimg.cn/blog_migrate/198bed2a2722197268c6608d31764494.png)
三. 在消息转储过程中创建索引
Message Bank中转储的消息默认是没有索引的,需要在消息转储过程中创建。
通过回调干预消息转储过程
Message Bank的Production中的服务Ens.Enterprise.MsgBank.TCPService提供了回调函数入口,使用户可以在消息转储过程中执行自定义的逻辑实现需求。因此,可用于触发对Search Table索引的建立过程。
要定义回调函数,则需要继承类Ens.Enterprise.MsgBank.BankHelperClass,并实现方法OnBankMsg,如下所示:
Class MessageBank.MsgSerializationHelper Extends Ens.Enterprise.MsgBank.BankHelperClass [ Language = objectscript ]
{
/// 参数IndexClassList用于指定将被该回调函数索引的(客户端)消息类
Parameter IndexClassList = "LIS.REST.MSG.PatientReq";
/// 消息签名不能更改
ClassMethod OnBankMsg(pHeader As Ens.Enterprise.MsgBank.MessageHeader, pFullHeaderID As %String, pBody As %RegisteredObject = "", pFullBodyID As %String, pService As Ens.Enterprise.MsgBank.TCPService) As %Status [ Language = objectscript ]
{
Set tSC = $$$OK
do
{
set clientBodyClassName = pHeader.ClientBodyClassName
/// 如果消息类型在本类的关注列表中,则进行后续处理;否则忽略,只被转储
if ..#IndexClassList[clientBodyClassName
{
/// 使用服务实例将Message Bank消息(包含客户端消息信息)传递给后续组件处理
do pService.SendRequestAsync("MessageBank.Process.ReceiveBankMsgBPL", pHeader)
}
} while 0
Quit $$$OK
}
}
注意,尽管整个建立Search Table的过程可以在服务中完成,但由于服务不能开启多进程,对于构建索引这样的高延迟任务,容易变成性能瓶颈。因此,强烈建议将消息转发给BP和BO,并启用多进程处理提高处理速度。
定义好回调函数和Helper类执行,在BS中引用即可,如下:
![image](https://i-blog.csdnimg.cn/blog_migrate/6817a0e7585c511093ddeeb0b14e7f72.png)
在Business Process中分发建立Search Table
建立索引的过程需要应用特定的Search Table类实现,因此通过Business Service将消息分发到Business Process后,则可通过BP根据消息的类型再调用特定的Search Table实现,如下:
![image](https://i-blog.csdnimg.cn/blog_migrate/a0391f1d5d3cc6251fbbc662a94cef4a.png)
即根据消息类型的不同,使用不同的逻辑进行索引。其中,对于本例的消息类型LIS.REST.MSG.PatientReq
![image](https://i-blog.csdnimg.cn/blog_migrate/91a8cb3d0790fea763111bad4b0c92bb.png)
在switch分支中判定后即可调用对应的代码段处理。
![image](https://i-blog.csdnimg.cn/blog_migrate/31d5d34fd949e986918aba940ac85c87.png)
在该段代码中,我们需要使用Message Bank消息(该BP的request)中记录的MessageBodyId,对于在源系统中的结构化消息,在Message Bank中被记录为流,该Id即为流记录的Id。根据Id加载流对象后,将该流对象通过IndexDoc方法传递给这类消息对象专用的Search Table实现类完成索引的建立。
Search Table类的实现
该类需要继承Ens.CustomSearchTable并实现OnIndexDoc回调方法,如下:
Class MessageBank.SearchTable.Client.LISReqTable Extends Ens.CustomSearchTable [ Language = objectscript ]
{
///在Search Table中对要建立索引的字段建模,并对该字段建立索引,在查询时即可套用
Property FullName As %String(MAXLEN = 36);
Index IdxFullName On FullName;
///特别提醒,在Message Bank中转储的结构化消息类型会变成字符流,因此,我们是在对字符流建立索引,而不是对源系统的消息类型建立索引
Parameter DOCCLASS = "%Stream.GlobalCharacter";
///建立索引的回调方法
ClassMethod OnIndexDoc(pDocObj As %Persistent, ByRef pSearchTable As MessageBank.SearchTable.Client.LISReqTable) As %Status
{
set st = $$$OK
do
{
set text = pDocObj.Read($$$MaxLocalLength)
set text = $ZSTRIP(text, "*C",, $C(9,10,13,133))
///读取字符流建立虚拟文档对象
set vDoc = ##class(EnsLib.EDI.XML.Document).ImportFromString(text, .st) quit:$$$ISERR(st)
///从虚拟文档对象获得所需的属性值并为Search Table对象赋值
set pSearchTable.FullName = vDoc.GetValueAt("/PatientReq/FullName")
} while 0
if $$$ISERR(st) $$$LOGWARNING("Failed to index message LIS.REST.MSG.PatientReq:"_pDocObj.%Id()_" "_st)
quit $$$OK
}
}
应当注意的是,使用Search Table支持查询,则意味着查询的目标是Search Table对象而不是直接在Message Bank转储的消息上进行查询。平台的查询功能会负责维护查询逻辑,在Search Table上执行查询并关联到消息上去。
因此,Search Table的属性可以使用消息中的属性组合,大家也可以思考和改进本例实现这样一个场景,源系统中不传FullName全名,代之以分在两个属性中的姓和名。但在Message Bank中却可以在Search Table上建立全名FullName并建立索引,使用户可以通过全名查询消息。
还应当注意到的是,在解析字符流获取所需的属性值时,用户可以选择使用虚拟文档处理,如果能够获得原始消息的类定义,也可以将字符流转换为消息对象再取值。考虑到在实际的生产环境下在Message Bank并不能确保获取源消息定义,采用虚拟文档更为通用且不受类型限制,推荐大家使用。
四. 实现效果
定义好上述代码后启动Message Bank的Production,并测试源系统的接口:
![image](https://i-blog.csdnimg.cn/blog_migrate/f9bc43fd63ee7df95d55b3580e2ce75a.png)
此时查看Message Bank的production上的消息:
![image](https://i-blog.csdnimg.cn/blog_migrate/40503e7ae9ac2ad6b062c8682599ec14.png)
可看到在Message Bank有新消息触发了处理流程
通过SQL查询Search Table中的记录
![image](https://i-blog.csdnimg.cn/blog_migrate/8a9965dbd05df3fc34781480bb1a03c2.png)
则可看到最后一行中记录的全名
再转到消息仓库查看器,查询时选择查询表域,选择好对应的SearchTable类型,选择我们刚才使用的全名Biden
![image](https://i-blog.csdnimg.cn/blog_migrate/da6616bf746c9a0f24e177b8020f6d0c.png)
则可查询到消息和对应的Message Trace
![image](https://i-blog.csdnimg.cn/blog_migrate/72697c4845ad2f387614afc78f4ed814.png)
对虚拟文档的处理过程
源系统的消息类型如为虚拟文档,例如EnsLib.EDI.XML.Document,则Message Bank的转储消息类型也是EnsLib.EDI.XML.Document,此时的处理流程与上述流程一致,但处理细节上会简单一些。体现在
MessageBank消息对象的获取
![image](https://i-blog.csdnimg.cn/blog_migrate/b63553c4d43d009845a0412517be8842.png)
如上图所示,不直接使用字符流,通过Id直接获得虚拟文档对象即可。
Search Table的定义
Class MessageBank.SearchTable.DocSearchTable Extends EnsLib.EDI.XML.SearchTable [ Language = objectscript ]
{
XData SearchSpec [ XMLNamespace = "http://www.intersystems.com/EnsSearchTable" ]
{
<Items>
<Item DocType="example:shiporder" PropName="OrderPerson" PropType="String:CaseSensitive">{orderperson}</Item>
<Item DocType=":root" PropName="d_e" PropType="String:CaseSensitive">{/root/d/e}</Item>
<Item DocType=":root" PropName="li_la_c" PropType="String:CaseSensitive">{/root/lis/li}</Item>
</Items>
}
}
如上所示,EnsLib.EDI.XML.Document有系统原生的Search Table模版EnsLib.EDI.XML.SearchTable,继承该类后在XData中定义好Search Table的属性和取值路径(在消息XML中的路径)即可。 关于EnsLib.EDI.XML.Document的Search Table,可通过https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls...获取更多信息。
关于EnsLib.EDI.XML.Document的Search Table,可通过
重要限制
目前的产品版本中并不能很好的支持在消息已转储后再生成索引,因此,通常需要在消息采集过程中生成索引。