DEV Community

InterSystems Developer for InterSystems

Posted on • Originally published at community.intersystems.com

IRIS Document Database (DocDB)

InterSystems IRIS Document Database (DocDB) offers a flexible and dynamic approach to managing database data. DocDB embraces the power of JSON (JavaScript Object Notation), providing a schema-less environment for storing and retrieving data.

It is a powerful tool, enables developers to bypass a ton of boiler plate code in interaction with existing applications, serialization, pagination and integration. the seamless flow of DocDB with Interoperability Rest services and operations, gives a big leap in API production and management.

for full DocDB documentation Here. in the context of this article i will showcase a use case in which DocDB will make a perfect fit.

Organizations using InterSystems Products like IRIS, Interoperability, TrakCare,... constantly generates agile and new requirements for sharing data through to other systems to consume. while InterSystems offers Built in JSON Adaptor to enable persistent classes to be JSON Serializable on record level, However neither rewriting your application to extent JSON Adaptor is an easy task nor your data of interest is going to be managed always on record level directly from your application classes. here comes DocDB to the picture, because you can build queries over your application code and dump it into DocDB as JSON, ready to ship to consumer apps.

 

here is a snippet running any standard IRIS stored procedure in a generic way  and saving output as records in json format.

 

        //assuming parameters exist as P1,P2,...Pn in context
        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
    
}

that is it, your data -whatever it is- is ready to be exposed to consuming apps.

lets take the use case of monitoring integrations, your queries will check the status of your InterSystems interoperability production, the errors, alerts,..

and the data will be saved in a DocDB on intervals.

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())
        }
}

}

now any external consuming app can fetch the data to check productions status, alerts, errors and warnings using a simple InterSystems REST service as the following

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
                }
        }
}

}

an actual example of a consuming app of the above use case is shared here on open exchange. it is a mobile app developed in dart for Flutter framework.

in addition to that, DocDB exposes direct API over all Document Tables , described in detail Here

The Edge that DocDB functionality offers is making API development for information sharing across systems driven by the use case focus while fast tracking and standardizing all the data serialization, staging and integration.

 

Top comments (0)