Escrito por

Sales Engineer at InterSystems
Artigo Danusa Calixto · Dez. 22, 2023 9m read

Banco de Dados de Documentos do IRIS (DocDB)

O Banco de Dados de Documentos do InterSystems IRIS (DocDB) oferece uma abordagem flexível e dinâmica de gestão dos dados de bancos de dados. O DocDB abraça o poder do JSON (JavaScript Object Notation), fornecendo um ambiente sem esquema para armazenar e recuperar dados.

É uma ferramenta poderosa que permite aos desenvolvedores ignorar um monte de código boilerplate na interação com aplicativos existentes, serialização, paginação e integração. O fluxo perfeito do DocDB com os serviços e as operações de Interoperability Rest possibilita um grande salto na produção e no gerenciamento de APIs.

Confira a documentação completa do DocDB aqui. No contexto deste artigo, mostrarei um caso de uso em que o DocDB é uma combinação perfeita.

Organizações que usam produtos da InterSystems como IRIS, Interoperability, TrakCare... geram constantemente requisitos novos e ágeis para o compartilhamento de dados com outros sistemas para consumo. A InterSystems oferece o adaptador JSON integrado para permitir que as classes persistentes sejam serializáveis em JSON no nível do registro. No entanto, reescrever seu aplicativo para estender o adaptador JSON não é uma tarefa fácil, e seus dados de interesse serão gerenciados sempre no nível do registro diretamente das classes do seu aplicativo. É aí que entra o DocDB, porque você pode criar consultas no código do seu aplicativo e despejá-las no DocDB como JSON, prontas para serem enviadas a apps para consumidores.

 

Aqui está um snippet executando qualquer procedimento armazenado do IRIS padrão de maneira genérica  e salvando a saída como registros no formato json.

 

        //presumindo que os parâmetros existem como P1,P2,...Pn no contexto
set queryData=$lb("queryclassname:queryname",<no of parameters>)
set queryString=$lg(queryData,1)
set paramCount=$lg(queryData,2)
set args=$lb()
for ix=1:1:paramCount{
set var="P"ix
XECUTE ("(in,out) SET out=$g(in)", @var, .y)
set $li(args,ix)=$s(y'="":""""y"""",1:"""""""")
}
set resultSet=##class(%ResultSet).%New(queryString)
XECUTE ("(in,out) set out=in.%Execute("$lts(args)")",resultSet,.scx)
set sc=scx
If $$$ISERR(sc) $$$ThrowStatus(sc)
set columnNo=resultSet.GetColumnCount()

	While resultSet.%Next(.sc) {
		If $$$ISERR(sc) $$$ThrowStatus(sc)
		
		set row={}
		for iz=1:1:columnNo{
			set columnName=resultSet.GetColumnName(iz)
			do row.%Set(columnName,resultSet.GetDataByName(columnName))
		}
		set sc=..AddRecordToReportResult(ID,row)
		If $$$ISERR(sc) $$$ThrowStatus(sc)
	}

ClassMethod AddRecordToReportResult(doc As %DynamicObject)As %Status {
set sc=$$$OK
try {
if (##class(%DocDB.Database).xNExists("PreparedReportDB")){
set db=##class(%DocDB.Database).%GetDatabase("PreparedReportDB")
} else {
set db= ##class(%DocDB.Database).%CreateDatabase("PreparedReportDB")
}
set nwRecord=db.%SaveDocument(doc)
do nwRecord.%Save(0)

	k nwRecord
}catch err{
	set sc=$$$ADDSC(sc,$$$ERROR("5001",err.DisplayString()))
}
return sc

}

É isso. Seus dados, sejam quais forem, estão prontos para serem expostos a apps para consumidores.

Considerando o caso de uso de monitoramento de integrações, suas consultas verificarão o status da sua produção de interoperabilidade da InterSystems, os erros, alertas...

E os dados serão salvos em um DocDB em intervalos.

Class ProdEye.DataTask Extends %SYS.Task.Definition
{

Property IntervalMinutes As %Integer [ InitialExpression = 5 ]; Method OnTask() As %Status { try { set packageName=$P($classname(),".",1)

	set targetDocument={}
	
	set ZeroTimeStamp= $$HorologAddSecs^EnsUtil($ztimestamp,-(..IntervalMinutes*60))
	
	set targetDocument.TimeOfQuery=##class(Ens.DataType.UTC).timeUTC()
	set targetDocument.TimeOfQueryIdx=$classmethod(packageName_".Query","getDateTimeUTCIdx")
	do $classmethod(packageName_".Query","GetNameSpaceList")
	k prodArray
	
	do $classmethod(packageName_".Query","GetProductionsList",.prodArray)
	
	
	set productionObjectList=[]
	
	set currNameSpace=""
	for  {
		set currNameSpace=$O(prodArray(currNameSpace))
		quit:currNameSpace=""
		set currProd=""
		for  {
			set currProd=$O(prodArray(currNameSpace,currProd))
			quit:currProd=""
			set newProdData=$g(prodArray(currNameSpace,currProd))
			set newProd=$classmethod(packageName_".Data.Production","%New",currProd,$lg(newProdData,1),$lg(newProdData,2))
			
			k compArray
			do $classmethod(packageName_".Query","GetComponentListByProd",.compArray,currProd,currNameSpace)
			
			set components=""
			set currComp=""
			for  {
				set currComp=$O(compArray(currProd,currComp))
				quit:currComp=""
				set newComp=$classmethod(packageName_".Data.ProductionConfigItem","%New",currComp,$g(compArray(currProd,currComp)))
				set compId=$classmethod(packageName_".Query","GetComponentId",currComp,currProd,currNameSpace)
				set newComp.Type= $classmethod(packageName_".Query","GetComponentType",compId,currNameSpace)
				set newComp.QueueSize=$classmethod(packageName_".Query","GetComponentQueueData",currComp,currNameSpace)
				
				set MessageStats=$classmethod(packageName_".Query","GetComponentMessageCountAndAvg",currComp,ZeroTimeStamp,currNameSpace)
				set:$lv(MessageStats) newComp.MessageCount=$lg(MessageStats,1)
				set:$lv(MessageStats) newComp.MessageAVGProcessingMilliseconds=$lg(MessageStats,2)
				
				set newComp.JobsStatus=$classmethod(packageName_".Data.JobStatus","BuildPropertyFromList",$classmethod(packageName_".Query","GetComponentJobs",currComp,currNameSpace))
				set newComp.Errors=$classmethod(packageName_".Data.Log","BuildPropertyFromList",$classmethod(packageName_".Query","GetComponentErrors",currComp,ZeroTimeStamp,currNameSpace))
				set newComp.Warnings=$classmethod(packageName_".Data.Log","BuildPropertyFromList",$classmethod(packageName_".Query","GetComponentWarnings",currComp,ZeroTimeStamp,currNameSpace))
				set newComp.Alerts=$classmethod(packageName_".Data.Log","BuildPropertyFromList",$classmethod(packageName_".Query","GetComponentAlerts",currComp,ZeroTimeStamp,currNameSpace))
				
				do newProd.Components.Insert(newComp)
			}
			
			
			set newProdJSONStr=""
			do newProd.%JSONExportToString(.newProdJSONStr)
			set newProdJSON={}.%FromJSON(newProdJSONStr)
			do:newProdJSON'="" productionObjectList.%Push(newProdJSON)
			}
		}
	
	do targetDocument.%Set("ProductionList",productionObjectList)
	if (##class(%DocDB.Database).xNExists(packageName_".DB.ProdEyeDocument")){
		set db=##class(%DocDB.Database).%GetDatabase(packageName_".DB.ProdEyeDocument")
	} else {
		set db= ##class(%DocDB.Database).%CreateDatabase("ProdEye.DB.ProdEyeDocument")
		do db.%CreateProperty("TimeOfQueryIdx","%Integer","$.TimeOFQueryIdx",0)
		}
	set nwRecord=db.%SaveDocument(targetDocument)
	
	if $$$ISERR(nwRecord){$$$ThrowStatus(nwRecord)}
	
	set nwRecord.ProfileName=$get(^ProdEyeProfileName(0))
	set nwRecord.TimeOfQueryIdx=targetDocument.TimeOfQueryIdx
	do nwRecord.%Save()
	
	return $$$OK
} catch error {
	do BACK^%ETN
	return $$$ERROR("5001",error.AsStatus())
	}

}

}

agora, qualquer aplicativo externo para consumidores pode buscar os dados para verificar o status da produção, alertas, erros e avisos usando um serviço REST simples da InterSystems conforme a seguir

Include Ensemble

Class ProdEye.Prod.ProdEyeRest Extends EnsLib.REST.Service {

Property PageSize As %Integer [ InitialExpression = 10 ]; Property TokenValidationURL As %String; Parameter SETTINGS = "PageSize,TokenValidationURL"; XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ] { <Routes> <Route Url="/getdata" Method="POST" Call="GetData"/> </Routes> }

Method GetData(pInput As %Library.AbstractStream, Output poutput As %Stream) As %Status { quit:'$IsObject(pInput) $$$OK quit:(pInput.SizeGet()=0) $$$OK set paramsObj={}.%FromJSON(pInput.Read()) set cutoff=paramsObj.cutoff set authorisation=paramsObj.Authorisation set profilename=paramsObj.profilename

$$$TRACE(..Adapter.IOAddr_" Profile "_profilename_" cutoff parameter: "_paramsObj.%ToJSON())

if ('..Authorise(authorisation,..TokenValidationURL)){
	do poutput.Write("{""Error"":""Invalid Credentials""}")
	do ..ReportHttpStatusCode(403)
	 return $$$OK
}

if ('..ValidateCutoff(cutoff)){
	do poutput.Write("{""Error"":""Invalid Request Parameters""}")
	do ..ReportHttpStatusCode(400)
	 return $$$OK
}


try{
	set packageName=$P($classname(),".",1)
	SET db = ##class(%DocDB.Database).%GetDatabase(packageName_".DB.ProdEyeDocument")
	set restriction=[]
	do restriction.%Push("TimeOfQueryIdx")
	do restriction.%Push(cutoff)
	do restriction.%Push("&gt;")
	
	do restriction.%Push("ProfileName")
	do restriction.%Push(profilename)
	do restriction.%Push("=")
	
	SET result = db.%FindDocuments(restriction)
	
	if (($IsObject(result))&amp;&amp;($Isobject(result.content))){
		set resultPage={}
		set resultPage.Next=0
		set resultList=[]
		
		set iter=result.content.%GetIterator()
		for dx=1:1:..PageSize{
			set resultItem=iter.%GetNext(.key,.resultItemvalue)
			if $IsObject(resultItemvalue){
				set resultItemvalue={}.%FromJSON(resultItemvalue.%Doc)
				do resultList.%Push(resultItemvalue)
				set:dx&gt;1 resultPage.Next=resultItemvalue.TimeOfQueryIdx
			}
			}
	set resultPage.content=resultList
	
	
	do poutput.Write(resultPage.%ToJSON())
	}else {
		Throw $$$ERROR("5001","Error During Processing Data")
		}
} catch error {
	do poutput.Write("{""Error"":""Error During Processing Data : """_error.AsSystemError()_""" ""}")
	do ..ReportHttpStatusCode(500)
	}

return $$$OK

}

ClassMethod ValidateCutoff(val As %String) As %Boolean { set sc=1 quit:+val<=0 0 quit:$l(+val)<5 0 set valdt=$E(val,1,5)","$E(val,6,10)

try {
	set date=$zdt(valdt)
	
}catch err{
		
		set sc=0
	}

return sc

}

ClassMethod Authorise(val As %String, TokenValidationURL As %String) As %Boolean { quit:$g(val)="" 0 if (TokenValidationURL=""){ set type=$P(val," ",1) set type=$zstrip($zstrip(type,"*c"),"<=>w") quit:$ZCVT(type,"L")'="basic" 0 set userpass=$P(val," ",2) set userpassDecoded=$system.Encryption.Base64Decode(userpass)

	set user=$P(userpassDecoded,":",1)
	set user=$zstrip($zstrip(user,"*c"),"&lt;=&gt;w")
	set pass=$P(userpassDecoded,":",2)
	set pass=$zstrip($zstrip(pass,"*c"),"&lt;=&gt;w")
	
	set prodeyeUser=##class(Ens.Config.Credentials).GetValue("ProdEyeUser","Username")
	set prodeyePass=##class(Ens.Config.Credentials).GetValue("ProdEyeUser","Password")
	
	return ((prodeyeUser=user)&amp;&amp;(prodeyePass=pass))
}elseif(TokenValidationURL'=""){
	set prodeyeAuthUser=##class(Ens.Config.Credentials).GetValue("ProdEyeAuthUser","Username")
	set prodeyeAuthPass=##class(Ens.Config.Credentials).GetValue("ProdEyeAuthUser","Password")
	
	set tokenVerificationRequest=##class(%Net.HttpRequest).%New()
	do tokenVerificationRequest.SetHeader("content-type","application/json")
	do tokenVerificationRequest.SetHeader("Authorisation","BASIC "_$system.Encryption.Base64Encode(prodeyeAuthUser_":"_prodeyeAuthPass))
	set token=$P(val," ",2)
	set data={}
	set data.Token=token
	do tokenVerificationRequest.EntityBody.Write(data.%ToJSON())
	do tokenVerificationRequest.Post(TokenValidationURL)
	
	set response=tokenVerificationRequest.HttpResponse
	if ($IsObject(response) &amp;&amp; $IsObject(response.Data) &amp;&amp; response.StatusCode=200 ){
		set message="" while 'response.Data.AtEnd {set message=message_response.Data.Read(,.tSC) if 'tSC quit}
		set responseMessage={}.%FromJSON(message)
		if (responseMessage.%IsDefined("IsValid") &amp;&amp; (responseMessage.IsValid="1")){
			return 1
		}else{
			return 0
				}
	}else{
		return 0
			}
	}

}

}

Um exemplo real de um aplicativo de consumo do caso de uso acima está disponível aqui no Open Exchange. É um aplicativo móvel desenvolvido em Dart para o framework Flutter.

Além disso, o DocDB expõe a API direta sobre todas as tabelas de documentos, descritas em detalhes aqui

A vantagem da funcionalidade do DocDB é tornar o desenvolvimento de APIs para compartilhamento de informações entre sistemas orientado pelo foco do caso de uso, rastreando e padronizando toda a serialização, staging e integração de dados ao mesmo tempo.