Artigo
· Dez. 22, 2023 9min de leitura

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.

Discussão (0)2
Entre ou crie uma conta para continuar