#业务流程 (BPL)

0 关注者 · 13 帖子

InterSystems Ensemble 业务流程语言 (BPL) 是一种用于在标准 XML 文档中描述可执行业务流程的语言。

文档

文章 Michael Lei · 九月 27, 2024 11m read

欢迎来到我的 CI/CD 系列的下一个章节,我们将探讨使用 InterSystems 技术和 GitLab 进行软件开发可以采用的几种方式。

今天,我们来谈谈互操作性。

问题

当您有一个有效的互操作性生产时,您有两个独立的流程:一个是处理消息的可以正常运行的生产流程,另一个是更新代码、生产配置和系统默认设置的 CI/CD 流程。

显然,CI/CD 流程会影响互操作性。 但问题是:

  • 更新期间究竟发生了什么?
  • 我们需要做些什么以在更新期间尽可能缩短或消除生产停机时间?

术语

  • 业务主机 (BH) – 互操作性生产的一个可配置元素:业务服务 (BS)、业务流程(BP、BPL)或业务操作 (BO)。
  • 业务主机作业 (Job) – 运行业务主机代码并由互操作性生产管理的 InterSystems IRIS 作业。
  • 生产 – 业务主机的互联集合。
  • 系统默认设置 (SDS) – 特定于安装 InterSystems IRIS 的环境的值。
  • 有效消息 – 当前正在由某个业务主机作业处理的请求。 一个业务主机作业最多只能有一条有效消息。 没有有效消息的业务主机作业处于空闲状态。

发生了什么?

我们从生产生命周期开始。

生产启动

首先,可以启动生产。 每个命名空间只能同时运行一个生产,通常而言(除非您真正知道自己在做什么以及为什么这样做),每个命名空间内只能运行一个生产。 不推荐在一个命名空间中于两个或多个不同的生产之间来回切换。 启动生产会启动生产中定义的所有已启用的业务主机。 某些业务主机启动失败不会影响生产启动。

提示:

  • 可以从系统管理门户或者调用以下代码来启动生产:##class(Ens.Director).StartProduction("ProductionName")
  • 通过实现 OnStart 方法可以在生产启动时(在启动任何业务主机作业之前)执行任意代码
  • 生产启动是一个可审核事件。 您始终可以在审核日志中查看是谁在何时启动的生产。

生产更新

在生产启动后,Ens.Director 会持续监视正在运行的生产。 生产存在两种状态:目标状态,在生产类和系统默认设置中定义;以及_运行状态_ – 当前运行的作业及其创建时应用的设置。 如果所需状态与当前状态相同,则一切正常;但如果有差异,就可以(也应该)更新生产。 通常,您会在系统管理门户的“生产配置”页面上看到一个红色的 Update 按钮。

更新生产意味着尝试使当前生产状态与目标生产状态匹配。

当您运行 ##class(Ens.Director).UpdateProduction(timeout=10, force=0) 以更新生产时,它会为每个业务主机执行以下操作:

  1. 将有效设置与生产/SDS/类设置进行比较
  2. 当且仅当 (1) 显示不匹配时,业务主机会被标记为过时并需要更新。

为每个业务主机运行此操作后,UpdateProduction 会构建一组更改:

  • 要停止的业务主机
  • 要启动的业务主机
  • 要更新的生产设置

然后,应用这些更改。

通过这种方式,“更新”设置而不更改任何内容不会导致生产停机。

提示:

  • 可以从系统管理门户或者调用以下代码来更新生产:##class(Ens.Director).UpdateProduction(timeout=10, force=0)
  • 默认的系统管理门户更新超时为 10 秒。 如果您知道处理消息需要更长时间,请调用 Ens.Director:UpdateProduction 并设置更长的超时。
  • 更新超时是一个生产设置,您可以将其更改为更大的值。 此设置适用于系统管理门户。

代码更新

UpdateProduction 不会使用过时的代码更新 BH。 这是一种以安全为导向的行为,但如果您想要在底层代码更改时自动更新所有正在运行的 BH,请按以下步骤操作:

首先,按以下方式加载和编译:

do $system.OBJ.LoadDir(dir, "", .err, 1, .load)
do $system.OBJ.CompileList(load, "curk", .errCompile, .listCompiled)

现在,listCompiled 将包含由于 u 标志而实际编译的所有条目(使用 git 差异来尽可能减小加载集)。 使用此 listCompiled 获取所有已编译类的 $lb:

set classList = ""
set class = $o(listCompiled(""))
while class'="" { 
  set classList = classList _ $lb($p(class, ".", 1, *-1))
  set class=$o(listCompiled(class))
}

然后,计算需要重启的 BH 的列表:

SELECT %DLIST(Name) bhList
FROM Ens_Config.Item 
WHERE 1=1
  AND Enabled = 1
  AND Production = :production
  AND ClassName %INLIST :classList

最后,在获取 bhList 后,停止并启动受影响的主机:

for stop = 1, 0 {
  for i=1:1:$ll(bhList) {
    set host = $lg(bhList, i)
    set sc = ##class(Ens.Director).TempStopConfigItem(host, stop, 0)
  }
  set sc = ##class(Ens.Director).UpdateProduction()
}

生产停止

生产可以停止,这意味着向所有业务主机作业发送关闭请求(如果存在,则在它们处理完有效消息后安全地关闭)。

提示:

  • 可以从系统管理门户或者调用以下代码来停止生产:##class(Ens.Director).StopProduction(timeout=10, force=0)
  • 默认的系统管理门户停止超时为 120 秒。 如果您知道处理消息需要更长时间,请调用 Ens.Director:StopProduction 并设置更长的超时。
  • 关闭超时是一个生产设置。 您可以将其更改为更大的值。 此设置适用于系统管理门户。
  • 通过实现 OnStop 方法可以在生产停止时执行任意代码
  • 生产停止是一个可审核事件,您始终可以在审核日志中查看是谁在何时停止的生产。

重要的一点是,生产是业务主机的总和:

  • 启动生产意味着启动所有已启用的业务主机。
  • 停止生产意味着停止所有正在运行的业务主机。
  • 更新生产意味着计算出过时的业务主机的子集,因此首先停止它们,然后立即重新启动。 此外,新增的业务主机只会启动,从生产中删除的业务主机只会停止。

这会将我们带到业务主机的生命周期。

业务主机启动

业务主机由相同的业务主机作业组成(根据池大小设置的值)。 启动业务主机意味着启动所有业务主机作业。 它们会并行启动。

单个业务主机作业的启动方式如下:

  1. 互操作性作业是一个将成为业务主机作业的新进程。
  2. 新进程注册为互操作性作业。
  3. 业务主机代码和适配器代码加载到进程内存中。
  4. 与业务主机和适配器相关的设置加载到内存中。 优先级顺序如下: a. 生产设置(覆盖系统默认设置和类设置)。 b. 系统默认设置(覆盖类设置)。 c. 类设置。
  5. 作业准备就绪并开始接受消息。

在完成 (4) 后,作业无法更改设置或代码,因此当您导入新的/相同的代码和新的/相同的系统默认设置时,它不会影响当前正在运行的互操作性作业。

业务主机停止

停止业务主机作业意味着:

  1. 互操作性命令作业停止接受更多消息/输入。
  2. 如果存在有效消息,业务主机作业具有一定的超时秒数来处理该消息(完成消息会结束 BO 的 OnMessage 方法,BS 的 OnProcessInput,BPL BP 的状态 S<int> 方法以及 BP 的 On* 方法)。
  3. 如果在超时前有效消息未被处理且 force=0,则生产更新会在该业务主机上失败(您会在 SMP 中看到一个红色的 Update 按钮)。
  4. 如果此列表中的任何一项为真,则表示停止成功:
    • 没有有效消息
    • 有效消息在 timeout 之前处理完
    • 有效消息在超时之前未处理完但 force=1
  5. 作业的互操作性取消注册并停止。

业务主机更新

业务主机更新意味着停止当前运行的业务主机作业并启动新作业。

业务规则、路由规则和 DTL

所有业务主机会在新版本的业务规则、路由规则和 DTL 可用时立即开始使用它们。 在这种情况下,不需要重启业务主机。

离线更新

不过,有时生产更新需要单个业务主机停机。

规则取决于新代码

考虑接下来的情况。 您有一个当前的路由规则 X,它根据任意标准将消息路由到业务流程 A 或 B。 在新提交中,您同时添加:

  • 业务流程 C
  • 新版本的路由规则 X,可以将消息路由到 A、B 或 C。

在此场景下,您不能先加载规则,然后再更新生产。 原因在于,新编译的规则会立即开始将消息路由到业务流程 C,而 InterSystems IRIS 可能尚未编译该规则,或者互操作性尚未更新以供使用。 在这种情况下,您需要禁用包含路由规则的业务主机,更新代码,更新生产,然后再次启用业务主机。

注:

  • 如果您使用生产部署文件更新生产,它会自动禁用/启用所有受影响的 BH。
  • 对于 InProc 调用的主机,编译会使调用方持有的特定主机的缓存失效。

业务主机之间的依赖关系

业务主机之间的依赖关系至关重要。 假设您有业务流程 A 和 B,其中 A 向 B 发送消息。 在新提交中,您同时添加:

  • 新版本的流程 A,可以在向 B 发送的请求中设置新属性 X
  • 新版本的流程 B,可以处理新属性 X

在此场景中,我们必须首先更新流程 B,然后更新流程 A。 您可通过以下两种方式之一完成此操作:

  • 在更新期间禁用业务主机
  • 将更新拆分为两步:首先,仅更新流程 B,然后在单独的更新中开始从流程 A 向流程 B 发送消息。

这个主题一个更具挑战性的变体是,新版本的流程 A 和流程 B 与旧版本不兼容,这需要业务主机停机。

队列

如果您知道更新后某个业务主机将无法处理旧消息,则需要确保在更新前该业务主机队列为空。 为此,请禁用所有向该业务主机发送消息的业务主机,并等到其队列变空。

BPL 业务流程中的状态更改

首先,简单介绍一下 BPL BP 的运作方式。 在您编译 BPL BP 后,会在与完整 BPL 类同名的软件包中创建两个类:

  • Thread1 类包含方法 S1、S2、... SN,对应于 BPL 中的活动
  • Context 类包含所有上下文变量,以及 BPL 将执行的下一个状态(即 S5

此外,BPL 类是持久类,可以存储当前正在处理的请求。

BPL 的工作方式是在 Thread 类中执行 S 方法并相应地更新 BPL 类表、Context 表和 Thread1 表,其中一条“正在处理”的消息是 BPL 表中的一行。 请求处理完后,BPL 会删除 BPL、ContextThread 条目。 由于 BPL BP 是异步的,通过在 S 调用之间保存信息并在不同请求之间切换,一个 BPL 作业可同时处理多个请求。 例如,BPL 处理一个请求,直至其到达 sync 活动 – 等待来自 BO 的回答。 它会将当前上下文保存到磁盘中,同时将 %NextState 属性(位于 Thread1 类中)设置为响应活动 S 方法,并在 BO 回答前继续处理其他请求。 BO 回答后,BPL 会将上下文加载到内存中,并执行与 %NextState 属性中保存的状态对应的方法。

现在,当我们更新 BPL 时会发生什么? 首先,我们需要检查是否至少满足以下两个条件之一:

  • 更新期间,上下文表为空,这意味着没有正在处理的有效消息。
  • 新状态与旧状态相同,或者新状态是在旧状态之后添加的。

如果至少满足一个条件,我们就可以开始更新。 要么没有需要更新后 BPL 处理的更新前请求,要么状态是在结束时添加的,这意味着旧请求也可以进入其中(假设更新前请求与更新后 BPL 活动和处理兼容)。

但是,如果您有正在处理的有效请求,而 BPL 更改了状态顺序,该怎么办? 理想情况下,如果您可以等待,则禁用 BPL 调用方并等待队列为空。 验证上下文表是否也为空。 请记住,队列只显示未处理的请求,而上下文表存储正在处理的请求,因此您可能会遇到一个非常繁忙的 BPL 显示零队列大小的情况,这是正常的。 之后,禁用 BPL,执行更新并启用所有之前禁用的业务主机。

如果无法实现(通常是在 BPL 非常长的情况下,例如,我记得我更新过一个花了大约一周时间处理请求的 BPL,或者更新窗口太短),请使用 BPL 版本控制

或者,您也可以编写一个更新脚本。 在此更新脚本中,将旧的后续状态映射到新的后续状态并在 Thread1 表上运行,以便更新的 BPL 可以处理旧请求。 当然,在更新期间必须禁用 BPL。 也就是说,这是一种极为罕见的情况,通常您不必这样做,但如果您需要这样做,方法就是如此。

结论

为了将底层代码更改后实现生产所需的操作数降到最低,互操作性实现了一种复杂的算法。 每次 SDS 更新时,调用具有安全超时的 UpdateProduction。 对于每次代码更新,您都需要决定一种更新策略。

通过使用 git 差异减少编译的代码量有助于缩短编译时间,但利用自身“更新”代码并重新编译它或使用相同的值“更新”设置不会触发或要求进行生产更新。

更新和编译业务规则、路由规则和 DTL 可使它们在未进行生产更新的情况下立即可用。

最后,生产更新是一项安全操作,通常不需要停机。

链接

在本文撰写期间,@James MacKeith@Dmitry Zasypkin@Regilo Regilio Guedes de Souza 提供了宝贵的帮助,作者对此深表感谢。

0
0 117
文章 Lilian Huang · 三月 14, 2024 6m read

人工智能(AI)最近受到广泛关注,因为它可以改变我们生活的许多领域。更好的计算机能力和更多数据帮助人工智能完成了许多惊人的事情,例如改进医学测试和制造自动驾驶汽车。人工智能还可以帮助企业做出更好的决策,提高工作效率,这也是人工智能越来越流行和广泛应用的原因。如何将 OpenAI API 调用集成到现有的 IRIS 互操作性应用程序中?

 

0
0 208
文章 Qiao Peng · 一月 31, 2024 21m read

InterSystems IRIS、Health Connect和上一代的Ensemble提供了优秀的互操作架构,但即便有低代码开发能力,很多开发者还是希望能用自己的技术栈语言在InterSystems的产品上开发互操作产品。

考虑到互操作产品本身的开放性要求和各个技术栈背后庞大的生态价值,InterSystems IRIS和Health Connect提供了Production EXtension (PEX)架构,让开发者使用自己的技术栈语言来开发互操作解决方案。目前PEX支持Java、.net、Python。

这里我们介绍使用Java利用PEX进行互操作产品的开发。

一 InterSystems IRIS上使用Java开发的基础

在进入PEX主题前,需要简单介绍一下Java在InterSystems IRIS上开发的各种技术选项,因为PEX也是以这些技术选项为基础的。

0
1 274
文章 sun yao · 一月 4, 2023 6m read

一、目的 因使用BP中Switch分支来区分不同节点(即接口)及流向,个人已知方法中无相关统计功能能直接获取各分支节点及流向BO(调用第三方系统)信息,且当前使用系统未封装相关模块供查询,故通过解析BP中XData的方式获取Switch-Case节点及Call调用相关信息

二、解析类中的XData数据

/// 解析类中的XData内容
/// SELECT * FROM %Dictionary.CompiledXData
/// xdataID XData表ID
/// d ##class(BOE.TEST.ClassAnalysis).XDataAnalysisTextReader("JHIP.SM.BP.View||BPL")
ClassMethod XDataAnalysisTextReader(xdataID As %String) As %Status
{
   //获取XData流
   s compiledXdata=##class(%Dictionary.CompiledXData).%OpenId(xdataID)
   s tStream=compiledXdata.Data
   If '$IsObject(tStream) s tSC=%objlasterror q
   
   //逐行读取xml
   s status=##class(%XML.TextReader).ParseStream(tStream,.textreader)
   if $$$ISERR(status) do $System.Status.DisplayError(status) q
   s conditionMsgCode="sy"
   while textreader.Read()
   {
	   //节点类型为element
       if (textreader.NodeType="element"){
	       //节点路径中包含/switch/case且case为结尾
	       if ((textreader.Path["/switch/case")&&(textreader.Path'["/switch/case/")){ //接口switch case解析接口 element
	       		//节点有属性
	       		if (textreader.HasAttributes){
		       		//移动到对应的属性节点Attribute
		       		if (textreader.MoveToAttributeName("condition")){
			       		s condition=textreader.Value
			       		s len=$l(condition,"||")
			       		for i=1:1:len{
				       		s tepCondition=$p(condition,"||",i)
				       		if (tepCondition["'="){
					       		s conditionMsgCode=$tr($p(tepCondition,"action",2),"""")
					       		if (conditionMsgCode=""){
						       		s conditionMsgCode=$tr($p(tepCondition,"OriginalDocId",2),"""")
					       		}
				       		}else{
				       			s conditionMsgCode=$tr($p(tepCondition,"=",2),"""")
				       		}
			       		}
			       		//b:conditionMsgCode=""
			       		s conditionMsgCode=$tr(conditionMsgCode,"()")
			       		if (conditionMsgCode=""){ //如果未定义分支条件,则设置为类名
				       		s conditionMsgCode=xdataID
			       		}
			       		//移动到下一个节点Node
			       		d textreader.Read()
			       		if (textreader.HasAttributes){
				       		if (textreader.MoveToAttributeName("name")){
					       		s name=textreader.Value
			       				s ^sy(conditionMsgCode)=name
				       		}
			       		}else{
				       		s ^sy(conditionMsgCode)=""
			       		}
		       		}
	       		}		
	       }
	       if ((textreader.Path["/switch/case/")&&(textreader.Path["/call")&&(textreader.Path'["/call/")){ //接口switch case call解析接口 element
	       		s callName="",callTarget=""
	       		if (textreader.HasAttributes){
		       		if (textreader.MoveToAttributeName("name")){
			       		s callName=textreader.Value
			       		s ^sy(conditionMsgCode,"callName",callName)=callName
		       		}
		       		if (textreader.MoveToAttributeName("target")){
			       		s callTarget=textreader.Value
			       		s ^sy(conditionMsgCode,"callTarget",callTarget)=callTarget
		       		}
	       		}		
	       }
       }
   }
}

XData解析测试

  • 测试用BP结构
Class JHIP.SM.BP.View Extends Ens.BusinessProcessBPL
{

/// BPL Definition
XData BPL [ XMLNamespace = "http://www.intersystems.com/bpl" ]
{
<process language='objectscript' request='Ens.Request' response='Ens.Response' height='2000' width='2015' >
<context>
<property name='sqltype' type='%String' initialexpression='"Query"' instantiate='0' >
<parameters>
<parameter name='MAXLEN'  value='50' />
</parameters>
</property>
<property name='sqlstatement' type='%String' instantiate='0' >
<parameters>
<parameter name='MAXLEN'  value='1000' />
</parameters>
</property>
<property name='tablename' type='%String' instantiate='0' >
<parameters>
<parameter name='MAXLEN'  value='50' />
</parameters>
</property>
<property name='sql' type='JHIPLIB.SQL.MSG.SQLMessage' instantiate='0' />
<property name='sqlparameter' type='%String' instantiate='0' >
<parameters>
<parameter name='MAXLEN'  value='50' />
</parameters>
</property>
</context>
<sequence xend='200' yend='600' >
<switch name='判断接口' xpos='200' ypos='250' xend='200' yend='500' >
<case condition='request.OriginalDocId="JH1301"' >
<sequence name='JH1301手麻测试' xpos='335' ypos='400' xend='200' yend='850' >
<assign name="设定查询参数" property="context.sqlparameter" value="request.GetValueAt(&quot;/REQUEST/KSBM&quot;)" action="set" xpos='200' ypos='250' >
<annotation><![CDATA[设定查询参数]]></annotation>
</assign>
<assign name="设定SQL脚本" property="context.sqlstatement" value="&quot;select * from whkx_oas_operation_dept&quot;" action="set" xpos='200' ypos='350' >
<annotation><![CDATA[设定SQL脚本]]></annotation>
</assign>
<assign name="设定数据库表名" property="context.tablename" value="&quot;whkx_oas_operation_dept&quot;" action="set" xpos='200' ypos='450' >
<annotation><![CDATA[设定数据库表名]]></annotation>
</assign>
<assign name="设定SQL类型" property="context.sqltype" value="&quot;Query&quot;" action="set" xpos='200' ypos='550' >
<annotation><![CDATA[有QUERY、INSERT、UPDATE、DELETE4种]]></annotation>
</assign>
<code name='拼接sql脚本生成xml消息' xpos='200' ypos='650' >
<annotation><![CDATA[拼接sql脚本生成xml消息]]></annotation>
<![CDATA[ 
 Set sqlreq=##class(JHIPLIB.SQL.MSG.SQLMessage).%New()
 Set context.sqlstatement=$replace(context.sqlstatement,"?",context.sqlparameter)
 
 Set sqlreq.sqlreq=context.sqlstatement
 Set sqlreq.sqltype=context.sqltype
 Set sqlreq.tablename=context.tablename
 
 Set context.sql=sqlreq
 ]]>
</code>
<call name='查询手麻视图服务1' target='测试服务BO' async='0' xpos='200' ypos='750' >
<annotation><![CDATA[查询手麻视图服务1]]></annotation>
<request type='Ens.Request' >
<assign property="callrequest" value="context.sql" action="set" />
</request>
<response type='Ens.Response' >
<assign property="response" value="callresponse" action="set" />
</response>
</call>
</sequence>
</case>
<case condition='request.OriginalDocId="JH1302"' >
<sequence name='his测试' xpos='605' ypos='400' xend='200' yend='850' >
<assign name="设定查询参数" property="context.sqlparameter" value="request.GetValueAt(&quot;/REQUEST/KSBM&quot;)" action="set" xpos='200' ypos='250' >
<annotation><![CDATA[设定查询参数]]></annotation>
</assign>
<assign name="设定SQL脚本" property="context.sqlstatement" value="&quot;SELECT DISTINCT ORDERED_EMP_CODE  FROM core_his50.HIS_DEPT_INCOME_MONTH_I_H080 WHERE  HIS_PATIENT_ID = 'ZY010000000039'&quot;" action="set" xpos='200' ypos='350' >
<annotation><![CDATA[设定SQL脚本]]></annotation>
</assign>
<assign name="设定数据库表名" property="context.tablename" value="&quot;HIS_DEPT_INCOME_MONTH_I_H080&quot;" action="set" xpos='200' ypos='450' >
<annotation><![CDATA[设定数据库表名]]></annotation>
</assign>
<assign name="设定SQL类型" property="context.sqltype" value="&quot;Query&quot;" action="set" xpos='200' ypos='550' >
<annotation><![CDATA[有QUERY、INSERT、UPDATE、DELETE4种]]></annotation>
</assign>
<code name='拼接sql脚本生成xml消息' xpos='200' ypos='650' >
<annotation><![CDATA[拼接sql脚本生成xml消息]]></annotation>
<![CDATA[ 
 Set sqlreq=##class(JHIPLIB.SQL.MSG.SQLMessage).%New()
 Set context.sqlstatement=$replace(context.sqlstatement,"?",context.sqlparameter)
 
 Set sqlreq.sqlreq=context.sqlstatement
 Set sqlreq.sqltype=context.sqltype
 Set sqlreq.tablename=context.tablename
 
 Set context.sql=sqlreq
 ]]>
</code>
<call name='查询手麻视图服务2' target='his测试BO' async='0' xpos='200' ypos='750' >
<annotation><![CDATA[查询手麻视图服务1]]></annotation>
<request type='Ens.Request' >
<assign property="callrequest" value="context.sql" action="set" />
</request>
<response type='Ens.Response' >
<assign property="response" value="callresponse" action="set" />
</response>
</call>
</sequence>
</case>
<case condition='request.OriginalDocId="JH0000"' >
<sequence name='JH000001' xpos='875' ypos='400' xend='200' yend='850' >
<assign name="设定查询参数" property="context.sqlparameter" value="request.GetValueAt(&quot;/REQUEST/KSBM&quot;)" action="set" xpos='200' ypos='250' >
<annotation><![CDATA[设定查询参数]]></annotation>
</assign>
<assign name="设定SQL脚本" property="context.sqlstatement" value="&quot;SELECT * FROM core_his50.His_dict_dept_H080&quot;" action="set" xpos='200' ypos='350' >
<annotation><![CDATA[设定SQL脚本]]></annotation>
</assign>
<assign name="设定数据库表名" property="context.tablename" value="&quot;core_his50.His_dict_dept_H080&quot;" action="set" xpos='200' ypos='450' >
<annotation><![CDATA[设定数据库表名]]></annotation>
</assign>
<assign name="设定SQL类型" property="context.sqltype" value="&quot;Query&quot;" action="set" xpos='200' ypos='550' >
<annotation><![CDATA[有QUERY、INSERT、UPDATE、DELETE4种]]></annotation>
</assign>
<code name='拼接sql脚本生成xml消息' xpos='200' ypos='650' >
<annotation><![CDATA[拼接sql脚本生成xml消息]]></annotation>
<![CDATA[ 
 Set sqlreq=##class(JHIPLIB.SQL.MSG.SQLMessage).%New()
 Set context.sqlstatement=$replace(context.sqlstatement,"?",context.sqlparameter)
 
 Set sqlreq.sqlreq=context.sqlstatement
 Set sqlreq.sqltype=context.sqltype
 Set sqlreq.tablename=context.tablename
 
 Set context.sql=sqlreq
 ]]>
</code>
<call name='查询手麻视图服务3' target='his测试BO' async='0' xpos='200' ypos='750' >
<annotation><![CDATA[查询手麻视图服务1]]></annotation>
<request type='Ens.Request' >
<assign property="callrequest" value="context.sql" action="set" />
</request>
<response type='Ens.Response' >
<assign property="response" value="callresponse" action="set" />
</response>
</call>
</sequence>
</case>
<case condition='request.OriginalDocId="JH0000"' >
<sequence name='JH000002' xpos='1145' ypos='400' xend='200' yend='850' disabled="true">
<assign name="设定查询参数" property="context.sqlparameter" value="request.GetValueAt(&quot;/REQUEST/KSBM&quot;)" action="set" xpos='200' ypos='250' >
<annotation><![CDATA[设定查询参数]]></annotation>
</assign>
<assign name="设定SQL脚本" property="context.sqlstatement" value="&quot;SELECT * FROM His_dict_dept_H080&quot;" action="set" xpos='200' ypos='350' >
<annotation><![CDATA[设定SQL脚本]]></annotation>
</assign>
<assign name="设定数据库表名" property="context.tablename" value="&quot;dhis.His_dict_dept_H080&quot;" action="set" xpos='200' ypos='450' >
<annotation><![CDATA[设定数据库表名]]></annotation>
</assign>
<assign name="设定SQL类型" property="context.sqltype" value="&quot;Query&quot;" action="set" xpos='200' ypos='550' >
<annotation><![CDATA[有QUERY、INSERT、UPDATE、DELETE4种]]></annotation>
</assign>
<code name='拼接sql脚本生成xml消息' xpos='200' ypos='650' >
<annotation><![CDATA[拼接sql脚本生成xml消息]]></annotation>
<![CDATA[ 
 Set sqlreq=##class(JHIPLIB.SQL.MSG.SQLMessage).%New()
 Set context.sqlstatement=$replace(context.sqlstatement,"?",context.sqlparameter)
 
 Set sqlreq.sqlreq=context.sqlstatement
 Set sqlreq.sqltype=context.sqltype
 Set sqlreq.tablename=context.tablename
 
 Set context.sql=sqlreq
 ]]>
</code>
<call name='查询手麻视图服务1' target='测试服务BO' async='1' xpos='200' ypos='750' >
<annotation><![CDATA[查询手麻视图服务1]]></annotation>
<request type='Ens.Request' >
<assign property="callrequest" value="context.sql" action="set" />
</request>
<response type='Ens.Response' >
<assign property="response" value="callresponse" action="set" />
</response>
</call>
</sequence>
</case>
<case condition='request.action="JH1401"' >
<call name='HTTP测试' target='测试HTTP样例BO' async='1' xpos='1415' ypos='400' >
<request type='Ens.Request' >
<assign property="callrequest" value="request" action="set" />
</request>
<response type='Ens.Response' >
<assign property="response" value="callresponse" action="set" />
</response>
</call>
</case>
<default name='default' />
</switch>
</sequence>
</process>
}

Storage Default
{
<Type>%Storage.Persistent</Type>
}

}
  • 测试结果

image

  • 参考 image

%XML.TextReader逐个节点地读取和解析文档。 %XML.XPATH.document使用引用文档中特定节点的XPATH表达式来获取数据。 [XMLTools参考链接](%3Cdiv align="justify" style="min-height: 13pt; "%3E%3Ca href="https://docs.intersystems.com/irisforhealth20222/csp/docbook/DocBook.UI.Page.cls?KEY=GXML_intro"%3E%3Cfont face="Calibri" color="#0000ff" size="2"%3E%3Cspan dir="ltr" style=" font-size:10.5pt"%3E%3Cu%3Ehttps://docs.intersystems.com/irisforhealth20222/csp/docbook/DocBook.UI.Page.cls?KEY=GXML_intro%3C/u%3E%3C/span%3E%3C/font%3E%3C/a%3E%3C/div%3E)

三、获取工程下所有BP类型类文件

/// 获取工程下所有BP类型类文件
/// SELECT * FROM %Studio.Project
/// package:包名称
/// w ##class(BOE.TEST.ClassAnalysis).GetClassNameByPackage()			
ClassMethod GetClassNameByPackage(package As %String = "") As %String
{
				
	s $zt="Exception"
	k ^sy //初始化,Global用于存储分支节点相关信息			
	s project=""
	for{
		s project=$o(^oddPROJECT(project))
		q:project=""
		s rs=##class(%Library.ResultSet).%New("%Studio.Project:ProjectItemsList")
		//s columns=rs.GetColumnCount()
		s sc=rs.Execute(project)
		while(rs.Next()){
			s i=0			
			s name=rs.GetData(2) //Item名称			
			s type=rs.GetData(3) //Item类型			
			s tempPackage=rs.GetData(5) //包名			
			if type="CLS"{ //类文件
				s theCompiledClassObj = ##Class(%Dictionary.CompiledClass).%OpenId(name)
				continue:theCompiledClassObj=""
				s primarySuper=theCompiledClassObj.PrimarySuper
				continue:primarySuper'["Ens.BusinessProcess" //只处理BP
				if (package'=""){
					for j=1:1:$L(name,"."){
						if $P(name,".",j)=package s i=1 //过滤包名
					}
					continue:i=0
				}
				s xDataId=""
				&sql(SELECT top 1 ID into :xDataId FROM %Dictionary.CompiledXData WHERE parent=:name)
				if (xDataId'=""){
					//解析XData
					d ##class(BOE.TEST.ClassAnalysis).XDataAnalysisTextReader(xDataId)
				}
			}
		}
	}
	q 1
Exception
	q "-1^"_$ze
}

查询已生成的分支节点数据

/// 查询接口目录
/// d ##class(%Results).RunQuery("BOE.TEST.ClassAnalysis","GetMsgInfo")
Query GetMsgInfo() As %Query(ROWSPEC = "msgCode,msgDesc,callName,callTarget") [ SqlProc ]
{
}

ClassMethod GetMsgInfoExecute(ByRef qHandle As %Binary) As %Status
{
 	s repid=$I(^CacheTemp)
 	s ind=1
 	s qHandle=$lb(0,repid,0)
	s msgCode=""
	for{
		s msgCode=$o(^sy(msgCode)) //接口编码
		q:msgCode=""
		s msgDesc=^sy(msgCode) //接口描述
		s callName="",callTarget=""
		for{
			s callName=$o(^sy(msgCode,"callName",callName))
			q:callName=""
			d OutRow
		}
		
	}
	
	Quit $$$OK

OutRow
	s Data=$lb(msgCode,msgDesc,callName,callTarget)
 	s ^CacheTemp(repid,ind)=Data
 	s ind=ind+1
	quit
}

ClassMethod GetMsgInfoFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = GetBISQCIndexDataExecute ]
{
	s AtEnd=$LIST(qHandle,1)
 	s repid=$LIST(qHandle,2)
 	s ind=$LIST(qHandle,3)
 	s ind=$o(^CacheTemp(repid,ind))
 	If ind="" {				// if there are no more rows, finish fetching
 		s AtEnd=1
 		s Row=""
 	}
 	Else      {			
 		s Row=^CacheTemp(repid,ind)
 	}
 	s qHandle=$lb(AtEnd,repid,ind)
	Quit $$$OK
}

ClassMethod GetMsgInfoClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = GetBISQCIndexDataExecute ]
{
	s repid=$LIST(qHandle,2)
 	Kill ^CacheTemp(repid)
    Quit $$$OK
}

工程分支节点生成及查询测试

  • 测试结果 image

  • 通过存储过程查询

call BOE_TEST.ClassAnalysis_GetMsgInfo()

image

0
0 152
文章 sun yao · 十月 12, 2022 10m read

概述

现有Ensemble平台BS(服务)、BP(流程)、BO(操作)需对平台及开发语言有一定的了解才能实现,为简化用户操作,现对现有平台进行二次封装,通过API接口的形式进行前后端分离,通过前端界面操作实现BS(对外提供的服务)、BP、BO(逻辑处理或调用外部的服务)自动生成(通过%Dictionary实现),具体实现如下。

一、开发技术和工具

版本:Ensemble 2017.2.1

二、涉及公用类

2.1 %Dictionary.ClassDefinition(自定义类)

• property Super as %CacheString; Specifies one or more superclasses for the class. 定义一个或多个父类,继承父类

• property** ProcedureBlock** as %Boolean [ InitialExpression = 0 ]; Specifies that the class uses procedure block for method code. 设置类是否允许使用程序块,程序块强制实施变量作用域:方法无法看到由其调用方定义的变量,程序块中的任何变量都会自动成为私有变量

• relationship Parameters as %Dictionary.ParameterDefinition [ Inverse = parent,Cardinality = children ]; Parameter. 定义类参数,如全局变量、适配器等相关定义

• relationship Methods as %Dictionary.MethodDefinition [ Inverse = parent,Cardinality = children ]; Method. 定义类方法

参考链接:http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Dictionary.ClassDefinition

2.2 %Dictionary.ParameterDefinition(自定义类参数)

• property Name as %Dictionary.CacheIdentifier [ Required ]; The name of the parameter. 定义参数名

• property Default as %CacheString [ SqlFieldName = _Default ]; Specifies a default value for the parameter assuming the Expression keyword is blank. 定义参数默认值,不设置则为空

• property Description as %CacheString; Specifies a description of the parameter. 定义参数描述

参考链接:http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Dictionary.ParameterDefinition

###2.3 %Dictionary.MethodDefinition(自定义类方法) • property Name as %Dictionary.CacheIdentifier [ Required ]; The name of the method. 定义方法名

• property ClassMethod as %Boolean [ InitialExpression = 0 ]; Specifies that the method is a class method. Instance methods can only be invoked via an instantiated object while class methods can be directly invoked without an object instance. 指定该方法是类方法。实例方法只能通过实例化的对象调用,而类方法可以在没有对象实例的情况下直接调用。

• property FormalSpec as %CacheString; Specifies the list of arguments. Each argument is of the format [&|*][:][=] where & means pass-by-reference and * means output-only. 定义方法入参,每个入参格式为“参数名:参数类型=默认值”,如:code:%String=””

• property ReturnType as %Dictionary.CacheClassname; Specifies the data type of the value returned by a call to the method. Setting ReturnType to an empty string specifies that there is no return value. 定义方法返回值,设置为空则无返回值

• property WebMethod as %Boolean [ InitialExpression = 0 ]; Specifies that a method can be invoked as a web method using the SOAP protocol. 设置方法是否为web方法,适用于SOAP协议

• property Implementation as %Stream.TmpCharacter; The code that is executed when the method is invoked. In the case of an expression method, this is an expression. In the case of a call method, this is the name of a Cache routine to call. 调用方法时执行的代码。对于表达式方法,这是一个表达式。对于调用方法,这是要调用的缓存例程的名称

参考链接:http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Dictionary.MethodDefinition

###2.4 Ens.Config.Production • property Items as list of Ens.Config.Item(XMLNAME="Item",XMLPROJECTION="ELEMENT"); 定义Production下的BS、BP、BO,根据父类确认属于哪一类

• method SaveToClass(pItem As Ens.Config.Item = $$$NULLOREF) as %Status This method saves the production into the XData of the corresponding class

参考链接: http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=ENSLIB&CLASSNAME=Ens.Config.Production

###2.5 Ens.Config.Item(BS服务、BP流程、BO操作) • property PoolSize as %Integer(MINVAL=0,XMLPROJECTION="ATTRIBUTE"); Number of jobs to start for this config item. Default value: 0 for Business Processes (i.e. use shared Actor Pool) 1 for FIFO message router Business Processes (i.e. use a dedicated job) 1 for Business Operations 0 for adapterless Business Services 1 for others For TCP based Services with JobPerConnection=1, this value is used to limit the number of connection jobs if its value is greater than 1. A value of 0 or 1 places no limit on the number of connection jobs. 设置缓冲池大小

• property Name as %String(MAXLEN=128,XMLPROJECTION="ATTRIBUTE") [ Required ]; The name of this config item. Default is the class name. 设置BS、BP、BO名称

• property ClassName as %String(MAXLEN=128,XMLPROJECTION="ATTRIBUTE") [ Required ]; Class name of this config item. 设置BS、BP、BO类名称

• property Category as %String(MAXLEN=2500,XMLPROJECTION="ATTRIBUTE"); Optional list of categories this item belongs to, comma-separated. This is only used for display purposes and does not affect the behavior of this item. 设置类别 • property Comment as %String(MAXLEN=512,XMLPROJECTION="ATTRIBUTE"); Optional comment text for this component. 设置注释

• property Enabled as %Boolean(XMLPROJECTION="ATTRIBUTE") [ InitialExpression = 1 ]; Whether this config item is enabled or not. 设置启用停用标志

参考链接:http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=ENSLIB&CLASSNAME=Ens.Config.Item ##三、实现方法 ###3.1 创建BS模板类 创建模板类,后续类生成方法体通过模板类获取

/// BS的SOAP模板
Class HIP.Platform.Template.BSSOAPTemplate Extends EnsLib.SOAP.Service
{

Parameter ADAPTER;

Parameter NAMESPACE = "http://tempuri.org";

Parameter SERVICENAME = "BSSOAPTemplate";

Method TemplateFun(code As %String, data As %GlobalCharacterStream) As %GlobalCharacterStream [ WebMethod ]
{
	set OutStream=##class(%GlobalCharacterStream).%New()
	try{
		s ..%ConfigName = $classname($this)
		set sourceCode=$p($classname($this),".",4) //PUB000X
		set methodCode=##safeexpression(""""_$get(%methodname)_"""") //SendDataFromHis
		
		s messageCode = $p(code,"^",1)
		s requestType= $select($p(code,"^",2)="REST":"REST", 1:"SOAP")
		set proc = ##class(%SYS.ProcessQuery).%OpenId($j) //当前进程 获取调用服务客户端的IP地址
		
		set sc = ##class(HIP.Service.PublishService).GetAllowedIP(sourceCode)
		if +sc=1 {
			s allowedIP = $p(sc,"^",2)
			if allowedIP '[ proc.ClientIPAddress {
				SET oref=##class(%Exception.General).%New("<401>","无权限",,"您的IP地址不允许访问,请联系管理员") 
         		THROW oref
			}
		}else{
			return sc
		}
		s request = ##class(HIP.Platform.Message.Request).%New()
		s request.sourceCode=sourceCode		//PUB0001
		s request.requestType=requestType	//REST SOAP
		s request.inputFlag="0"				//-1表示失败,0表示未处理,1表示成功
		s request.inputStream = data		//JSON流,或者XML流
		s request.messageCode=messageCode	//BOE0001
		
		Set tSC=..SendRequestSync("HIP.Platform.BP.ProcessCode",request,.pOutput)
		If $$$ISERR(tSC) Do ..ReturnMethodStatusFault(tSC)
		
		d OutStream.CopyFrom(pOutput.outStream)
		return OutStream
	}catch err {
		set OutStream=##class(%GlobalCharacterStream).%New()
		do OutStream.Write(err.DisplayString())
		return OutStream
	}
}

Storage Default
{
<Data name="BSSOAPTemplateDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
</Data>
<DataLocation>^HIP.PlatforE240.BSSOAPTemplateD</DataLocation>
<DefaultData>BSSOAPTemplateDefaultData</DefaultData>
<IdLocation>^HIP.PlatforE240.BSSOAPTemplateD</IdLocation>
<IndexLocation>^HIP.PlatforE240.BSSOAPTemplateI</IndexLocation>
<StreamLocation>^HIP.PlatforE240.BSSOAPTemplateS</StreamLocation>
<Type>%Library.CacheStorage</Type>
}

}

###3.2 自动生成BS,并添加至Production中 通过模板类自动生成WebService方法,并添加到Production的BS中

/// 创建BS服务 PUB00XX服务,提供给第三方调用
/// d ##class(HIP.Util.SOAP).BSCreateSOAPInfo("PUB0001","提供给HIS访问平台")
ClassMethod BSCreateSOAPInfo(Code As %String, Desc As %String) As %Status
{
	///HIP.Platform.BS.PUB0001
	s src = "HIP.Platform.BS."_Code_".PublishWebService"
	s isExist = 0
	try {
		
		set isExist=##class(%Dictionary.ClassDefinition).%ExistsId(src)
		if isExist=1 { //类已存在则更新,先删除再插入
			set classObj = ##class(%Dictionary.ClassDefinition).%OpenId(src)
			d classObj.Parameters.Clear()
			d classObj.Properties.Clear()
			d classObj.Indices.Clear()
			d classObj.ForeignKeys.Clear()
			d classObj.Methods.Clear()
		}else { //类不存在则新建
			set classObj = ##class(%Dictionary.ClassDefinition).%New(src)
		}
		//设置父类
		s classObj.Super="EnsLib.SOAP.Service"
		//设置允许使用程序块,则可动态定义变量
		s classObj.ProcedureBlock=1

		///Parameter的值
		//设置适配器
		set ParDef = ##class(%Dictionary.ParameterDefinition).%New()
		set ParDef.Name="ADAPTER"
		d classObj.Parameters.Insert(ParDef)
		set ParDef = ##class(%Dictionary.ParameterDefinition).%New()
		//设置服务名
		set ParDef.Name="SERVICENAME"
		set ParDef.Default=Code
		set ParDef.Description=Desc
		d classObj.Parameters.Insert(ParDef)
		//设置命名空间
		set ParDef = ##class(%Dictionary.ParameterDefinition).%New()
		set ParDef.Name="NAMESPACE"
		set ParDef.Default="www.boe.com"
		d classObj.Parameters.Insert(ParDef)
		
		///函数模板代码,通过模板类获取
		s methodTemplate = ##class(%Dictionary.MethodDefinition).%OpenId("HIP.Platform.Template.BSSOAPTemplate||TemplateFun")
		Set methodObj=##class(%Dictionary.MethodDefinition).%OpenId(src_"||SendData")
		if methodObj="" Set methodObj=##class(%Dictionary.MethodDefinition).%New(src_".SendData")
		//设置方法名
		set methodObj.Name="SendData"
		set methodObj.ClassMethod=0
		//set methodObj.FormalSpec="code:%String,data:%GlobalCharacterStream,*pOutput:HIP.Platform.Message.Response"
		//设置方法入参
		set methodObj.FormalSpec="code:%String,data:%GlobalCharacterStream"
		//设置方法返回值
		set methodObj.ReturnType="%GlobalCharacterStream"
		//设置方法为WebService方法
		set methodObj.WebMethod=1
		//设置方法具体实现代码,通过模板类获取
		set methodObj.Implementation=methodTemplate.Implementation
		d classObj.Methods.Insert(methodObj)
		
		set sc=classObj.%Save()
		if $$$ISERR(sc) {
			return $system.Status.GetErrorText(sc)
		}else{
			d $system.OBJ.Compile(src,"ck/displaylog=0")
		}
		if isExist=0 {
			//存储到production中
			s prodObj = ##class(Ens.Config.Production).%OpenId("HIP.Platform.Production")
			if $IsObject($G(prodObj)){
				Set item = ##class(Ens.Config.Item).%New()
				Set item.PoolSize = 1 
				Set item.Name = src
				Set item.ClassName = src
				Set:item.Name="" item.Name = item.ClassName
				Set item.Category = ""
				Set item.Comment = Desc
				Set item.Enabled = 1
				Set tSC = prodObj.Items.Insert(item)
				
				If $$$ISOK(tSC) {
					// save production (and item)
					Set tSC = prodObj.%Save()
					set ^TempSy("tSC")=tSC
					If ($$$ISOK(tSC)) {
						// update production class
						Set tSC = prodObj.SaveToClass()
					}
					return tSC
				}
				If $$$ISERR(tSC) return $system.Status.GetErrorText(tSC)
			}
		}
		return $$$OK
	} catch(ex) {
		return ex.DisplayString()
	}
}

###四、 结果展示 运行 d ##class(HIP.Util.SOAP).BSCreateSOAPInfo("PUB0001","提供给HIS访问平台") 后,Studio中自动生成HIP.Platform.BS.PUB0001.PublishWebService.cls 类 如下: image 打开Portal管理界面,Production配置,可看到该服务已添加至Production中,如下: image 可直接通过soapUI调用,地址 http://localhost:57772/csp/hip/HIP.Platform.BS.PUB0001.PublishWebService.CLS?WSDL=1image InterSystems消息查看 imageimageimage ###五、 结论与猜想 同理,BO也可通过该方法实现自动生成,另可通过建立REST服务或WebService服务的方式通过前端调用该方法实现前端自动生成BS、BP、BO,以简化用户操作,但该方法存在问题点,如BP都为公用单个BP,消息并发量大时可能导致BP堵塞问题,可能实现的解决方法为前端先单独调用接口创建BP,后生成BS,再通过配置实现BS到BP的关联,大家感兴趣可自行尝试,以上,谢谢!

10
6 789
文章 Qiao Peng · 九月 22, 2022 25m read

        关注FHIR的大侠们估计都注意到了,FHIR更新了它支持的互操作范式,除了消息、文档、服务、API这4种,增加了2个:资源仓库、订阅。前面4个好理解,为什么资源仓库和订阅会成为FHIR的新的互操作范式?互操作与应用集成是什么关系?

        这里借FHIR的新互操作范式,聊聊应用集成,看看集成平台是什么?有什么样的集成方案?以及怎么评价不同的方案。

0
4 667
文章 Michael Lei · 五月 30, 2022 1m read

你好,我很高兴地宣布向OpenExchange和目前的比赛提交的一个作品,即FHIR匿名化代理。FHIR匿名化代理为任何现有的FHIR服务器增加了一个透明的匿名化层,使客户能够在FHIR服务器上进行查询--其中可能包含个人识别信息--并收到一个即时的匿名化数据版本。

代理机制是通过互操作性Production、BPLs和DTLs以及FHIR互操作性适配器在IRIS for Health平台上实现的。匿名化包括所有身份ID和个人数据,并可通过DTLs进行配置。

OpenExchange的演示应用程序带有一个内置的FHIR endpoint,并以会填充一些示例数据。试一下,或者将你自己的FHIR服务器配置为代理目标亲自测试一下 smiley

0
0 107
文章 Qiao Peng · 四月 25, 2022 12m read

InterSystems流程自动化与工作流引擎

InterSystems工作流程引擎的主要功能    2

使用InterSystems工作流程引擎    3

场景描述    3

环境配置与测试    5

任务管理    15

任务API和自定义任务用户界面    16

展望    17

15

集成平台除了集成业务系统,打通数据与业务流程外,另一个核心的功能就是流程自动化(BPA)。

流程自动化涉及几个重要的特性:

  1. 流程建模
  2. 流程协同
  3. 决策自动化
  4. 低代码工作流程自动化
  5. 任务协同与任务管理

其中第4和5点都是和工作流程相关的。

什么是工作流程(Workflow)?它和业务流程(Business Process)有何区别?为何集成平台要涉及对工作流程的管理?

0
2 422
文章 Qiao Peng · 十二月 18, 2021 12m read

应用集成技术是市场上被广泛使用的,也是充斥着术语和概念的一个技术领域。集成平台、消息引擎、消息中间件、集成引擎、集成中间件、企业服务总线(ESB)、API网关、API管理… 很多概念与名词。到底它们是什么意思?有什么区别?哪种技术适合解决哪种集成问题?

业务集成的需求和技术的演进是紧随业务系统的软件架构发展而发展的。通过小结软件架构的发展,我们更容易梳理业务集成技术的演进、更容易看清楚各种集成架构的优势和未来发展方向。

0
0 853
文章 Michael Lei · 十一月 25, 2021 8m read

关于 "智慧医院 "的真正内涵,有很多误解在流传。术语 "智慧Smart "已经成为 "自动化 " "数字设备 "的同义词。然而,事实是,增加技术、设备和传感器并不一定能使建筑或者医院变得'智慧'。而且,在某些情况下,数字创新被强加于医院,而没有真正考虑到其效果。

这种情况导致了一系列的复杂性和矛盾。例如,一方面,人们对医院采用数字技术的期望越来越高,但另一方面,人们越来越担心数字医疗解决方案正在创造更多离散的、孤岛的生态系统。同样,尽管医院面临着实现实时医疗系统的更大压力,但往往受制于其运营模式的孤岛性质或围绕各种医疗信息系统的互操作性问题。

这些相互冲突的压力表明,需要一种更协同、更集成、更综合、更全面的数字化转型方法--一种将系统整合在一起并从各个角度考虑影响的方法。

智慧医院数字孪生的出现,证明了这一技术为解决这些日益严峻的挑战提供了可行的手段。

在过去的几年里,数字孪生已经有了很大的发展,成为一项值得期待的技术。然而,尽管数字孪生被炒得沸沸扬扬,但对于数字孪生是什么(不是什么)以及它是否能实现其承诺,仍然存在相当大的困惑。像许多新技术一样,数字孪生正在 "幻觉破灭 "中挣扎并且在某些情况下被错误地描述。

在本文中,我们将通过回答这六个关键问题来正面解决这种困惑。

1
0 770