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(">")
do restriction.%Push("ProfileName")
do restriction.%Push(profilename)
do restriction.%Push("=")
SET result = db.%FindDocuments(restriction)
if (($IsObject(result))&&($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>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"),"<=>w")
set pass=$P(userpassDecoded,":",2)
set pass=$zstrip($zstrip(pass,"*c"),"<=>w")
set prodeyeUser=##class(Ens.Config.Credentials).GetValue("ProdEyeUser","Username")
set prodeyePass=##class(Ens.Config.Credentials).GetValue("ProdEyeUser","Password")
return ((prodeyeUser=user)&&(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) && $IsObject(response.Data) && 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") && (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.